Files
grademaxxing/.github/copilot-instructions.md
2026-01-17 13:37:57 +01:00

7.0 KiB

Grademaxxing - AI Coding Agent Instructions

Project Architecture

Fullstack SPA Architecture:

  • Frontend: React Router 7 SPA (client-side only, no SSR) built with Vite + TypeScript + Tailwind CSS v4
  • Backend: FastAPI (Python) serves both API (/api/*) and built React SPA (build/client/)
  • Database: MongoDB with Motor async driver
  • Deployment: Docker Compose with 3 services (mongodb, frontend-build, backend)

Critical Architectural Decision: Backend serves the compiled React SPA from build/client/ directory. All non-/api/* routes fall through to index.html for client-side routing. Frontend must be built before backend serves it.

Essential Workflows

Development:

# Docker (recommended) - starts MongoDB + builds frontend + runs backend
docker-compose up --build

# Local development (3 terminals)
# Terminal 1: cd backend && uvicorn main:app --reload
# Terminal 2: pnpm dev
# Terminal 3: mongod --dbpath ./data

Build & Deploy:

pnpm build              # Builds React SPA to build/client/
# Backend automatically serves from build/client/ when running

Quick start scripts: start.bat (Windows) or ./start.sh (Linux/Mac) wrap docker-compose up --build

Data Model & Business Logic

Flexible Grading System (Not German-specific):

  • Each Subject has custom grading_categories (e.g., "Written" 60%, "Oral" 40%)
  • Categories must sum to 100% weight (validated in backend/models.py)
  • Each Grade has grade/max_grade (e.g., 85/100) and weight_in_category for multiple tests
  • Supports any grading scale (percentage, German 1-6, US letters, etc.)

Grade Calculation Pipeline (app/utils/gradeCalculations.ts):

  1. Normalize each grade to percentage: (grade / max_grade) * 100
  2. Calculate category average: weighted sum of grades in category
  3. Calculate subject average: weighted sum of category averages
  4. Calculate overall GPA: simple average of all subject averages

Report Periods: Filter grades by date range for semester/quarter calculations (see calculateReportCard)

Code Conventions

Backend (FastAPI + MongoDB):

  • All routes require auth via Depends(get_current_user) from backend/routes/auth_routes.py
  • MongoDB ObjectId handling: Convert to string for JSON responses, convert to ObjectId() for queries
  • Model pattern: {Entity}Base{Entity}Create/Update{Entity}InDB{Entity}Response (see backend/models.py)
  • Route structure: Each entity gets its own router in backend/routes/, imported in main.py
  • Always use await with Motor async operations: await db.collection.find().to_list()

Frontend (React Router 7 SPA):

  • Route definitions in app/routes.ts, files in app/routes/ (use .tsx extension)
  • All authenticated pages wrap children in <ProtectedRoute> component (app/components/ProtectedRoute.tsx)
  • API client singleton: import { api } from "~/api/client" - handles auth headers automatically
  • Auth token stored in localStorage (access_token), set on login/register, removed on logout
  • Component pattern: Small reusable components in app/components/, import as ~/components/{Name}
  • Tailwind CSS only - no CSS modules or styled-components

TypeScript Type Safety:

  • Backend types defined in backend/models.py (Pydantic)
  • Frontend types in app/types/api.ts - manually sync with backend models
  • Use _id (alias) for MongoDB ObjectId in responses, map to id field in TypeScript when needed

Authentication & Authorization

Flow:

  1. Login/register → Backend returns JWT + user data (backend/routes/auth_routes.py)
  2. Frontend stores token in localStorage (app/api/client.ts)
  3. All protected API calls include Authorization: Bearer {token} header
  4. Backend validates token with get_current_user dependency
  5. Frontend uses ProtectedRoute wrapper to redirect unauthenticated users

User Isolation: All database queries filter by user_id (from token) - users only see their own data

Common Patterns

Adding a New Entity:

  1. Define Pydantic models in backend/models.py: {Entity}Base, {Entity}Create, {Entity}InDB, {Entity}Response
  2. Create router in backend/routes/{entity}_routes.py with CRUD endpoints
  3. Import and include router in backend/main.py
  4. Add TypeScript types to app/types/api.ts
  5. Add API client methods to app/api/client.ts
  6. Create routes/components in app/

Adding a New Route:

  1. Create app/routes/{name}.tsx with default export component
  2. Add route to app/routes.ts config array
  3. Wrap in <ProtectedRoute> if auth required

MongoDB Query Pattern:

db = get_database()
cursor = db.collection.find({"user_id": current_user.id})
items = await cursor.to_list(length=None)
for item in items:
    item["_id"] = str(item["_id"])  # Convert ObjectId to string
return [ResponseModel(**item) for item in items]

Critical Files

Known Issues & Gotchas

  • Frontend build must complete before backend serves (frontend-build container in Docker)
  • MongoDB ObjectId serialization: Always convert to string for JSON responses
  • CORS: Backend allows origins from CORS_ORIGINS env var (comma-separated)
  • Category weights validation: Must sum to 100% (enforced in Pydantic model)
  • Date handling: Python uses datetime.utcnow(), frontend converts ISO strings
  • No SSR: React Router in SPA mode only, backend serves static build

Testing

Manual testing: See DEVELOPMENT.md checklist (register → create period → add subject → add grades → view dashboard)

API Testing: FastAPI auto-docs at http://localhost:8000/docs (Swagger UI)

Environment Setup

Required Environment Variables:

  • MONGODB_URL - MongoDB connection string (default: mongodb://localhost:27017)
  • SECRET_KEY - JWT signing key (generate with python -c "import secrets; print(secrets.token_urlsafe(64))")
  • DATABASE_NAME - MongoDB database name (default: grademaxxing)
  • CORS_ORIGINS - Comma-separated allowed origins (default: http://localhost:5173,http://localhost:8000)
  • ACCESS_TOKEN_EXPIRE_MINUTES - JWT expiration (default: 10080 = 7 days)

Frontend uses VITE_API_URL for API base URL (defaults to http://localhost:8000/api)