first commit

This commit is contained in:
Space
2026-01-17 13:37:57 +01:00
commit 3e34d84a29
49 changed files with 8579 additions and 0 deletions

168
backend/main.py Normal file
View File

@@ -0,0 +1,168 @@
from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, StreamingResponse
from contextlib import asynccontextmanager
import os
from pathlib import Path
from io import StringIO
import csv
from typing import Optional
from config import settings
from database import connect_to_mongo, close_mongo_connection, get_database
from routes import auth_routes, subject_routes, grade_routes, period_routes, teacher_grade_routes
from routes.auth_routes import get_current_user
from models import UserInDB
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan events"""
# Startup
await connect_to_mongo()
yield
# Shutdown
await close_mongo_connection()
app = FastAPI(
title="Grademaxxing API",
description="Grade management system API",
version="1.0.0",
lifespan=lifespan
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# API routes
app.include_router(auth_routes.router, prefix="/api")
app.include_router(subject_routes.router, prefix="/api")
app.include_router(grade_routes.router, prefix="/api")
app.include_router(period_routes.router, prefix="/api")
app.include_router(teacher_grade_routes.router, prefix="/api")
@app.get("/api/health")
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "version": "1.0.0"}
@app.get("/api/export/grades")
async def export_grades_csv(
period_id: Optional[str] = None,
current_user: UserInDB = Depends(get_current_user)
):
"""Export all grades as CSV, optionally filtered by period"""
db = get_database()
# Get all subjects for the user
subjects_cursor = db.subjects.find({"user_id": current_user.id})
subjects = await subjects_cursor.to_list(length=None)
subject_dict = {str(subject["_id"]): subject for subject in subjects}
# Build grade query
grade_query = {"user_id": current_user.id}
# If period_id is provided, filter grades by date range
if period_id:
period = await db.periods.find_one({"_id": period_id, "user_id": current_user.id})
if period:
grade_query["date"] = {
"$gte": period["start_date"],
"$lte": period["end_date"]
}
# Get all grades
grades_cursor = db.grades.find(grade_query).sort("date", -1)
grades = await grades_cursor.to_list(length=None)
# Create CSV in memory
output = StringIO()
writer = csv.writer(output)
# Write header
writer.writerow([
"Subject",
"Category",
"Grade Name",
"Grade",
"Max Grade",
"Percentage",
"Weight in Category",
"Date",
"Notes"
])
# Write grade rows
for grade in grades:
subject = subject_dict.get(grade["subject_id"])
subject_name = subject["name"] if subject else "Unknown Subject"
# Calculate percentage
percentage = (grade["grade"] / grade["max_grade"] * 100) if grade["max_grade"] > 0 else 0
writer.writerow([
subject_name,
grade.get("category_name", ""),
grade.get("name", ""),
grade["grade"],
grade["max_grade"],
f"{percentage:.2f}%",
grade.get("weight_in_category", 1.0),
grade["date"].strftime("%Y-%m-%d") if "date" in grade else "",
grade.get("notes", "")
])
# Prepare response
output.seek(0)
filename = f"grades_export_{current_user.username}.csv"
if period_id:
period = await db.periods.find_one({"_id": period_id, "user_id": current_user.id})
if period:
filename = f"grades_{period['name'].replace(' ', '_')}.csv"
return StreamingResponse(
iter([output.getvalue()]),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename={filename}"}
)
# Serve static files (React SPA)
static_dir = Path(__file__).parent.parent / "build" / "client"
if static_dir.exists():
app.mount("/assets", StaticFiles(directory=static_dir / "assets"), name="assets")
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
"""Serve React SPA for all non-API routes"""
# Check if the file exists
file_path = static_dir / full_path
if file_path.is_file():
return FileResponse(file_path)
# For all other routes, serve index.html (SPA routing)
index_path = static_dir / "index.html"
if index_path.exists():
return FileResponse(index_path)
raise HTTPException(status_code=404, detail="Not found")
else:
print(f"Warning: Static directory not found at {static_dir}")
print("Run the frontend build first to serve the UI")
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)