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