Let the same choice open notes and drafts
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m47s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m10s

The picker in the bell now governs both lists, so note_subscriptions is
renamed to colleague_subscriptions -- a name that only mentions notes would
mislead the next reader.

Reading and writing a draft now reach differently far. hire_drafts_owner
(for all) is split into four policies: select lets in your own drafts and
those of the people you added, while insert/update/delete stay with the
owner. A draft is unfinished work with no lock and no history; two people
writing into the same row would overwrite each other silently.

That split forces a change in the actions: a policy does not reject a write,
it lets it hit no rows. saveHireDraft and deleteHireDraft now read the row
count instead of reporting success over a row that never changed.

The card shows a foreign draft with its author and without Fortsetzen or
Loeschen -- offering a button that reliably ends in a database error is a
promise without cover.

check-schema-types.mjs learns `alter table ... rename to`; without it the
drift check reports one rename as two errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 16:05:40 +02:00
parent e8e675fd07
commit eeaf210e78
17 changed files with 650 additions and 54 deletions

View File

@@ -6,7 +6,12 @@ import { withUser } from "@/lib/db";
import type { ActionResult } from "@/lib/db/rpc";
/**
* Notizen einer Kollegin oder eines Kollegen hinzuwählen oder abwählen.
* Eine Kollegin oder einen Kollegen hinzuwählen oder abwählen.
*
* Die Auswahl steuert zwei Listen: die Notizen in der Glocke und die
* Entwürfe auf der Übersicht. Der Name der Funktion nennt nur die erste,
* weil die Einstellung in der Glocke sitzt — was sie bewirkt, steht an der
* Einstellung selbst.
*
* Kein Aufruf einer SQL-Funktion und kein Protokolleintrag, anders als bei
* allem, was Personaldaten ändert: das hier ist eine persönliche
@@ -15,7 +20,7 @@ import type { ActionResult } from "@/lib/db/rpc";
* Dasselbe Muster wie bei gespeicherten Auswertungen und Entwürfen
* (actions/reports.ts, actions/hireDrafts.ts).
*
* Abgesichert ist es trotzdem: die Regel `note_subscriptions_owner` lässt nur Zeilen
* Abgesichert ist es trotzdem: die Regel `colleague_subscriptions_owner` lässt nur Zeilen
* zu, deren `user_id` die angemeldete Person ist. Eine fremde Einstellung
* liesse sich auch mit erfundenen Werten nicht schreiben.
*/
@@ -38,13 +43,13 @@ export async function setNotizSichtbarkeit(payload: {
// `on conflict do nothing`: zweimal dasselbe Hinzuwählen ist kein
// Fehler, sondern derselbe Wunsch — etwa wenn zwei Reiter offen sind.
await tx
.insertInto("note_subscriptions")
.insertInto("colleague_subscriptions")
.values({ user_id: userId, author_user_id: payload.kollegeId })
.onConflict((oc) => oc.columns(["user_id", "author_user_id"]).doNothing())
.execute();
} else {
await tx
.deleteFrom("note_subscriptions")
.deleteFrom("colleague_subscriptions")
.where("user_id", "=", userId)
.where("author_user_id", "=", payload.kollegeId)
.execute();