# 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:** ```bash # 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:** ```bash 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](backend/models.py#L74-L80)) - 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](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](app/utils/gradeCalculations.ts#L80-L110)) ## Code Conventions **Backend (FastAPI + MongoDB):** - All routes require auth via `Depends(get_current_user)` from [backend/routes/auth_routes.py](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](backend/models.py)) - Route structure: Each entity gets its own router in `backend/routes/`, imported in [main.py](backend/main.py#L40-L43) - Always use `await` with Motor async operations: `await db.collection.find().to_list()` **Frontend (React Router 7 SPA):** - Route definitions in [app/routes.ts](app/routes.ts), files in `app/routes/` (use `.tsx` extension) - All authenticated pages wrap children in `` component ([app/components/ProtectedRoute.tsx](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](backend/models.py) (Pydantic) - Frontend types in [app/types/api.ts](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](backend/routes/auth_routes.py)) 2. Frontend stores token in localStorage ([app/api/client.ts](app/api/client.ts#L56-L58)) 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](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](backend/main.py) 4. Add TypeScript types to [app/types/api.ts](app/types/api.ts) 5. Add API client methods to [app/api/client.ts](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](app/routes.ts) config array 3. Wrap in `` if auth required **MongoDB Query Pattern:** ```python 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 - [backend/main.py](backend/main.py) - FastAPI app initialization, SPA serving, route inclusion - [app/api/client.ts](app/api/client.ts) - Centralized API client with auth - [app/utils/gradeCalculations.ts](app/utils/gradeCalculations.ts) - Core business logic for grade calculations - [backend/models.py](backend/models.py) - Pydantic models with validation (category weights must sum to 100%) - [docker-compose.yml](docker-compose.yml) - Multi-stage build: frontend-build → backend serves result ## 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](DEVELOPMENT.md#L244-L254) 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`)