Skip to content

Feat/db constraints secure cookies - #1

Merged
BloomyInDev merged 3 commits into
masterfrom
feat/db-constraints-secure-cookies
Aug 27, 2026
Merged

Feat/db constraints secure cookies#1
BloomyInDev merged 3 commits into
masterfrom
feat/db-constraints-secure-cookies

Conversation

@BloomyInDev

Copy link
Copy Markdown
Owner

No description provided.

BloomyInDev and others added 3 commits July 30, 2026 17:04
Handlers were constructor closures over *sql.DB, and main.go listed every
route by hand. Turn them into methods on a Handlers struct holding the
shared dependencies, and let each resource file own its own sub-router.
Router() is now the only place that knows URL prefixes, so a resource file
never repeats its own mount path.

Two routing details worth calling out:

- Bind /static to GET and HEAD explicitly. http.FileServer ignores the
  request method, so a single catch-all Handle answered POST, DELETE and
  TRACE with the file body.
- Canonicalise paths with CleanPath and StripSlashes, so "/clients//" and
  "/clients/1/edit/" reach their handler instead of the catch-all.

Route parity with the previous net/http mux was verified route by route:
same paths, same status codes, same Location headers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two data-integrity problems and one transport one, plus the migration
machinery the first of them needed.

Foreign keys were never actually on. The pragma was issued as a statement
after connecting, which configures only the connection that happened to
serve it, and database/sql pools connections freely; every other one ran
with enforcement off. Deleting a client therefore left its tasks behind,
pointing at a row that no longer existed, where they rendered with a blank
name and still counted toward every hours total. The pragmas move into the
DSN, so no connection can miss them.

Turning enforcement on changes what a delete means. Tasks are the records
this app exists to keep, so their foreign keys stay RESTRICT and the
refusal surfaces as db.ErrInUse, which the handlers report as a 409 rather
than a 500 full of SQL. The client/task-type join table is bookkeeping
instead, so it cascades; that needed a table rebuild, since SQLite can't
ALTER a constraint. Periods sit between the two: they only group hours, so
deleting one detaches its tasks in a transaction rather than refusing.

Expressing that rebuild meant replacing the old startup schema block and
its list of ALTERs, which could only ever add columns. Migrations are now
embedded .sql files applied in order and recorded in schema_migrations.
Migration 0001 is the schema as it already stands, written idempotently so
existing databases are recognised as migrated instead of rebuilt.

Session cookies were never marked Secure, so the token travelled in
cleartext over any plain-HTTP hop. It defaults on now, since the app is
normally reached through a TLS-terminating proxy, and a deployment that
never considered it should land on the safe setting; plain-HTTP setups opt
out with TRACKER_SECURE_COOKIES=false, which compose.yml does. Login and
logout share one helper so their attributes can't drift apart.

Environment variables gain a TRACKER_ prefix. This is a breaking change for
existing deployments: DB_PATH becomes TRACKER_DB_PATH and JWT_SECRET
becomes TRACKER_JWT_SECRET. The bundled Dockerfile and compose.yml are
updated; anything overriding them needs the same rename.

WAL stays opt-in behind TRACKER_SQLITE_WAL, since it leaves -wal and -shm
files next to the database. Capping the pool at one connection, which
SQLite's single-writer rule wants anyway, removes the contention that would
otherwise be the reason to reach for it.

Verified against a copy of a live database: 927 tasks and their 1063.5
hours preserved, all 52 assignments carried across the rebuild, both
cascade clauses in place, no foreign key violations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The port was the one setting hardcoded in Config, so the only way to move
the server was to remap it outside the process. It reads TRACKER_PORT now,
alongside the other settings.

An out-of-range or unparseable value falls back to 8080 with a warning
rather than failing to start, matching how the boolean settings already
behave. Zero is rejected with the rest: it would ask the kernel for an
arbitrary free port, which is never what a server someone has to reach was
meant to do.

Config is read once into a package-level variable instead of per command.
Load logs as it falls back, and every command builds its flags at startup,
so a single malformed value used to print its warning five times.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BloomyInDev
BloomyInDev merged commit 3dfcf84 into master Aug 27, 2026
1 of 19 checks passed
@BloomyInDev
BloomyInDev deleted the feat/db-constraints-secure-cookies branch August 27, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant