5de974edaf
ESLint
- Pinned eslint@^8 + eslint-config-next@^14 to match Next 14's `next lint`.
ESLint 9's flat-config breaks `next lint` for legacy projects.
- .eslintrc.json extends next/core-web-vitals; ignores tests/, scripts/,
prisma/data/, .next/, node_modules.
- 7 pre-existing warnings surfaced (exhaustive-deps + alt-text + img tag
in user-written components). Left as warnings — pre-existing, not
breaking. CI runs lint; warnings don't fail the job.
Server action tests (tests/actions-admin.test.ts, tests/actions-auth.test.ts)
- Vitest setup file (tests/helpers/setup-actions.ts) sets DATABASE_URL
to a per-process temp SQLite DB and runs `prisma db push` BEFORE
lib/prisma instantiates its global PrismaClient. Tests then call the
real server actions against an isolated DB.
- vi.mock + vi.hoisted to mock @/lib/auth.getCurrentUser, next/headers
cookies+headers, next/navigation redirect, next/cache revalidatePath.
- Coverage:
- admin: setUserAdmin (Forbidden, promote, last-admin demote refused,
demote-with-other-admin allowed), deleteUser (last-admin guard,
self-delete refused, cascading delete to exercises + workouts),
adminResetPassword (hash-and-revoke, short-password rejected).
- auth flows: signupAction (closed by default, opens-and-creates,
mismatched confirm rejected, short pwd rejected, malformed email
rejected, no email-enumeration leak), changePasswordAction
(rotate-and-revoke-others, wrong current pwd rejected, no-op pwd
rejected), deleteMyAccountAction (phrase required, password required,
last-admin refused, success cascades + clears cookie + redirects).
- Total suite: 34 tests, ~2s.
Export my data (/api/me/export + Settings -> Export my data)
- Downloads a JSON dump of every workout/set/exercise/program tied to
the user. Excludes password hash and sessions. Filename includes
email + date. content-disposition: attachment, no-store cache.
- Exported shape matches the underlying tables 1:1 so a future "import
my data" flow can round-trip without ambiguity.
Enriched /api/health
- Now reports: database.connected, database.journalMode (and walEnabled
shortcut), users count, instanceSettings.signupsOpen, library.available
+ sizeBytes. Surfaces a `warnings` array if journal_mode != 'wal' but
doesn't fail the check (app still works without WAL — just unsafe for
online backups). Returns 503 only on hard DB failure.
CHANGELOG.md
- Single Unreleased section documenting everything that will ship as
v1.0.0:1 once the maintainer drops a fresh /data snapshot. Added /
Changed / Removed / Compat-notes sections.
5.3 KiB
5.3 KiB
Changelog
All notable changes to Proof of Work and its StartOS package wrapper.
The format roughly follows Keep a Changelog;
versions track the StartOS package release rev (upstream:rev per ExVer).
[Unreleased]
This is everything in master since the last published .s9pk. Will
ship as v1.0.0:1 once the maintainer drops a fresh /data snapshot
into start9/0.4/seed/data/app.db and runs make x86 && make install.
Added
- Multi-user support. Every install starts with one admin
(
admin@local) and sign-ups closed. The admin opens sign-ups via Settings -> Instance Settings or via the new StartOS package action "Set new signups". New users start with no admin privileges and the full curated exercise library auto-seeded. - Curated exercise library at
proof-of-work/prisma/exercises.seed.json. Used byprisma/seed.tsfor fresh installs and byensureExerciseLibrary.cjs(run fromdocker_entrypoint.shon every boot) so library updates flow to existing installs additively, never overwriting users' own custom exercises.npm run sync-libraryregenerates the JSON from the live snapshot.
- In-app password change at Settings -> Change password. Verifies current password, requires 8+ char new password, auto-revokes every other session for the user.
- Admin user management at
/main/admin/users. List / promote / demote / reset-password / delete with last-admin guard and self-delete guard. Admin-initiated password reset force-revokes all the target's sessions. - Self-serve account deletion at Settings -> Danger Zone. Requires current password AND typing the literal phrase "delete my account". Refused for the last admin. Cascades through Prisma onDelete.
- Last-login tracking (
User.lastLoginAt). Stamped on every session creation, displayed as a relative-age cell in the admin Users table. - Export my data at Settings -> Export my data. Downloads a JSON with every workout, set, exercise, program tied to the user. Password hash and sessions excluded.
- Rate limits on
/auth/login(10/IP/15min) and/auth/signup(5/IP/15min). In-process sliding window, no deps. - Security headers: Content-Security-Policy (with frame-ancestors 'none', form-action 'self', object-src 'none'), Strict-Transport-Security, Referrer-Policy, Permissions-Policy.
- SQLite WAL mode +
synchronous=NORMALenabled in entrypoint. Keeps readers from blocking on a concurrent backup-time writer. - StartOS Package Action
change-admin-credentialsnow keys onWHERE isAdmin = 1 ORDER BY createdAt ASC LIMIT 1(previously oldest user regardless of role). - StartOS Package Action
toggle-signups: same setter as the in-app admin toggle, accessible from the StartOS UI without an admin login. Asserts read-back matches written value. - Test suite (Vitest, 34 tests, ~2s): rate limit, hashing, curated-library multi-user idempotency, admin actions including last-admin guard, signup gate + email-enumeration leak check, password-change, account deletion.
- GitHub Actions CI: app job runs prisma validate + prisma generate
- tsc + lint + tests; startos job runs the package's
npm run check(tsc --noEmit). Both on push and PR to master/main.
- tsc + lint + tests; startos job runs the package's
- ESLint config (
.eslintrc.jsonextendingnext/core-web-vitals). Wired into CI. - Enriched
/api/healthreports DB connection, journal mode (WAL status), library JSON availability, instance signup state, and user count. 503 if DB is unreachable; warnings field for non-fatal issues (e.g. journal_mode != wal).
Changed
- Rebranded
workout-log->proof-of-workend-to-end. Folder, npm package name, StartOS package id. StartOS treats this as a brand new service; cutover from the legacy package is via baked seed in v1.0.0:1, not in-place upgrade. - Session tokens now 256-bit
crypto.randomByteshex. Were derived fromMath.random() + Date.now()— predictable enough that a determined attacker could enumerate other users' tokens. Existing sessions invalidate on upgrade by design — token format/length changed, old tokens won't validate. - Version graph reset to
1.0.0:1(was on the legacyworkout-logv0.1.0:18 / :19 / :20 line).
Removed
- Legacy
start9/0.3.5/package (StartOS 0.3.5 wrapper, 65MB image artifacts, no longer the deploy target). start9-example-packaging/template from another project.- Workout-planner standalone Dockerfile + docker-compose.yml (replaced by the StartOS package's own Dockerfile).
- Various planning docs replaced by
start9/0.4/DEPLOY_040.mdand the rootREADME.md.
Compat notes for cutover
The boot-time entrypoint runs idempotent ALTERs that:
- Add
User.isAdminand auto-promote the oldest user to admin if no admin exists (preserves admin functionality across the cutover). - Add
User.lastLoginAt. - Create the
InstanceSettingstable + singleton row (signupsOpen=0). - Switch SQLite to WAL mode (persists in DB header thereafter).
So a snapshot pulled off the legacy workout-log host comes up as a
working multi-user proof-of-work install with no manual SQL.
v0.1.0:17 and earlier (legacy workout-log package)
Out of scope for this changelog. The legacy package's history lives in the git log; nothing in this repo references it after v1.0.0:1 ships.