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

141 lines
7.0 KiB
Markdown

# 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 `<ProtectedRoute>` 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 `<ProtectedRoute>` 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`)