tags: add edit + delete endpoints, enable FK enforcement

PUT /image/tags/{id} renames a tag globally; DELETE /image/tags/{id}
removes a tag and every photo's reference. Rename returns 200/404/409
(case-insensitive name conflict) / 400 (empty name); delete returns
204/404. New migration adds a UNIQUE COLLATE NOCASE index on
tags.name with a pre-flight pass that collapses existing case-
insensitive duplicates onto the lowest id.

The connection setup now sets PRAGMA foreign_keys = ON. The schema
already declares ON DELETE CASCADE / SET NULL on several tables —
those clauses were documentation-only because SQLite has FK
enforcement off per-connection by default. Audited every
diesel::delete site; each touches either no inbound FKs or has a
matching policy. delete_tag relies on the tagged_photo cascade
instead of doing manual cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron
2026-04-30 20:26:35 -04:00
parent 89b743ba54
commit 44d677528e
4 changed files with 452 additions and 2 deletions

View File

@@ -136,10 +136,19 @@ pub fn connect() -> SqliteConnection {
// rollback-journal durability; we accept the narrow last-fsync
// window for the 210× write throughput).
use diesel::connection::SimpleConnection;
// foreign_keys = ON is per-connection in SQLite (off by default), so
// it has to be set here alongside the other pragmas. Without it
// every `REFERENCES … ON DELETE CASCADE / SET NULL` clause in the
// schema is documentation-only — orphan rows would survive the
// referenced row's deletion. With it, the cascade fires
// automatically and code that previously did manual two-step
// cleanup (delete child rows, then parent) becomes redundant but
// still correct.
conn.batch_execute(
"PRAGMA journal_mode = WAL; \
PRAGMA busy_timeout = 5000; \
PRAGMA synchronous = NORMAL;",
PRAGMA synchronous = NORMAL; \
PRAGMA foreign_keys = ON;",
)
.expect("set sqlite pragmas");
conn