mirror of
https://github.com/karma-riuk/crab-webapp.git
synced 2025-07-06 06:28:12 +02:00
ported backend to python
This commit is contained in:
@ -1,92 +0,0 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import datasetsRouter from '../datasets.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Mock the paths utility
|
||||
jest.mock('../../utils/paths.js', () => ({
|
||||
getProjectPath: (path) => join(__dirname, '../../..', path)
|
||||
}));
|
||||
|
||||
// Create Express app for testing
|
||||
const app = express();
|
||||
app.use('/datasets', datasetsRouter);
|
||||
|
||||
describe('Datasets Router', () => {
|
||||
// Mock environment variables
|
||||
const originalEnv = process.env;
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = { ...originalEnv };
|
||||
process.env.DATA_DIR = './test-data';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('GET /download/:dataset', () => {
|
||||
it('should return 400 for invalid dataset name', async () => {
|
||||
const response = await request(app)
|
||||
.get('/datasets/download/invalid_dataset')
|
||||
.expect(400);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
error: 'Invalid dataset name'
|
||||
});
|
||||
});
|
||||
|
||||
it('should download comment_generation without context', async () => {
|
||||
const response = await request(app)
|
||||
.get('/datasets/download/comment_generation')
|
||||
.expect(200);
|
||||
|
||||
expect(response.headers['content-type']).toBe('application/zip');
|
||||
expect(response.headers['content-disposition']).toContain('comment_generation_no_context.zip');
|
||||
});
|
||||
|
||||
it('should download comment_generation with context', async () => {
|
||||
const response = await request(app)
|
||||
.get('/datasets/download/comment_generation')
|
||||
.query({ withContext: true })
|
||||
.expect(200);
|
||||
|
||||
expect(response.headers['content-type']).toBe('application/zip');
|
||||
expect(response.headers['content-disposition']).toContain('comment_generation_with_context.zip');
|
||||
});
|
||||
|
||||
it('should download code_refinement without context', async () => {
|
||||
const response = await request(app)
|
||||
.get('/datasets/download/code_refinement')
|
||||
.expect(200);
|
||||
|
||||
expect(response.headers['content-type']).toBe('application/zip');
|
||||
expect(response.headers['content-disposition']).toContain('code_refinement_no_context.zip');
|
||||
});
|
||||
|
||||
it('should download code_refinement with context', async () => {
|
||||
const response = await request(app)
|
||||
.get('/datasets/download/code_refinement')
|
||||
.query({ withContext: true })
|
||||
.expect(200);
|
||||
|
||||
expect(response.headers['content-type']).toBe('application/zip');
|
||||
expect(response.headers['content-disposition']).toContain('code_refinement_with_context.zip');
|
||||
});
|
||||
|
||||
it('should handle JSON boolean for withContext parameter', async () => {
|
||||
const response = await request(app)
|
||||
.get('/datasets/download/comment_generation')
|
||||
.query({ withContext: 'true' })
|
||||
.expect(200);
|
||||
|
||||
expect(response.headers['content-disposition']).toContain('comment_generation_with_context.zip');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,129 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import multer from "multer";
|
||||
import { InvalidJsonFormatError } from "../utils/errors.js";
|
||||
import { evaluate_comments } from "../utils/process_data.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Configure multer for file uploads
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: {
|
||||
fileSize: 200 * 1024 * 1024, // 200MB limit, since the comement gen is 147MB (deflated)
|
||||
},
|
||||
fileFilter: (_req, file, cb) => {
|
||||
// Accept only JSON files
|
||||
if (file.mimetype === "application/json") {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error("Only JSON files are allowed"));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Helper function to validate JSON format
|
||||
const validateJsonFormat = (data) => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
// Check if it's an object
|
||||
if (
|
||||
typeof parsed !== "object" ||
|
||||
parsed === null ||
|
||||
Array.isArray(parsed)
|
||||
) {
|
||||
throw new InvalidJsonFormatError(
|
||||
"Submitted json doesn't contain an object",
|
||||
);
|
||||
}
|
||||
// Check if all values are strings
|
||||
if (
|
||||
!Object.values(parsed).every((value) => typeof value === "string")
|
||||
) {
|
||||
throw new InvalidJsonFormatError(
|
||||
"Submitted json object must only be str -> str. Namely id -> comment",
|
||||
);
|
||||
}
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidJsonFormatError) {
|
||||
throw error;
|
||||
}
|
||||
throw new InvalidJsonFormatError("Invalid JSON format");
|
||||
}
|
||||
};
|
||||
|
||||
router.post("/submit/comments", upload.single("file"), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: "No file uploaded" });
|
||||
}
|
||||
|
||||
const fileContent = req.file.buffer.toString();
|
||||
let validatedData;
|
||||
|
||||
try {
|
||||
validatedData = validateJsonFormat(fileContent);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidJsonFormatError) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid JSON format",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const io = req.app.get("io");
|
||||
const header = req.get("X-Socket-Id");
|
||||
const socketId = header && header.trim();
|
||||
if (socketId && io.sockets.sockets.has(socketId)) {
|
||||
io.to(socketId).emit("successul-upload");
|
||||
io.to(socketId).emit("started-processing");
|
||||
}
|
||||
|
||||
const results = evaluate_comments(validatedData, (percent) => {
|
||||
if (!(socketId && io.sockets.sockets.has(socketId))) return;
|
||||
|
||||
io.to(socketId).emit("progress", { percent });
|
||||
});
|
||||
res.status(200).json(results);
|
||||
} catch (error) {
|
||||
console.error("Error processing submission:", error);
|
||||
res.status(500).json({ error: "Error processing submission" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/submit/refinement", upload.single("file"), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: "No file uploaded" });
|
||||
}
|
||||
|
||||
const fileContent = req.file.buffer.toString();
|
||||
let validatedData;
|
||||
|
||||
try {
|
||||
validatedData = validateJsonFormat(fileContent);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidJsonFormatError) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid JSON format",
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
socket.emit("started-processing");
|
||||
evaluate_comments(validatedData);
|
||||
res.status(200).json({
|
||||
message: "Answer submitted successfully",
|
||||
data: validatedData,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error processing submission:", error);
|
||||
res.status(500).json({ error: "Error processing submission" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
55
src/routes/answers.py
Normal file
55
src/routes/answers.py
Normal file
@ -0,0 +1,55 @@
|
||||
# routes/answers.py
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from utils.errors import InvalidJsonFormatError
|
||||
from utils.process_data import evaluate_comments
|
||||
import json
|
||||
|
||||
router = Blueprint('answers', __name__, url_prefix='/answers')
|
||||
|
||||
ALLOWED_EXT = {'json'}
|
||||
|
||||
|
||||
def validate_json_format(data: str) -> dict[str, str]:
|
||||
try:
|
||||
obj = json.loads(data)
|
||||
if not isinstance(obj, dict):
|
||||
raise InvalidJsonFormatError("Submitted json doesn't contain an object")
|
||||
if not all(isinstance(v, str) for v in obj.values()):
|
||||
raise InvalidJsonFormatError(
|
||||
"Submitted json object must only be str -> str. Namely id -> comment"
|
||||
)
|
||||
return obj
|
||||
except InvalidJsonFormatError as e:
|
||||
raise e
|
||||
except Exception:
|
||||
raise InvalidJsonFormatError()
|
||||
|
||||
|
||||
@router.route('/submit/comments', methods=['POST'])
|
||||
def submit_comments():
|
||||
file = request.files.get('file')
|
||||
if file is None or file.filename is None or file.filename.split('.')[-1] not in ALLOWED_EXT:
|
||||
return jsonify({'error': 'Only JSON files are allowed'}), 400
|
||||
data = file.read().decode()
|
||||
try:
|
||||
validated = validate_json_format(data)
|
||||
except InvalidJsonFormatError as e:
|
||||
return jsonify({'error': 'Invalid JSON format', 'message': str(e)}), 400
|
||||
|
||||
socketio = current_app.extensions['socketio']
|
||||
sid = request.headers.get('X-Socket-Id')
|
||||
if sid:
|
||||
socketio.emit('successful-upload', room=sid)
|
||||
socketio.emit('started-processing', room=sid)
|
||||
|
||||
results = evaluate_comments(
|
||||
validated, lambda p: socketio.emit('progress', {'percent': p}, room=sid)
|
||||
)
|
||||
return jsonify(results)
|
||||
|
||||
|
||||
@router.route('/submit/refinement', methods=['POST'])
|
||||
def submit_refinement():
|
||||
file = request.files.get('file')
|
||||
# similar to above
|
||||
return jsonify({'message': 'Answer submitted successfully'})
|
@ -1,33 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import { join } from "path";
|
||||
import { getProjectPath } from "../utils/paths.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Environment variables for paths (all relative to project root)
|
||||
const DATA_DIR = getProjectPath("data");
|
||||
|
||||
const DATASETS = ["comment_generation", "code_refinement"];
|
||||
|
||||
router.get("/download/:dataset", async (req, res) => {
|
||||
const { dataset } = req.params;
|
||||
const withContext = req.query.withContext
|
||||
? JSON.parse(req.query.withContext)
|
||||
: false;
|
||||
|
||||
if (!DATASETS.includes(dataset)) {
|
||||
return res.status(400).json({ error: "Invalid dataset name" });
|
||||
}
|
||||
|
||||
const fileName = `${dataset}_${withContext ? "with_context" : "no_context"}.zip`;
|
||||
const filePath = join(DATA_DIR, fileName);
|
||||
|
||||
try {
|
||||
res.download(filePath);
|
||||
} catch (error) {
|
||||
console.error("Error serving file:", error);
|
||||
res.status(500).json({ error: "Error serving file" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
17
src/routes/datasets.py
Normal file
17
src/routes/datasets.py
Normal file
@ -0,0 +1,17 @@
|
||||
# routes/datasets.py
|
||||
from flask import Blueprint, send_from_directory, request, jsonify
|
||||
from utils.paths import get_project_path
|
||||
|
||||
router = Blueprint('datasets', __name__, url_prefix='/datasets')
|
||||
|
||||
DATASETS = {'comment_generation', 'code_refinement'}
|
||||
DATA_DIR = get_project_path('../data')
|
||||
|
||||
|
||||
@router.route('/download/<dataset>')
|
||||
def download(dataset):
|
||||
if dataset not in DATASETS:
|
||||
return jsonify({'error': 'Invalid dataset name'}), 400
|
||||
with_ctx = request.args.get('withContext', 'false').lower() == 'true'
|
||||
fname = f"{dataset}_{'with_context' if with_ctx else 'no_context'}.zip"
|
||||
return send_from_directory(DATA_DIR, fname, as_attachment=True)
|
@ -1,23 +0,0 @@
|
||||
import { Router } from 'express';
|
||||
import datasetRoutes from './datasets.js';
|
||||
import answerRoutes from './answers.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Routes
|
||||
router.get('/', (_req, res) => {
|
||||
res.json({ message: 'Welcome to the Express backend!' });
|
||||
});
|
||||
|
||||
// Example route
|
||||
router.get('/api/hello', (_req, res) => {
|
||||
res.json({ message: 'Hello from the backend!' });
|
||||
});
|
||||
|
||||
// Dataset routes
|
||||
router.use('/datasets', datasetRoutes);
|
||||
|
||||
// Answer submission routes
|
||||
router.use('/answers', answerRoutes);
|
||||
|
||||
export default router;
|
16
src/routes/index.py
Normal file
16
src/routes/index.py
Normal file
@ -0,0 +1,16 @@
|
||||
# routes/index.py
|
||||
from flask import Blueprint, jsonify, current_app
|
||||
|
||||
|
||||
router = Blueprint('index', __name__)
|
||||
|
||||
|
||||
@router.route('/')
|
||||
def welcome():
|
||||
print("hello")
|
||||
return current_app.send_static_file('index.html')
|
||||
|
||||
|
||||
@router.route('/api/hello')
|
||||
def hello():
|
||||
return jsonify({'message': 'Hello from the backend!'})
|
Reference in New Issue
Block a user