chore(release): v1.13.0 — per-user custom mood tags + catalogue hide

Let a user shape their own mood-tag set on top of the global catalogue:
create custom tags (label encrypted at rest, icon from a fixed allow-list),
and hide built-in tags per user. Custom tags live in `mood_tags` with a
non-null `user_id` and a `custom:<uuid>` key; `GET /api/mood/tags` returns
the effective per-user set (catalogue − hidden + own customs) with new
`custom` + `label` fields, and `?include=hidden` surfaces hidden catalogue
tags for the management screen. The write path resolves a `custom:` key only
against the caller's own rows — a foreign custom key is dropped, never linked.
Delete soft-deactivates by default (history intact); `?purge=true` removes
the row and its links. BINARY-only for v1.

New: POST/PATCH/DELETE /api/mood/tags/custom[/:key], PUT /api/mood/tags/:key/hidden,
migration 0127 (user_id + label_encrypted columns, mood_tag_hidden table,
`custom` category). Integration setup snapshots + restores the seeded catalogue
because the new mood_tags→users FK makes TRUNCATE users CASCADE wipe it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-06-05 20:50:05 +02:00
1 parent 09bdd49791
commit f1329b24e1
21 files changed
+934 -121

No files matched your search

+26
View File
@@ -26,6 +26,11 @@ export function getPrismaClient(): PrismaClient {
return prisma;
}
/** Lazily-captured global mood-tag catalogue (NULL user_id rows). */
let catalogueSnapshot:
| Awaited<ReturnType<PrismaClient["moodTag"]["findMany"]>>
| null = null;
/**
* Truncate every user-facing table in dependency-safe order using
* `TRUNCATE … RESTART IDENTITY CASCADE`. Call from beforeEach() to make
@@ -74,8 +79,29 @@ export async function truncateAllTables(client: PrismaClient): Promise<void> {
"workouts",
];
// v1.13.0 — the mood-tag catalogue (`mood_tags` rows with NULL user_id) is
// global reference data seeded by migrations and is intentionally NOT in the
// truncate list, so tests can rely on it. But `mood_tags.user_id` now FKs to
// `users`, so `TRUNCATE users CASCADE` cascades into `mood_tags` and wipes
// the catalogue too (TRUNCATE CASCADE truncates dependent tables wholesale,
// regardless of the ON DELETE action). Snapshot the catalogue once (before
// the first truncate clears it) and restore it after each truncate so the
// migration-seeded catalogue persists across tests as it did before the FK.
if (catalogueSnapshot === null) {
catalogueSnapshot = await client.moodTag.findMany({
where: { userId: null },
});
}
const quoted = tables.map((t) => `"${t}"`).join(", ");
await client.$executeRawUnsafe(
`TRUNCATE TABLE ${quoted} RESTART IDENTITY CASCADE;`,
);
if (catalogueSnapshot.length > 0) {
await client.moodTag.createMany({
data: catalogueSnapshot,
skipDuplicates: true,
});
}
}