Adds an end-to-end project-optional path for Schriftsatz drafts:
- Migration 120 drops NOT NULL on paliad.submission_drafts.project_id
and rewrites the four RLS policies to gate purely on user_id when
project_id IS NULL, otherwise on paliad.can_see_project. Down
refuses to run if project-less rows exist (safer than silent
data corruption).
- SubmissionDraft.ProjectID becomes *uuid.UUID end-to-end. Service
layer skips project/parties/deadline lookups when nil and exposes
DraftPatch.ProjectID for the "Projekt zuweisen" affordance.
ListAllForUser LEFT JOINs paliad.projects so project-less drafts
surface in the global index next to project-scoped ones.
- New HTTP surface:
GET /submissions/new (picker page)
GET /submissions/draft/{draft_id} (editor for any draft)
GET /api/submissions/catalog (catalog without project)
POST /api/submission-drafts (project-less or attached)
GET/PATCH/DELETE /api/submission-drafts/{draft_id}
POST /api/submission-drafts/{draft_id}/export
Existing /api/projects/{id}/submissions/... routes remain bit-
identical so the project-scoped flow keeps working unchanged.
- Frontend: /submissions/new lists the full cross-proceeding catalog
grouped by proceeding, filterable by text + chip. Each row offers
"Ohne Projekt" (instant draft) or "Mit Projekt…" (modal picker
with autocomplete over visible projects). /submissions index gains
a prominent "Neuer Entwurf" CTA and an empty-state CTA pointing at
the picker. The editor renders a banner + "Projekt zuweisen"
action when project_id is null; assigning persists project_id and
redirects to the project-scoped URL.
Audit + project-event writes detect d.ProjectID == nil; the audit
row's scope flips to 'user' (scope_root = user_id) and the
project_events row is skipped entirely.
71 lines
2.5 KiB
SQL
71 lines
2.5 KiB
SQL
-- t-paliad-243: drafts may exist without a project attached.
|
|
--
|
|
-- The global /submissions/new picker lets a lawyer start a Schriftsatz
|
|
-- draft straight from the top-level Schriftsätze sidebar, with or
|
|
-- without binding it to a project. project_id therefore becomes
|
|
-- optional. Existing rows are unaffected; new rows may insert NULL.
|
|
--
|
|
-- RLS rewrite: every policy splits on (project_id IS NULL):
|
|
--
|
|
-- project_id IS NOT NULL → gate on paliad.can_see_project (existing
|
|
-- inheritance-aware visibility).
|
|
-- project_id IS NULL → owner-only (user_id = auth.uid()). A
|
|
-- project-less draft is a personal scratch
|
|
-- space — never shared, never visible to
|
|
-- other team members.
|
|
--
|
|
-- INSERT enforces the same shape via WITH CHECK: a project-less insert
|
|
-- only writes user_id = auth.uid(); a project-scoped insert additionally
|
|
-- requires can_see_project.
|
|
|
|
ALTER TABLE paliad.submission_drafts
|
|
ALTER COLUMN project_id DROP NOT NULL;
|
|
|
|
DROP POLICY IF EXISTS submission_drafts_select ON paliad.submission_drafts;
|
|
CREATE POLICY submission_drafts_select
|
|
ON paliad.submission_drafts FOR SELECT TO authenticated
|
|
USING (
|
|
(project_id IS NULL AND user_id = auth.uid())
|
|
OR (project_id IS NOT NULL AND paliad.can_see_project(project_id))
|
|
);
|
|
|
|
DROP POLICY IF EXISTS submission_drafts_insert ON paliad.submission_drafts;
|
|
CREATE POLICY submission_drafts_insert
|
|
ON paliad.submission_drafts FOR INSERT TO authenticated
|
|
WITH CHECK (
|
|
user_id = auth.uid()
|
|
AND (
|
|
project_id IS NULL
|
|
OR paliad.can_see_project(project_id)
|
|
)
|
|
);
|
|
|
|
DROP POLICY IF EXISTS submission_drafts_update ON paliad.submission_drafts;
|
|
CREATE POLICY submission_drafts_update
|
|
ON paliad.submission_drafts FOR UPDATE TO authenticated
|
|
USING (
|
|
user_id = auth.uid()
|
|
AND (
|
|
project_id IS NULL
|
|
OR paliad.can_see_project(project_id)
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
user_id = auth.uid()
|
|
AND (
|
|
project_id IS NULL
|
|
OR paliad.can_see_project(project_id)
|
|
)
|
|
);
|
|
|
|
DROP POLICY IF EXISTS submission_drafts_delete ON paliad.submission_drafts;
|
|
CREATE POLICY submission_drafts_delete
|
|
ON paliad.submission_drafts FOR DELETE TO authenticated
|
|
USING (
|
|
user_id = auth.uid()
|
|
AND (
|
|
project_id IS NULL
|
|
OR paliad.can_see_project(project_id)
|
|
)
|
|
);
|