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.get("/api/auth/check-username") async def check_username_availability(username: str): if not username or len(username.strip()) == 0: raise HTTPException(400, "Username cannot be empty") if len(username) < 3 or len(username) > 20: raise HTTPException(400, "Username must be between 3 and 20 characters") existing_user = db.get_user_by_username(username.strip()) return { "available": existing_user is None, "username": username.strip() } @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)