- storage.js: абстракция хранилища с драйверами local и s3 (AWS SDK v3), ключи объектов совпадают с текущими путями /uploads/<файл>, поэтому схема БД и URL не меняются - docker-compose.yml: сервис s3 (SeaweedFS, том s3-data, API только на loopback), переменные S3_*/STORAGE_*, restart unless-stopped для app и db - docker-compose.minio.yml: оверрайд S3-сервиса на MinIO (образ из своего зеркала) - server.js/worker.js: чтение и запись файлов только через storage (отдача /uploads/*, миниатюры, share-файлы, zip-отчёты, enhance/apply/rollback, photo-worker), автосоздание бакета, глобальная персистенция загрузок multer - бэкап/восстановление и scripts/backup.sh, restore.sh — через scripts/storage-sync.js - scripts/migrate-to-s3.js: идемпотентная миграция uploads/ в бакет (--dry-run, --verify-only, --delete-local) - админка: блок «Хранилище» в системной информации - .env.example, README.md, AGENTS.md: описание драйверов, переменных и перехода на S3
11 KiB
11 KiB
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, S3-совместимое хранилище файлов, Tailscale (Serve/Funnel)
- Architecture: Single Express server (
server.js) + storage abstraction (storage.js) + static frontend inpublic/ - Deployment: Docker Compose (app + db + s3 + tailscale), bind-mounted uploads, named volumes for Postgres and S3 data
- Auth: Admin-only via
X-Admin-Tokenheader (value =ADMIN_PASSWORDenv 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.sqlfor existing DBs — update both when changing schema - Connection: Single
Poolfrompg,DATABASE_URLfrom env - Queries: Parameterized only (
$1,$2...), never string interpolation - Transactions: Use
client.query('BEGIN')/COMMIT/ROLLBACKfor 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
- Staging: Multer always writes to
uploads/(timestamp-random.ext); a globalres.on('finish')hook persists each uploaded file throughstorage.persiston successful responses (only whenSTORAGE_DRIVER=s3) - HEIC: Auto-converted to JPEG via
heic-convert - Cleanup:
safeUnlink/sweepOrphanedUploads— never delete outsideuploads/or the configured bucket
3a. Storage (storage.js)
- Drivers:
local(default, files inuploads/) ands3(S3-compatible: SeaweedFS by default, MinIO viadocker-compose.minio.yml) - Keys are stable: DB stores
/uploads/<name>; S3 object keys are the same<name>(plus.originals/<name>). Never change key format — it would break existing DB rows and URLs - API:
put,putFile,head,exists,sizeOf,getStream,getBuffer,del,copyObject,listAll,localize,persist,streamTo,downloadAll,uploadTree,ensureBucket,usage,pruneCache - Rules: never call
fs.*onuploads/directly in request/worker code — usestorage.*.safeUnlinkis the only deletion helper (local + remote, idempotent) - Read path:
STORAGE_LOCAL_FALLBACK=1prefers a local file when it still exists (covers in-flight uploads and partial migration); otherwise the app streams the object from S3 - Cache:
.thumbs(WebP miniatures) and.cache(originals localized for sharp/zip) live insideuploads/and are pruned hourly (STORAGE_CACHE_MAX_AGE_HOURS) - Never publish the S3 API port: only
127.0.0.1on the host, file access stays behind app auth/rate limits
4. API Patterns
- Admin routes:
requireAdminmiddleware (checksX-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/offsetquery 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
fetchwithX-Admin-TokenfromlocalStorage - 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 mountedapp: builds from Dockerfile, exposes 3003/3443, mounts uploadstailscale: host network, NET_ADMIN, runsstart-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.<tailnet>.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
- Add route in
server.js(group with related routes) - Use
requireAdminfor admin,apiLimiter/fileLimiterfor public - Validate input with helper functions
- Use parameterized queries, transactions if multi-table
- Call
logAudit(req, 'action.name', { ... })for mutations - Return JSON, handle errors with appropriate status codes
Add a database column/table
- Update
db/init.sql(CREATE TABLE / ALTER TABLE) - Update
db/migration.sql(idempotent ALTERs) - Update
server.jsqueries that SELECT/INSERT the table - Test:
docker compose down && docker compose up -d --build
Add a frontend page
- Create
public/newpage.html(copy structure from existing) - Link in
public/admin.htmlnavigation if admin page - Use
admin.jsutilities:api(),requireAuth(),formatDate(), etc. - No build step — just refresh browser
Modify file upload rules
- Edit
BLOCKED_EXT,ALLOWED_IMAGE_EXT,ADMIN_ALLOWED_EXTconstants - Update Multer
fileFilterfunctions - Keep
MAX_TOTAL_UPLOAD_BYTESand per-file limit in sync
Migrate files to S3 / switch storage driver
docker compose up -d s3docker compose exec -T app node scripts/migrate-to-s3.js --dry-runthen without the flag (idempotent, size-checked, keeps local files)docker compose exec -T app node scripts/migrate-to-s3.js --verify-only- Set
STORAGE_DRIVER=s3in.env,docker compose up -d app - After verification:
docker compose exec -T app node scripts/migrate-to-s3.js --delete-local
- Rollback:
STORAGE_DRIVER=local+docker compose up -d app - Do not run
--delete-localbefore the app serves reads from S3 and the verification passes
Testing & Verification
No automated test suite exists. Verify manually:
# 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-<date>.tar.gz
# Storage checks
docker compose up -d s3
docker compose exec -T app node scripts/migrate-to-s3.js --dry-run
docker compose exec -T app node scripts/migrate-to-s3.js --verify-only
Security Checklist (before any change)
- No SQL interpolation — only
$1,$2... - Upload path validation via
isSafeUploadPath/safeUnlink - File I/O через
storage.*, ключи объектов не выходят за пределы бакета/uploads/ - 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
corsmiddleware)
File Map (key files)
| File | Purpose |
|---|---|
server.js |
Entire backend (Express, routes, DB, uploads, backup) |
storage.js |
Storage abstraction: local and s3 drivers, key normalization, cache/thumb helpers |
worker.js |
Background AI auto-check worker for entry messages + photo enhance worker |
db/init.sql |
Initial schema (runs on fresh DB) |
db/migration.sql |
Idempotent migrations for existing DBs |
docker-compose.yml |
Service definitions (app, db, s3, tailscale) |
docker-compose.minio.yml |
Override: S3 service backed by MinIO instead of SeaweedFS |
Dockerfile |
App image build |
public/*.html |
Frontend pages |
public/admin.js |
Shared frontend logic |
scripts/backup.sh |
Host-level backup script (DB dump + storage export) |
scripts/restore.sh |
Host-level restore script (DB dump + storage import) |
scripts/storage-sync.js |
Export/import all storage objects (used by backup/restore) |
scripts/migrate-to-s3.js |
One-off/idempotent migration uploads/ -> S3 bucket |
scripts/deploy.sh |
Deploy script (pull master, build image with commit version, restart app) |
start-tailscale.sh |
Tailscale container entrypoint |
.env.example |
Env var template |
Do Not
- ❌ Add dependencies without updating
package.jsonand rebuilding - ❌ Write files outside
uploads/orcerts/ - ❌ Touch
uploads/withfs.*in request/worker code — usestorage.*(files may live only in S3) - ❌ Run
migrate-to-s3.js --delete-localbefore verification and cutover - ❌ Expose the S3 API port publicly (only
127.0.0.1in compose) - ❌ Commit
.env,certs/,uploads/,backups/,node_modules/ - ❌ Expose DB port (5432) outside docker network
- ❌ Use
eval,Functionconstructor, or dynamic code execution - ❌ Add comments to code (this file excepted)
Quick Commands
# Full rebuild
docker compose down && docker compose up -d --build
# Обновление на сервере (pull master + сборка образа с версией коммита + перезапуск app)
./scripts/deploy.sh
# App logs
docker compose logs -f app
# DB shell
docker compose exec db psql -U app -d whereldo
# S3 storage status and migration verification
docker compose up -d s3
docker compose exec -T app node scripts/migrate-to-s3.js --verify-only
docker compose exec -T app node scripts/migrate-to-s3.js --delete-local
# 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