141 lines
7.0 KiB
Markdown
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`)
|