mirror of
https://github.com/EDeev/compile-hub.git
synced 2026-06-15 11:01:11 +03:00
334 lines
No EOL
9.8 KiB
Python
334 lines
No EOL
9.8 KiB
Python
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) |