Let a person name their own medication categories

A medication can be filed under a category the person named, next to the
built-in ones (#1041). The labels live in medication_category_labels,
encrypted at rest, capped at 20 per account; a medication filed under one
carries its custom:<uuid> key in the category column.

- GET/POST /api/medications/categories (POST idempotent, 30/min per actor,
  admitted at MANAGE like the cycle symptom vocabulary) and owner-only
  PATCH/DELETE /api/medications/categories/{key}. DELETE moves every
  medication filed under the category to OTHER in the same transaction
  and answers movedCount.
- category is widened to a built-in value or custom:<uuid> on the wire,
  the create and update bodies refuse a key that is not the caller's
  (422 medications.category.unknown), and list, detail, the insights
  medication tiles and the MCP medications resource carry categoryLabel.
  A key with no owned label reads as OTHER and is counted on the event.
- The wizard's class step lists the person's categories under the
  built-in rows with an inline add row; the list page gains a category
  filter (session only) and a manage sheet for rename, hide and delete.
- Backup carries customMedicationCategories (decrypted labels) and the
  restore writes them before the medications; wipe plan, encrypted
  column registry, rotation script, sharing guards and OpenAPI follow.
This commit is contained in:
Marc-André Bombeck committed 2026-10-03 21:38:03 +02:00
1 parent 350c646463
commit 4cb8118802
55 files changed
+3201 -187

No files matched your search

+9
View File
@@ -373,6 +373,15 @@ async function main() {
prisma.cycleSymptom,
),
);
// v1.40 (#1041) — custom medication categories. A Bytes column, unlike the
// three String label columns above.
results.push(
await rotateBytesColumn(
"MedicationCategoryLabel",
"labelEncrypted",
prisma.medicationCategoryLabel,
),
);
// ───── NotificationChannel."config" (encrypted JSON) ─────
// Channel config (Telegram chat id, ntfy topic, etc.). Skipping these on