PRD.md — Product Requirements Document: WhatIDo
Version: 1.0
Status: Active
Last Updated: 2026-09-09
1. Product Summary
WhatIDo is a self-hosted accounting system for an educational center (youth club, coding school, art studio). It tracks attendance, student project works, group photo chronicles, and publishes public showcase pages via share links. Designed for zero-public-IP deployment using Tailscale Funnel.
Target Users:
- Administrators/Teachers — manage groups, students, entries, files, settings
- Parents/Students — view public showcase pages (read-only, no auth)
Core Value: Simple, secure, zero-infrastructure publishing. Runs on any Linux box with Docker.
2. Functional Requirements
2.1 Groups Management
| ID |
Requirement |
Priority |
| GRP-1 |
CRUD groups: name, schedule (day of week, start/end time), branch assignment |
Must |
| GRP-2 |
Group cover photo (auto-set from latest group photo or manual) |
Must |
| GRP-3 |
Photo chronicle per group: upload, caption, date taken, pagination |
Must |
| GRP-4 |
Active groups endpoint (filters by current day/time in Europe/Moscow) |
Must |
| GRP-5 |
Branches (locations): CRUD with address, phone; groups link to branch |
Should |
2.2 Students Management
| ID |
Requirement |
Priority |
| STU-1 |
CRUD students: name, group assignment |
Must |
| STU-2 |
Batch assign students to group |
Should |
| STU-3 |
Unique name constraint per student |
Must |
2.3 Journal Entries (Attendance + Project Works)
| ID |
Requirement |
Priority |
| ENT-1 |
Create entry: student name (free text), group, description, photo, multiple files |
Must |
| ENT-2 |
List entries with filters: group, date range, student name, search (name/description), deleted flag |
Must |
| ENT-3 |
Update entry: description, photo, files |
Must |
| ENT-4 |
Soft delete / restore (deleted_at timestamp) |
Must |
| ENT-5 |
Trash view: list deleted entries, restore, permanent delete |
Must |
| ENT-6 |
Pagination (limit/offset) + total count |
Must |
| ENT-7 |
Anti-spam: min interval between entries per student (configurable, default 30 min) |
Must |
| ENT-8 |
Files attached to entry: upload (max 10 files, per-file and total size limits from UPLOAD_FILE_LIMIT_MB/UPLOAD_TOTAL_LIMIT_MB), download by token |
Must |
2.4 Files Management (Centralized)
| ID |
Requirement |
Priority |
| FIL-1 |
List all files with filters: search, student, group, date range |
Must |
| FIL-2 |
Detached files tab: files with entry_id = NULL |
Must |
| FIL-3 |
Detach file from entry (sets detached_at) |
Must |
| FIL-4 |
Delete file (removes from disk + DB) |
Must |
| FIL-5 |
Public file access by token (image inline, others download) |
Must |
| FIL-6 |
File size display in list |
Should |
2.5 Share Links (Public Showcase Pages)
| ID |
Requirement |
Priority |
| SHR-1 |
Create share link: name, optional group, student, date range, anonymize names, expiry (default 7 days), optional password |
Must |
| SHR-2 |
List/Edit/Delete share links (admin) |
Must |
| SHR-3 |
Public page (/s/:token): shows filtered entries + group photos |
Must |
| SHR-4 |
Password protection on share link (bcrypt) |
Must |
| SHR-5 |
Expiry enforcement (410 Gone after expires_at) |
Must |
| SHR-6 |
Anonymize student names on public page (Student 1, Student 2...) |
Should |
| SHR-7 |
Group photos on public page (latest 12) |
Should |
| SHR-8 |
File download from share page (validates link + password + filters) |
Must |
2.6 Dashboard & Statistics
| ID |
Requirement |
Priority |
| DSH-1 |
Stats cards: total entries, trash count, groups, unique students |
Must |
| DSH-2 |
Active groups right now (schedule match) |
Must |
| DSH-3 |
Activity chart: entries per day (last 14 days) |
Should |
| DSH-4 |
Top students by entry count |
Should |
| DSH-5 |
Recent entries list (last 10) |
Should |
2.7 Settings
| ID |
Requirement |
Priority |
| SET-1 |
Footer left/right text (displayed on public pages) |
Must |
| SET-2 |
Anti-spam interval (minutes) |
Must |
| SET-3 |
Admin-only access |
Must |
2.8 Backup & Restore
| ID |
Requirement |
Priority |
| BAK-1 |
Download full backup: tar.gz with data.json (all tables) + uploads/ |
Must |
| BAK-2 |
Restore from backup file: validates format, replaces all data, resets sequences |
Must |
| BAK-3 |
Host-level scripts: backup.sh, restore.sh |
Should |
| BAK-4 |
Audit log entry for backup download/restore |
Must |
2.9 Audit Log
| ID |
Requirement |
Priority |
| AUD-1 |
Log all mutating actions: action name, target JSON, IP, timestamp |
Must |
| AUD-2 |
Admin view: paginated list (default 100, max 1000) |
Must |
2.10 Security & Infrastructure
| ID |
Requirement |
Priority |
| SEC-1 |
Admin auth via X-Admin-Token header (env ADMIN_PASSWORD, no default) |
Must |
| SEC-2 |
Rate limiting: entries 10/15min, files/share 300/15min |
Must |
| SEC-3 |
Upload validation: block dangerous extensions, MIME check, size limits |
Must |
| SEC-4 |
Path traversal protection on file delete/serve |
Must |
| SEC-5 |
Helmet headers (X-Frame-Options, nosniff, HSTS, Referrer-Policy) |
Must |
| SEC-6 |
No CORS (cross-origin blocked) |
Must |
| SEC-7 |
DB port not exposed publicly |
Must |
| SEC-8 |
TLS termination by Tailscale (Let's Encrypt), app uses self-signed cert internally |
Must |
| SEC-9 |
Tailscale Funnel publication (no public IP, no port forward) |
Must |
3. Non-Functional Requirements
| Category |
Requirement |
| Performance |
API responses < 500ms for typical queries; pagination for large lists |
| Reliability |
DB healthcheck; app restarts on crash; uploads persisted on host |
| Scalability |
Single-instance design; PostgreSQL connection pooling via pg.Pool |
| Maintainability |
Single server.js file; vanilla frontend; no build step |
| Portability |
Docker Compose; runs on any Linux/ARM64/AMD64 with Docker |
| Backup/Recovery |
Full restore < 5 min for typical dataset (< 1 GB) |
| Security |
No secrets in image; env vars only; regular dependency updates |
4. Data Model
groups
id PK, name UK, created_at, day_of_week (0-6), time_start, time_end, branch_id FK, cover_path
students
id PK, name UK, group_id FK, created_at
entries
id PK, student_name, group_id FK, description, photo_path, deleted_at, created_at
project_files
id PK, entry_id FK (nullable), token UK, path, name, created_at, detached_at
group_photos
id PK, group_id FK, photo_path, caption, taken_at, created_at
share_links
id PK, token UK, name, group_id FK, student_name, date_from, date_to,
anonymize_names, expires_at, access_password_hash, created_at
settings
key PK, value
audit_log
id PK, action, target JSONB, ip, created_at
branches
id PK, name UK, address, phone, created_at
5. API Surface (Key Endpoints)
| Method |
Path |
Auth |
Description |
| GET |
/api/entries |
Admin |
List entries (filters, pagination) |
| POST |
/api/entries |
Admin |
Create entry (photo + files) |
| PUT |
/api/entries/:id |
Admin |
Update entry |
| DELETE |
/api/entries/:id |
Admin |
Soft delete |
| POST |
/api/entries/:id/restore |
Admin |
Restore from trash |
| GET |
/api/files |
Admin |
All files (filters) |
| GET |
/api/files/detached |
Admin |
Detached files |
| POST |
/api/files/:id/detach |
Admin |
Detach file |
| GET |
/api/files/:token |
Public |
Download file by token |
| GET |
/api/groups |
Public |
List groups |
| POST/PUT/DELETE |
/api/groups |
Admin |
CRUD groups |
| GET/POST |
/api/groups/:id/photos |
Admin |
Group photo chronicle |
| GET |
/api/share/:token |
Public |
Share page data |
| GET |
/api/links |
Admin |
List share links |
| POST/PUT/DELETE |
/api/links |
Admin |
CRUD share links |
| GET |
/api/backup |
Admin |
Build & download backup (compat) |
| POST |
/api/backup |
Admin |
Build backup, returns download URL |
| GET |
/api/backup/:token |
Token |
Download built backup (resumable) |
| POST |
/api/restore |
Admin |
Upload & restore backup |
| GET |
/api/dashboard |
Admin |
Dashboard data |
| GET |
/api/stats |
Admin |
Stats cards |
| GET |
/api/audit |
Admin |
Audit log |
6. User Flows
6.1 Teacher Creates Attendance Entry
- Opens
/journal.html
- Fills form: student name (typeahead from existing), group, description
- Adds photo (optional) + project files (optional)
- Submits → entry appears in list, files accessible by token
6.2 Admin Publishes Showcase for Parents
- Opens
/links.html
- Creates share link: selects group, date range, sets password
- Copies link
https://whatido.tailnet.ts.net/s/abc123
- Sends to parents → they open, enter password, view entries + photos
6.3 Admin Restores from Backup
- Opens
/settings.html → Backups tab
- Uploads
.tar.gz backup file
- Confirms → all data replaced, sequences reset, orphans cleaned
7. Deployment Architecture
8. Configuration
| Variable |
Required |
Description |
ADMIN_PASSWORD |
Yes |
Admin token value (no default, server refuses start) |
DB_PASSWORD |
Yes |
Postgres app user password |
9. Release Criteria
10. Future Considerations (Not in Scope v1)
- Multi-user auth (teachers with own logins)
- Email/push notifications
- Mobile app / PWA
- Rich text editor for descriptions
- Bulk import students (CSV)
- Webhooks for external integrations
- Automated scheduled backups to S3/remote
- Role-based access (read-only vs admin)
11. Acceptance Test Scenarios
| Scenario |
Steps |
Expected |
| Fresh deploy |
cp .env.example .env → edit → docker compose up -d --build |
App healthy, DB migrated, HTTPS on 3443 |
| Create entry |
POST /api/entries with photo + 2 files |
Entry created, files downloadable by token |
| Share link |
Create link with password → open /s/token → enter password |
Entries filtered, photos shown, files download |
| Backup/restore |
Download backup → delete entry → restore → verify entry back |
Full state restored, sequences correct |
| Tailscale publish |
tailscale up → enable Funnel → tailscale funnel |
Public URL accessible via HTTPS |
| Upload rejection |
POST .html file → 400 error |
Dangerous extensions blocked |
| Rate limit |
11 rapid POST /api/entries → 429 on 11th |
Entry limiter enforced |