# AGENT.md — Developer Agent Guidelines for WhatIDo This document defines how AI agents should work with the WhatIDo codebase. Follow these rules strictly. --- ## Project Overview **WhatIDo** — Accounting system for an educational center: attendance journal, student project works, group gallery, detached files, and public showcase pages (share links). - **Stack**: Node.js 20 + Express, PostgreSQL 16, Docker Compose, Tailscale (Serve/Funnel) - **Architecture**: Single Express server (`server.js`) + static frontend in `public/` - **Deployment**: Docker Compose (app + db + tailscale), bind-mounted uploads, named volume for Postgres data - **Auth**: Admin-only via `X-Admin-Token` header (value = `ADMIN_PASSWORD` env var). No user sessions. --- ## Development Rules ### 1. Code Style - **No comments** unless explicitly requested - **ES modules not used** — CommonJS (`require`) throughout - **Error handling**: try/catch with explicit status codes, no global error handler - **Validation**: Inline helper functions (`reqInt`, `reqStr`, `optInt`, etc.) — use them - **Security first**: All uploads validated, path traversal blocked, rate limits on public routes ### 2. Database - **Schema**: Defined in `db/init.sql` (runs on first container start) - **Migrations**: `db/migration.sql` for existing DBs — update both when changing schema - **Connection**: Single `Pool` from `pg`, `DATABASE_URL` from env - **Queries**: Parameterized only (`$1`, `$2`...), never string interpolation - **Transactions**: Use `client.query('BEGIN')` / `COMMIT` / `ROLLBACK` for multi-statement ops ### 3. File Uploads - **Multer configs**: `upload` (images only), `adminUpload` (wider allowed ext), `uploadBackup` (restore) - **Limits**: 10 MB/file, 30 MB total per entry - **Storage**: `uploads/` bind-mounted to host, filenames = `timestamp-random.ext` - **HEIC**: Auto-converted to JPEG via `heic-convert` - **Cleanup**: `safeUnlink` / `sweepOrphanedUploads` — never delete outside `uploads/` ### 4. API Patterns - **Admin routes**: `requireAdmin` middleware (checks `X-Admin-Token`) - **Public routes**: `apiLimiter` (300/15min), `entryLimiter` (10/15min), `fileLimiter` (300/15min) - **Responses**: JSON, `{ error: 'message' }` on failure, data directly on success - **Pagination**: `limit` / `offset` query params, return `{ items, total }` or `{ entries, total }` - **Filters**: `group_id`, `date_from`, `date_to`, `student_name`, `search`, `deleted` ### 5. Frontend (public/) - Vanilla HTML/CSS/JS, no build step - Each page = single HTML file + shared `admin.js` / `admin.css` - API calls via `fetch` with `X-Admin-Token` from `localStorage` - Share pages (`share.html`, `links.html`) work without auth ### 6. Docker / Compose - **Dockerfile**: Node 20 Alpine, installs deps, generates self-signed TLS cert - **docker-compose.yml**: 3 services (db, app, tailscale) - `db`: postgres:16-alpine, healthcheck, init.sql mounted - `app`: builds from Dockerfile, exposes 3003/3443, mounts uploads - `tailscale`: host network, NET_ADMIN, runs `start-tailscale.sh` (funnel to 127.0.0.1:3443) - **Env vars** (required): `ADMIN_PASSWORD`, `DB_PASSWORD` - **Port 443 on host** must be free (tailscale listens directly) ### 7. Tailscale Publication - No external IP / port forwarding needed - Access: `https://whatido..ts.net` (inside tailnet + internet via Funnel) - First run: `docker exec -it whatido-tailscale-1 tailscale up --hostname=whatido` → authorize in browser - Enable Serve/Funnel in Tailscale admin console for the node - Cert: app generates self-signed cert at build (`certs/cert.pem`), mounted into tailscale container ### 8. Backup / Restore - **Admin UI**: `/api/backup` (download tar.gz), `/api/restore` (upload tar.gz) - **Scripts**: `scripts/backup.sh`, `scripts/restore.sh` (host-level) - Backup format: `data.json` (all tables) + `uploads/` directory - Restore validates all data, resets sequences, sweeps orphans --- ## Common Tasks ### Add a new API endpoint 1. Add route in `server.js` (group with related routes) 2. Use `requireAdmin` for admin, `apiLimiter`/`fileLimiter` for public 3. Validate input with helper functions 4. Use parameterized queries, transactions if multi-table 5. Call `logAudit(req, 'action.name', { ... })` for mutations 6. Return JSON, handle errors with appropriate status codes ### Add a database column/table 1. Update `db/init.sql` (CREATE TABLE / ALTER TABLE) 2. Update `db/migration.sql` (idempotent ALTERs) 3. Update `server.js` queries that SELECT/INSERT the table 4. Test: `docker compose down && docker compose up -d --build` ### Add a frontend page 1. Create `public/newpage.html` (copy structure from existing) 2. Link in `public/admin.html` navigation if admin page 3. Use `admin.js` utilities: `api()`, `requireAuth()`, `formatDate()`, etc. 4. No build step — just refresh browser ### Modify file upload rules - Edit `BLOCKED_EXT`, `ALLOWED_IMAGE_EXT`, `ADMIN_ALLOWED_EXT` constants - Update Multer `fileFilter` functions - Keep `MAX_TOTAL_UPLOAD_BYTES` and per-file limit in sync --- ## Testing & Verification No automated test suite exists. Verify manually: ```bash # Start stack docker compose up -d --build # Check logs docker compose logs -f app # Test API (replace TOKEN) curl -H "X-Admin-Token: $ADMIN_PASSWORD" http://localhost:3003/api/groups # Run backup/restore scripts ./scripts/backup.sh ./scripts/restore.sh backups/whatido-backup-.tar.gz ``` --- ## Security Checklist (before any change) - [ ] No SQL interpolation — only `$1`, `$2`... - [ ] Upload path validation via `isSafeUploadPath` / `safeUnlink` - [ ] Rate limiter on new public routes - [ ] Admin routes behind `requireAdmin` - [ ] No secrets in code — only via env vars - [ ] Helmet headers present (already global) - [ ] CORS disabled (no `cors` middleware) --- ## File Map (key files) | File | Purpose | |------|---------| | `server.js` | Entire backend (Express, routes, DB, uploads, backup) | | `db/init.sql` | Initial schema (runs on fresh DB) | | `db/migration.sql` | Idempotent migrations for existing DBs | | `docker-compose.yml` | Service definitions (app, db, tailscale) | | `Dockerfile` | App image build | | `public/*.html` | Frontend pages | | `public/admin.js` | Shared frontend logic | | `scripts/backup.sh` | Host-level backup script | | `scripts/restore.sh` | Host-level restore script | | `start-tailscale.sh` | Tailscale container entrypoint | | `.env.example` | Env var template | --- ## Do Not - ❌ Add dependencies without updating `package.json` and rebuilding - ❌ Write files outside `uploads/` or `certs/` - ❌ Commit `.env`, `certs/`, `uploads/`, `backups/`, `node_modules/` - ❌ Expose DB port (5432) outside docker network - ❌ Use `eval`, `Function` constructor, or dynamic code execution - ❌ Add comments to code (this file excepted) --- ## Quick Commands ```bash # Full rebuild docker compose down && docker compose up -d --build # App logs docker compose logs -f app # DB shell docker compose exec db psql -U app -d whereldo # Tailscale status docker exec -it whatido-tailscale-1 tailscale status # Manual funnel restart docker exec whatido-tailscale-1 tailscale funnel --bg --yes https://127.0.0.1:3443 ```