Let the same choice open notes and drafts
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:
118
db/migrations/20260910120000_abos_gelten_auch_fuer_entwuerfe.sql
Normal file
118
db/migrations/20260910120000_abos_gelten_auch_fuer_entwuerfe.sql
Normal file
@@ -0,0 +1,118 @@
|
||||
-- Die Auswahl der Kolleg:innen gilt jetzt für Notizen **und** Entwürfe.
|
||||
--
|
||||
-- ═══ 1. Der Name stimmt nicht mehr ═══════════════════════════════
|
||||
--
|
||||
-- `note_subscriptions` hiess nach dem, wofür die Auswahl gestern allein
|
||||
-- galt. Sie steuert ab jetzt zwei Listen; ein Name, der nur eine davon
|
||||
-- nennt, führt beim nächsten Lesen in die Irre.
|
||||
|
||||
alter table note_subscriptions rename to colleague_subscriptions;
|
||||
alter index idx_note_subscriptions_user rename to idx_colleague_subscriptions_user;
|
||||
alter table colleague_subscriptions
|
||||
rename constraint chk_note_abos_nicht_selbst to chk_kollegen_abos_nicht_selbst;
|
||||
|
||||
drop policy if exists "note_subscriptions_owner" on colleague_subscriptions;
|
||||
drop policy if exists "colleague_subscriptions_owner" on colleague_subscriptions;
|
||||
create policy "colleague_subscriptions_owner" on colleague_subscriptions
|
||||
for all
|
||||
using (user_id = app_current_user_id() and is_hr_user())
|
||||
with check (user_id = app_current_user_id() and is_hr_user());
|
||||
|
||||
comment on table colleague_subscriptions is
|
||||
'Hinzugewählte Kolleg:innen je Person. Eine Zeile holt deren Notizen und Entwürfe in die Ansicht von user_id. Ohne Zeile sieht man nur die eigenen.';
|
||||
|
||||
-- ═══ 2. Entwürfe: lesen weiter, schreiben eng ════════════════════
|
||||
--
|
||||
-- Bisher stand über hire_drafts eine einzige Regel `for all`. Sie wird in
|
||||
-- vier zerlegt, weil Lesen und Schreiben ab jetzt verschieden weit reichen:
|
||||
--
|
||||
-- lesen — die eigenen und die der hinzugewählten Kolleg:innen
|
||||
-- anlegen — nur auf den eigenen Namen
|
||||
-- ändern — nur die eigenen
|
||||
-- löschen — nur die eigenen
|
||||
--
|
||||
-- Warum das Schreiben eng bleibt: ein Entwurf ist unfertige Arbeit. Zwei
|
||||
-- Personen, die abwechselnd in derselben Zeile schreiben, überschreiben
|
||||
-- einander lautlos — es gibt keine Sperre und keine Historie, die das
|
||||
-- auffangen könnte. Wer einen fremden Entwurf übernehmen soll, braucht dafür
|
||||
-- einen eigenen Vorgang, keine stillschweigend geöffnete Regel.
|
||||
--
|
||||
-- ═══ Was das erzwingt ═══
|
||||
--
|
||||
-- Solange fremde Entwürfe unsichtbar waren, konnte niemand versuchen, einen
|
||||
-- zu speichern. Jetzt schon — und ein UPDATE, das die Regel abweist, trifft
|
||||
-- keine Zeile und meldet trotzdem keinen Fehler. Die Anwendung muss die Zahl
|
||||
-- der geänderten Zeilen prüfen (actions/hireDrafts.ts), sonst sähe die
|
||||
-- Person „gespeichert", und nichts wäre gespeichert.
|
||||
|
||||
drop policy if exists "hire_drafts_owner" on hire_drafts;
|
||||
|
||||
drop policy if exists "hire_drafts_select" on hire_drafts;
|
||||
create policy "hire_drafts_select" on hire_drafts
|
||||
for select
|
||||
using (
|
||||
is_hr_user()
|
||||
and (
|
||||
created_by = app_current_user_id()
|
||||
or exists (
|
||||
select 1 from colleague_subscriptions s
|
||||
where s.user_id = app_current_user_id()
|
||||
and s.author_user_id = hire_drafts.created_by
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
drop policy if exists "hire_drafts_insert" on hire_drafts;
|
||||
create policy "hire_drafts_insert" on hire_drafts
|
||||
for insert
|
||||
with check (created_by = app_current_user_id() and is_hr_user());
|
||||
|
||||
drop policy if exists "hire_drafts_update" on hire_drafts;
|
||||
create policy "hire_drafts_update" on hire_drafts
|
||||
for update
|
||||
using (created_by = app_current_user_id() and is_hr_user())
|
||||
with check (created_by = app_current_user_id() and is_hr_user());
|
||||
|
||||
drop policy if exists "hire_drafts_delete" on hire_drafts;
|
||||
create policy "hire_drafts_delete" on hire_drafts
|
||||
for delete
|
||||
using (created_by = app_current_user_id() and is_hr_user());
|
||||
|
||||
-- ═══ Gegenprobe ═══════════════════════════════════════════════════
|
||||
do $$
|
||||
declare
|
||||
anzahl int;
|
||||
begin
|
||||
if exists (select 1 from pg_tables where tablename = 'note_subscriptions') then
|
||||
raise exception 'note_subscriptions steht noch — die Umbenennung hat nicht gegriffen.';
|
||||
end if;
|
||||
|
||||
if not exists (
|
||||
select 1 from pg_policies
|
||||
where tablename = 'colleague_subscriptions' and policyname = 'colleague_subscriptions_owner'
|
||||
) then
|
||||
raise exception 'Die Eigentuemerregel auf colleague_subscriptions fehlt.';
|
||||
end if;
|
||||
|
||||
-- Die alte Sammelregel darf nicht übrigbleiben: sie erlaubte `for all`
|
||||
-- und machte die vier neuen wirkungslos, weil Policies sich addieren.
|
||||
if exists (select 1 from pg_policies where tablename = 'hire_drafts' and policyname = 'hire_drafts_owner') then
|
||||
raise exception 'hire_drafts_owner steht noch — die alte Sammelregel wuerde die neuen aushebeln.';
|
||||
end if;
|
||||
|
||||
select count(*) into anzahl from pg_policies where tablename = 'hire_drafts';
|
||||
if anzahl <> 4 then
|
||||
raise exception 'hire_drafts hat % Regeln, erwartet werden 4 (select, insert, update, delete).', anzahl;
|
||||
end if;
|
||||
|
||||
-- Keine der drei Schreibregeln darf die Abos kennen. Stünde dort dieselbe
|
||||
-- Bedingung wie beim Lesen, liesse sich ein fremder Entwurf überschreiben.
|
||||
if exists (
|
||||
select 1 from pg_policies
|
||||
where tablename = 'hire_drafts'
|
||||
and cmd in ('INSERT', 'UPDATE', 'DELETE')
|
||||
and coalesce(qual, '') || coalesce(with_check, '') like '%colleague_subscriptions%'
|
||||
) then
|
||||
raise exception 'Eine Schreibregel auf hire_drafts kennt die Abos — Schreiben muss eigentuemergebunden bleiben.';
|
||||
end if;
|
||||
end $$;
|
||||
Reference in New Issue
Block a user