mirror of
https://github.com/EDeev/compile-hub.git
synced 2026-06-15 11:01:11 +03:00
compiler c;p;j, first api and db events
This commit is contained in:
parent
26a53da8ec
commit
cae5b987d4
7 changed files with 835 additions and 0 deletions
116
compilers/base.py
Normal file
116
compilers/base.py
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import asyncio, tempfile, os, shutil
|
||||||
|
import subprocess, uuid
|
||||||
|
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class CompilerBase:
|
||||||
|
def __init__(self, language: str, timeout: int = 5):
|
||||||
|
self.language = language
|
||||||
|
self.timeout = timeout
|
||||||
|
self.max_output_size = 1000000 # 1MB
|
||||||
|
|
||||||
|
async def compile_and_run(self, code: str, input_data: str = "") -> Dict[str, Any]:
|
||||||
|
temp_dir = None
|
||||||
|
try:
|
||||||
|
temp_dir = tempfile.mkdtemp(prefix="compile_")
|
||||||
|
result = await self._execute(code, input_data, temp_dir)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Compilation error: {str(e)}"
|
||||||
|
}
|
||||||
|
finally:
|
||||||
|
if temp_dir and os.path.exists(temp_dir):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _execute(self, code: str, input_data: str, temp_dir: str) -> Dict[str, Any]:
|
||||||
|
raise NotImplementedError("Subclass must implement _execute method")
|
||||||
|
|
||||||
|
async def _run_process(self, cmd: list, input_data: str = "",
|
||||||
|
cwd: Optional[str] = None) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd,
|
||||||
|
stdin=asyncio.subprocess.PIPE,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
cwd=cwd
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout, stderr = await asyncio.wait_for(
|
||||||
|
process.communicate(input=input_data.encode() if input_data else None),
|
||||||
|
timeout=self.timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout_str = stdout.decode('utf-8', errors='replace')[:self.max_output_size]
|
||||||
|
stderr_str = stderr.decode('utf-8', errors='replace')[:self.max_output_size]
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": stderr_str or "Execution failed",
|
||||||
|
"output": stdout_str
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"output": stdout_str,
|
||||||
|
"error": stderr_str if stderr_str else None
|
||||||
|
}
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
if 'process' in locals():
|
||||||
|
process.kill()
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Execution timeout ({self.timeout}s exceeded)"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Execution error: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _sanitize_code(self, code: str) -> str:
|
||||||
|
# Базовая очистка кода
|
||||||
|
dangerous_patterns = [
|
||||||
|
"system(", "exec(", "eval(", "__import__",
|
||||||
|
"subprocess", "os.system", "popen"
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in dangerous_patterns:
|
||||||
|
if pattern in code:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return code
|
||||||
|
|
||||||
|
def _check_input_requirements(self, code: str) -> Dict[str, Any]:
|
||||||
|
# Проверка требований к вводу
|
||||||
|
input_keywords = {
|
||||||
|
"cpp": ["cin", "scanf", "getline"],
|
||||||
|
"python": ["input(", "raw_input"],
|
||||||
|
"javascript": ["readline", "prompt"]
|
||||||
|
}
|
||||||
|
|
||||||
|
keywords = input_keywords.get(self.language, [])
|
||||||
|
for keyword in keywords:
|
||||||
|
if keyword in code:
|
||||||
|
return {
|
||||||
|
"requiresInput": True,
|
||||||
|
"inputDescription": {
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"name": "input",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Program input data"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"requiresInput": False}
|
||||||
72
compilers/cpp.py
Normal file
72
compilers/cpp.py
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import os
|
||||||
|
from compilers.base import CompilerBase
|
||||||
|
|
||||||
|
class CppCompiler(CompilerBase):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("cpp", timeout=5)
|
||||||
|
|
||||||
|
async def _execute(self, code: str, input_data: str, temp_dir: str):
|
||||||
|
code = self._sanitize_code(code)
|
||||||
|
if not code:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Code contains forbidden operations"
|
||||||
|
}
|
||||||
|
|
||||||
|
source_file = os.path.join(temp_dir, "main.cpp")
|
||||||
|
exe_file = os.path.join(temp_dir, "main")
|
||||||
|
|
||||||
|
# Записываем код
|
||||||
|
with open(source_file, 'w') as f:
|
||||||
|
f.write(code)
|
||||||
|
|
||||||
|
# Компиляция
|
||||||
|
compile_result = await self._run_process(
|
||||||
|
["g++", "-o", exe_file, source_file, "-std=c++17", "-O2", "-Wall"],
|
||||||
|
cwd=temp_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
if not compile_result["success"]:
|
||||||
|
# Парсинг ошибок компиляции
|
||||||
|
error_lines = compile_result["error"].split('\n')
|
||||||
|
for line in error_lines:
|
||||||
|
if "error:" in line:
|
||||||
|
parts = line.split(':')
|
||||||
|
if len(parts) >= 3:
|
||||||
|
try:
|
||||||
|
line_num = int(parts[1])
|
||||||
|
message = ':'.join(parts[3:]).strip()
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Compilation error",
|
||||||
|
"details": {
|
||||||
|
"line": line_num,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": compile_result["error"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Проверка на требования ввода
|
||||||
|
input_check = self._check_input_requirements(code)
|
||||||
|
if input_check["requiresInput"] and not input_data:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"requiresInput": True,
|
||||||
|
"inputDescription": input_check["inputDescription"],
|
||||||
|
"error": "Program requires input"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Запуск
|
||||||
|
run_result = await self._run_process(
|
||||||
|
[exe_file],
|
||||||
|
input_data=input_data,
|
||||||
|
cwd=temp_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
return run_result
|
||||||
88
compilers/javascript.py
Normal file
88
compilers/javascript.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import os
|
||||||
|
from compilers.base import CompilerBase
|
||||||
|
|
||||||
|
class JavaScriptCompiler(CompilerBase):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("javascript", timeout=5)
|
||||||
|
|
||||||
|
async def _execute(self, code: str, input_data: str, temp_dir: str):
|
||||||
|
code = self._sanitize_code(code)
|
||||||
|
if not code:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Code contains forbidden operations"
|
||||||
|
}
|
||||||
|
|
||||||
|
source_file = os.path.join(temp_dir, "main.js")
|
||||||
|
|
||||||
|
# Обёртка для обработки ввода в Node.js
|
||||||
|
if input_data:
|
||||||
|
wrapped_code = f"""
|
||||||
|
const readline = require('readline');
|
||||||
|
const rl = readline.createInterface({{
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
}});
|
||||||
|
|
||||||
|
let inputLines = """ + repr(input_data.strip().split("\n")) + """;
|
||||||
|
let currentLine = 0;
|
||||||
|
|
||||||
|
global.readLine = function() {{
|
||||||
|
if (currentLine < inputLines.length) {{
|
||||||
|
return inputLines[currentLine++];
|
||||||
|
}}
|
||||||
|
return '';
|
||||||
|
}};
|
||||||
|
|
||||||
|
{code}
|
||||||
|
|
||||||
|
rl.close();
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
wrapped_code = code
|
||||||
|
|
||||||
|
# Записываем код
|
||||||
|
with open(source_file, 'w') as f:
|
||||||
|
f.write(wrapped_code)
|
||||||
|
|
||||||
|
# Проверка на требования ввода
|
||||||
|
input_check = self._check_input_requirements(code)
|
||||||
|
if input_check["requiresInput"] and not input_data:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"requiresInput": True,
|
||||||
|
"inputDescription": input_check["inputDescription"],
|
||||||
|
"error": "Program requires input"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Запуск Node.js
|
||||||
|
run_result = await self._run_process(
|
||||||
|
["node", source_file],
|
||||||
|
cwd=temp_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
# Парсинг ошибок JavaScript
|
||||||
|
if not run_result["success"] and run_result.get("error"):
|
||||||
|
error_lines = run_result["error"].split('\n')
|
||||||
|
for line in error_lines:
|
||||||
|
if ".js:" in line:
|
||||||
|
try:
|
||||||
|
parts = line.split(':')
|
||||||
|
if len(parts) >= 2:
|
||||||
|
line_num = int(parts[1])
|
||||||
|
# Поиск сообщения об ошибке
|
||||||
|
for err_line in error_lines:
|
||||||
|
if "Error:" in err_line or "error:" in err_line:
|
||||||
|
message = err_line.strip()
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Runtime error",
|
||||||
|
"details": {
|
||||||
|
"line": line_num,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return run_result
|
||||||
67
compilers/python.py
Normal file
67
compilers/python.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import os
|
||||||
|
from compilers.base import CompilerBase
|
||||||
|
|
||||||
|
class PythonCompiler(CompilerBase):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("python", timeout=5)
|
||||||
|
|
||||||
|
async def _execute(self, code: str, input_data: str, temp_dir: str):
|
||||||
|
code = self._sanitize_code(code)
|
||||||
|
if not code:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Code contains forbidden operations"
|
||||||
|
}
|
||||||
|
|
||||||
|
source_file = os.path.join(temp_dir, "main.py")
|
||||||
|
|
||||||
|
# Записываем код
|
||||||
|
with open(source_file, 'w') as f:
|
||||||
|
f.write(code)
|
||||||
|
|
||||||
|
# Проверка на требования ввода
|
||||||
|
input_check = self._check_input_requirements(code)
|
||||||
|
if input_check["requiresInput"] and not input_data:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"requiresInput": True,
|
||||||
|
"inputDescription": input_check["inputDescription"],
|
||||||
|
"error": "Program requires input"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Запуск Python
|
||||||
|
run_result = await self._run_process(
|
||||||
|
["python3", source_file],
|
||||||
|
input_data=input_data,
|
||||||
|
cwd=temp_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
# Парсинг ошибок Python
|
||||||
|
if not run_result["success"] and run_result.get("error"):
|
||||||
|
error_lines = run_result["error"].split('\n')
|
||||||
|
for i, line in enumerate(error_lines):
|
||||||
|
if "line" in line:
|
||||||
|
try:
|
||||||
|
# Извлечение номера строки
|
||||||
|
parts = line.split(',')
|
||||||
|
for part in parts:
|
||||||
|
if "line" in part:
|
||||||
|
line_num = int(part.split()[-1])
|
||||||
|
# Поиск сообщения об ошибке
|
||||||
|
if i + 1 < len(error_lines):
|
||||||
|
for j in range(i, len(error_lines)):
|
||||||
|
if "Error:" in error_lines[j]:
|
||||||
|
message = error_lines[j].strip()
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Runtime error",
|
||||||
|
"details": {
|
||||||
|
"line": line_num,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return run_result
|
||||||
168
database.py
Normal file
168
database.py
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import sqlite3, json
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class DBase:
|
||||||
|
def __init__(self, db_path):
|
||||||
|
"""Подключаемся к БД и сохраняем курсор соединения"""
|
||||||
|
self.connection = sqlite3.connect(db_path)
|
||||||
|
self.connection.row_factory = sqlite3.Row
|
||||||
|
self.cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
def init_db(self):
|
||||||
|
with self.connection:
|
||||||
|
# Таблица пользователей
|
||||||
|
self.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
email VARCHAR(100) UNIQUE NOT NULL,
|
||||||
|
username VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
password VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Таблица файлов и папок
|
||||||
|
self.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS files (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
type VARCHAR(10) NOT NULL,
|
||||||
|
size VARCHAR(20),
|
||||||
|
folder_id INTEGER,
|
||||||
|
code TEXT,
|
||||||
|
code_lang VARCHAR(20),
|
||||||
|
modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (folder_id) REFERENCES files(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Таблица лимитов
|
||||||
|
self.cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS limits (
|
||||||
|
user_id INTEGER PRIMARY KEY,
|
||||||
|
count_files INTEGER DEFAULT 0,
|
||||||
|
length_code INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Пользователи
|
||||||
|
def create_user(self, email: str, username: str, password: str) -> int:
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute(
|
||||||
|
"INSERT INTO users (email, username, password) VALUES (?, ?, ?)",
|
||||||
|
(email, username, password)
|
||||||
|
)
|
||||||
|
user_id = self.cursor.lastrowid
|
||||||
|
|
||||||
|
# Создаём запись лимитов
|
||||||
|
self.cursor.execute(
|
||||||
|
"INSERT INTO limits (user_id, count_files, length_code) VALUES (?, 0, 0)",
|
||||||
|
(user_id,)
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
|
||||||
|
row = self.cursor.fetchone()
|
||||||
|
return dict(row) if row else None
|
||||||
|
|
||||||
|
def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]:
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute("SELECT * FROM users WHERE email = ?", (email,))
|
||||||
|
row = self.cursor.fetchone()
|
||||||
|
return dict(row) if row else None
|
||||||
|
|
||||||
|
# Файлы
|
||||||
|
def create_file(self, user_id: int, name: str, file_type: str, size: str,
|
||||||
|
folder_id: Optional[int] = None, code: Optional[str] = None,
|
||||||
|
code_lang: Optional[str] = None) -> int:
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute('''
|
||||||
|
INSERT INTO files (user_id, name, type, size, folder_id, code, code_lang, modified)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
''', (user_id, name, file_type, size, folder_id, code, code_lang,
|
||||||
|
datetime.now().isoformat()))
|
||||||
|
|
||||||
|
file_id = self.cursor.lastrowid
|
||||||
|
|
||||||
|
# Обновляем лимиты
|
||||||
|
if file_type == "file":
|
||||||
|
self.cursor.execute(
|
||||||
|
"UPDATE limits SET count_files = count_files + 1 WHERE user_id = ?",
|
||||||
|
(user_id,)
|
||||||
|
)
|
||||||
|
if code:
|
||||||
|
self.cursor.execute(
|
||||||
|
"UPDATE limits SET length_code = length_code + ? WHERE user_id = ?",
|
||||||
|
(len(code), user_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
return file_id
|
||||||
|
|
||||||
|
def get_user_files(self, user_id: int) -> List[Dict[str, Any]]:
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute(
|
||||||
|
"SELECT * FROM files WHERE user_id = ? ORDER BY type DESC, name",
|
||||||
|
(user_id,)
|
||||||
|
)
|
||||||
|
rows = self.cursor.fetchall()
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
def get_file_by_id(self, file_id: int) -> Optional[Dict[str, Any]]:
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute("SELECT * FROM files WHERE id = ?", (file_id,))
|
||||||
|
row = self.cursor.fetchone()
|
||||||
|
return dict(row) if row else None
|
||||||
|
|
||||||
|
def update_file_folder(self, file_id: int, folder_id: Optional[int]):
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute(
|
||||||
|
"UPDATE files SET folder_id = ?, modified = ? WHERE id = ?",
|
||||||
|
(folder_id, datetime.now().isoformat(), file_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete_file(self, file_id: int):
|
||||||
|
with self.connection:
|
||||||
|
# Получаем информацию о файле
|
||||||
|
self.cursor.execute("SELECT * FROM files WHERE id = ?", (file_id,))
|
||||||
|
file = self.cursor.fetchone()
|
||||||
|
|
||||||
|
if file:
|
||||||
|
file_dict = dict(file)
|
||||||
|
user_id = file_dict["user_id"]
|
||||||
|
|
||||||
|
# Если это файл, обновляем лимиты
|
||||||
|
if file_dict["type"] == "file":
|
||||||
|
self.cursor.execute(
|
||||||
|
"UPDATE limits SET count_files = count_files - 1 WHERE user_id = ?",
|
||||||
|
(user_id,)
|
||||||
|
)
|
||||||
|
if file_dict["code"]:
|
||||||
|
self.cursor.execute(
|
||||||
|
"UPDATE limits SET length_code = length_code - ? WHERE user_id = ?",
|
||||||
|
(len(file_dict["code"]), user_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Удаляем файл
|
||||||
|
self.cursor.execute("DELETE FROM files WHERE id = ?", (file_id,))
|
||||||
|
|
||||||
|
# Если это папка, удаляем все файлы в ней
|
||||||
|
if file_dict["type"] == "folder":
|
||||||
|
self.cursor.execute("DELETE FROM files WHERE folder_id = ?", (file_id,))
|
||||||
|
|
||||||
|
def get_user_limits(self, user_id: int) -> Dict[str, int]:
|
||||||
|
with self.connection:
|
||||||
|
self.cursor.execute("SELECT * FROM limits WHERE user_id = ?", (user_id,))
|
||||||
|
row = self.cursor.fetchone()
|
||||||
|
return dict(row) if row else {"count_files": 0, "length_code": 0}
|
||||||
|
|
||||||
|
# ЗАКРЫТИЕ ВЫЗОВА
|
||||||
|
def close(self):
|
||||||
|
"""Закрываем соединение с БД"""
|
||||||
|
self.connection.close()
|
||||||
319
main.py
Normal file
319
main.py
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional, List
|
||||||
|
from collections import defaultdict, deque
|
||||||
|
import asyncio, time, hashlib
|
||||||
|
|
||||||
|
from database import DBase
|
||||||
|
from compilers.cpp import CppCompiler
|
||||||
|
from compilers.python import PythonCompiler
|
||||||
|
from compilers.javascript import JavaScriptCompiler
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
db = DBase("compilehub.db")
|
||||||
|
|
||||||
|
# CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Компиляторы
|
||||||
|
compilers = {
|
||||||
|
"cpp": CppCompiler(),
|
||||||
|
"python": PythonCompiler(),
|
||||||
|
"javascript": JavaScriptCompiler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Очередь задач компиляции
|
||||||
|
compilation_queue = asyncio.Queue(maxsize=100)
|
||||||
|
compilation_results = {}
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
rate_limit_storage = defaultdict(lambda: {"count": 0, "reset_time": time.time() + 60})
|
||||||
|
MAX_REQUESTS_PER_MINUTE = 30
|
||||||
|
|
||||||
|
# Метрики
|
||||||
|
metrics = {
|
||||||
|
"total_compilations": 0,
|
||||||
|
"failed_compilations": 0,
|
||||||
|
"avg_compilation_time": 0,
|
||||||
|
"compilation_times": deque(maxlen=100),
|
||||||
|
"active_users": set(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Модели Pydantic
|
||||||
|
class UserRegister(BaseModel):
|
||||||
|
email: str
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
class UserLogin(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
class FileItem(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
type: str # "file" или "folder"
|
||||||
|
size: str
|
||||||
|
modified: str
|
||||||
|
folder: Optional[int] = None
|
||||||
|
code: Optional[str] = None
|
||||||
|
code_lang: Optional[str] = None
|
||||||
|
|
||||||
|
class MoveFileRequest(BaseModel):
|
||||||
|
folderId: Optional[int]
|
||||||
|
|
||||||
|
class MigrateFilesRequest(BaseModel):
|
||||||
|
userId: str
|
||||||
|
files: List[FileItem]
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
def check_rate_limit(client_ip: str):
|
||||||
|
current_time = time.time()
|
||||||
|
limits = rate_limit_storage[client_ip]
|
||||||
|
|
||||||
|
if current_time >= limits["reset_time"]:
|
||||||
|
limits["count"] = 0
|
||||||
|
limits["reset_time"] = current_time + 60
|
||||||
|
|
||||||
|
if limits["count"] >= MAX_REQUESTS_PER_MINUTE:
|
||||||
|
return False
|
||||||
|
|
||||||
|
limits["count"] += 1
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Worker для обработки компиляций
|
||||||
|
async def compilation_worker():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
task = await compilation_queue.get()
|
||||||
|
task_id = task["id"]
|
||||||
|
code = task["code"]
|
||||||
|
language = task["language"]
|
||||||
|
input_data = task.get("input", "")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
compiler = compilers.get(language, compilers["cpp"])
|
||||||
|
result = await compiler.compile_and_run(code, input_data)
|
||||||
|
|
||||||
|
execution_time = time.time() - start_time
|
||||||
|
|
||||||
|
# Обновление метрик
|
||||||
|
metrics["total_compilations"] += 1
|
||||||
|
if not result["success"]:
|
||||||
|
metrics["failed_compilations"] += 1
|
||||||
|
metrics["compilation_times"].append(execution_time)
|
||||||
|
metrics["avg_compilation_time"] = sum(metrics["compilation_times"]) / len(metrics["compilation_times"])
|
||||||
|
|
||||||
|
result["executionTime"] = execution_time
|
||||||
|
compilation_results[task_id] = result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Worker error: {e}")
|
||||||
|
compilation_results[task_id] = {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_event():
|
||||||
|
db.init_db()
|
||||||
|
asyncio.create_task(compilation_worker())
|
||||||
|
|
||||||
|
# Auth endpoints
|
||||||
|
@app.post("/api/auth/register")
|
||||||
|
async def register(user: UserRegister):
|
||||||
|
existing = db.get_user_by_username(user.username)
|
||||||
|
if existing:
|
||||||
|
raise HTTPException(400, "Username already taken")
|
||||||
|
|
||||||
|
existing_email = db.get_user_by_email(user.email)
|
||||||
|
if existing_email:
|
||||||
|
raise HTTPException(400, "Email already registered")
|
||||||
|
|
||||||
|
password_hash = hashlib.sha256(user.password.encode()).hexdigest()
|
||||||
|
user_id = db.create_user(user.email, user.username, password_hash)
|
||||||
|
|
||||||
|
return {"message": "Successfully signed up, please login", "success": True}
|
||||||
|
|
||||||
|
@app.post("/api/auth/login")
|
||||||
|
async def login(user: UserLogin):
|
||||||
|
db_user = db.get_user_by_username(user.username)
|
||||||
|
if not db_user:
|
||||||
|
raise HTTPException(401, "Invalid credentials")
|
||||||
|
|
||||||
|
password_hash = hashlib.sha256(user.password.encode()).hexdigest()
|
||||||
|
if db_user["password"] != password_hash:
|
||||||
|
raise HTTPException(401, "Invalid credentials")
|
||||||
|
|
||||||
|
metrics["active_users"].add(db_user["id"])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": str(db_user["id"]),
|
||||||
|
"email": db_user["email"],
|
||||||
|
"username": db_user["username"],
|
||||||
|
"token": hashlib.sha256(f"{db_user['id']}{time.time()}".encode()).hexdigest(),
|
||||||
|
"isGuest": False
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.post("/api/auth/logout")
|
||||||
|
async def logout():
|
||||||
|
return {"message": "Logged out successfully"}
|
||||||
|
|
||||||
|
@app.get("/api/files")
|
||||||
|
async def get_files(userId: str):
|
||||||
|
user_files = db.get_user_files(int(userId))
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for file in user_files:
|
||||||
|
item = {
|
||||||
|
"id": file["id"],
|
||||||
|
"name": file["name"],
|
||||||
|
"type": file["type"],
|
||||||
|
"size": file["size"],
|
||||||
|
"modified": file["modified"],
|
||||||
|
}
|
||||||
|
if file["type"] == "file":
|
||||||
|
item["folder"] = file["folder_id"]
|
||||||
|
items.append(item)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
@app.delete("/api/files/{file_id}")
|
||||||
|
async def delete_file(file_id: int):
|
||||||
|
db.delete_file(file_id)
|
||||||
|
return {"message": "File deleted"}
|
||||||
|
|
||||||
|
@app.patch("/api/files/{file_id}/move")
|
||||||
|
async def move_file(file_id: int, request: MoveFileRequest):
|
||||||
|
db.update_file_folder(file_id, request.folderId)
|
||||||
|
return {"message": "File moved"}
|
||||||
|
|
||||||
|
@app.post("/api/files/migrate")
|
||||||
|
async def migrate_files(request: MigrateFilesRequest):
|
||||||
|
for file in request.files:
|
||||||
|
db.create_file(
|
||||||
|
user_id=int(request.userId),
|
||||||
|
name=file.name,
|
||||||
|
file_type=file.type,
|
||||||
|
size=file.size,
|
||||||
|
folder_id=file.folder,
|
||||||
|
code=file.code,
|
||||||
|
code_lang=file.code_lang
|
||||||
|
)
|
||||||
|
return {"message": "Files migrated"}
|
||||||
|
|
||||||
|
# Compilation endpoints
|
||||||
|
@app.get("/api/code")
|
||||||
|
async def get_code(fileId: int):
|
||||||
|
file = db.get_file_by_id(fileId)
|
||||||
|
if not file:
|
||||||
|
raise HTTPException(404, "File not found")
|
||||||
|
return file.get("code", "")
|
||||||
|
|
||||||
|
class CompileRequest(BaseModel):
|
||||||
|
code: str
|
||||||
|
language: Optional[str] = None
|
||||||
|
input: Optional[str] = None
|
||||||
|
|
||||||
|
@app.post("/api/compile/")
|
||||||
|
async def compile_code_post(request: Request, compile_req: CompileRequest):
|
||||||
|
client_ip = request.client.host
|
||||||
|
|
||||||
|
if not check_rate_limit(client_ip):
|
||||||
|
raise HTTPException(429, "Rate limit exceeded")
|
||||||
|
|
||||||
|
code = compile_req.code
|
||||||
|
input_data = compile_req.input or ""
|
||||||
|
|
||||||
|
# Определение языка
|
||||||
|
language = compile_req.language
|
||||||
|
if not language:
|
||||||
|
if "#include" in code:
|
||||||
|
language = "cpp"
|
||||||
|
elif "print(" in code or "def " in code or "import " in code:
|
||||||
|
language = "python"
|
||||||
|
elif "console.log" in code or "function" in code or "const " in code:
|
||||||
|
language = "javascript"
|
||||||
|
else:
|
||||||
|
language = "cpp"
|
||||||
|
|
||||||
|
task_id = hashlib.sha256(f"{code}{time.time()}".encode()).hexdigest()[:16]
|
||||||
|
|
||||||
|
await compilation_queue.put({
|
||||||
|
"id": task_id,
|
||||||
|
"code": code,
|
||||||
|
"language": language,
|
||||||
|
"input": input_data
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ждём результат (max 10 секунд)
|
||||||
|
for _ in range(100):
|
||||||
|
if task_id in compilation_results:
|
||||||
|
result = compilation_results.pop(task_id)
|
||||||
|
return result
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Compilation timeout"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/api/compile/")
|
||||||
|
async def compile_code(request: Request, code: str, input: Optional[str] = None):
|
||||||
|
client_ip = request.client.host
|
||||||
|
|
||||||
|
if not check_rate_limit(client_ip):
|
||||||
|
raise HTTPException(429, "Rate limit exceeded")
|
||||||
|
|
||||||
|
# Определение языка по расширению или синтаксису
|
||||||
|
language = "cpp" # по умолчанию
|
||||||
|
if "print(" in code or "def " in code or "import " in code:
|
||||||
|
language = "python"
|
||||||
|
elif "console.log" in code or "function" in code:
|
||||||
|
language = "javascript"
|
||||||
|
|
||||||
|
task_id = hashlib.sha256(f"{code}{time.time()}".encode()).hexdigest()[:16]
|
||||||
|
|
||||||
|
await compilation_queue.put({
|
||||||
|
"id": task_id,
|
||||||
|
"code": code,
|
||||||
|
"language": language,
|
||||||
|
"input": input or ""
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ждём результат (max 10 секунд)
|
||||||
|
for _ in range(100):
|
||||||
|
if task_id in compilation_results:
|
||||||
|
result = compilation_results.pop(task_id)
|
||||||
|
return result
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Compilation timeout"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Metrics endpoint
|
||||||
|
@app.get("/api/metrics")
|
||||||
|
async def get_metrics():
|
||||||
|
return {
|
||||||
|
"total_compilations": metrics["total_compilations"],
|
||||||
|
"failed_compilations": metrics["failed_compilations"],
|
||||||
|
"success_rate": ((metrics["total_compilations"] - metrics["failed_compilations"]) / max(metrics["total_compilations"], 1)) * 100,
|
||||||
|
"avg_compilation_time": round(metrics["avg_compilation_time"], 3),
|
||||||
|
"active_users": len(metrics["active_users"]),
|
||||||
|
"queue_size": compilation_queue.qsize()
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="192.168.3.29", port=9999)
|
||||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
fastapi==0.104.1
|
||||||
|
uvicorn[standard]==0.24.0
|
||||||
|
pydantic==2.5.0
|
||||||
|
python-multipart==0.0.6
|
||||||
|
aiofiles==23.2.1
|
||||||
Loading…
Add table
Reference in a new issue