Migration 020 renamed paliad.can_see_projekt → can_see_project (and
notiz_is_visible → note_is_visible) via ALTER FUNCTION but never rewrote
the bodies. On production the bodies still queried paliad.projekte /
projekt_teams / fristen / termine / projekt_events — all of which were
dropped or renamed in 018+020. Every RLS-enforced read against the new
English tables exploded with `relation "paliad.projekte" does not exist`,
breaking /api/projects, /api/deadlines, /api/appointments etc.
Same problem for the trigger functions paliad.projekte_sync_path() and
paliad.projekte_rewrite_subtree() — kept their German names and German
bodies; the triggers on paliad.projects still pointed at them.
Migration 021:
* DROP FUNCTION ... CASCADE drops can_see_project / note_is_visible
along with their 21 dependent RLS policies (whose names were still
German on prod: projekte_*, projekt_teams_*, fristen_all, termine_*,
parteien_all, dokumente_all, projekt_events_all, notizen_all,
checklist_instances_*).
* Recreates the two functions with English bodies + English parameter
names and rebuilds every dependent policy under its canonical
English name (matching migration 018).
* Drops the German trigger functions/triggers on paliad.projects and
recreates them as projects_sync_path / projects_rewrite_subtree.
Idempotent on a fresh DB (where everything is already English): the
CASCADE drops the same policies and the recreate produces an identical
end state.
Verified by running the up.sql in BEGIN/ROLLBACK against the actual
youpc prod Postgres — 21 policies dropped, 21 recreated, function
bodies now reference paliad.projects / project_teams / etc.
Refs: tests/smoke-auth-2026-04-25.md (Bug 3, root cause for Bugs 1+2).
132 lines
5.1 KiB
PL/PgSQL
132 lines
5.1 KiB
PL/PgSQL
-- Best-effort revert of 021. NOT a clean roll-back — this migration only
|
|
-- restores the broken German-bodied versions of can_see_project /
|
|
-- note_is_visible / projekte_*; if the underlying tables have already been
|
|
-- renamed to English (i.e. 020 hasn't been rolled back yet), the German
|
|
-- bodies will still error. Run 020 / 018 down first if you really want to
|
|
-- get back to a working German state.
|
|
--
|
|
-- We do not recreate the dropped policies under their previous German names;
|
|
-- migration 018 / 020 down rebuilds the German-named policy set, and the
|
|
-- English-named policies created by 021.up are the right starting point for
|
|
-- those rollbacks.
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Revert trigger functions
|
|
-- ---------------------------------------------------------------------------
|
|
DROP TRIGGER IF EXISTS projects_sync_path_before ON paliad.projects;
|
|
DROP TRIGGER IF EXISTS projects_rewrite_subtree_after ON paliad.projects;
|
|
DROP FUNCTION IF EXISTS paliad.projects_sync_path() CASCADE;
|
|
DROP FUNCTION IF EXISTS paliad.projects_rewrite_subtree() CASCADE;
|
|
|
|
CREATE OR REPLACE FUNCTION paliad.projekte_sync_path()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
DECLARE
|
|
parent_path text;
|
|
BEGIN
|
|
IF NEW.parent_id IS NULL THEN
|
|
NEW.path := NEW.id::text;
|
|
ELSE
|
|
SELECT path INTO parent_path
|
|
FROM paliad.projekte
|
|
WHERE id = NEW.parent_id;
|
|
IF parent_path IS NULL THEN
|
|
RAISE EXCEPTION 'parent projekt % not found', NEW.parent_id;
|
|
END IF;
|
|
IF parent_path = NEW.id::text
|
|
OR parent_path LIKE '%.' || NEW.id::text
|
|
OR parent_path LIKE NEW.id::text || '.%'
|
|
OR parent_path LIKE '%.' || NEW.id::text || '.%'
|
|
THEN
|
|
RAISE EXCEPTION 'cannot set parent to own descendant';
|
|
END IF;
|
|
NEW.path := parent_path || '.' || NEW.id::text;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION paliad.projekte_rewrite_subtree()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
IF OLD.path IS DISTINCT FROM NEW.path THEN
|
|
UPDATE paliad.projekte
|
|
SET path = NEW.path || substring(path FROM length(OLD.path) + 1)
|
|
WHERE path LIKE OLD.path || '.%';
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
-- The triggers can only be bound to a table that exists; pick whichever of
|
|
-- paliad.projekte / paliad.projects is currently around.
|
|
DO $$ BEGIN
|
|
EXECUTE 'CREATE TRIGGER projekte_sync_path_before BEFORE INSERT OR UPDATE OF parent_id ON paliad.projekte FOR EACH ROW EXECUTE FUNCTION paliad.projekte_sync_path()';
|
|
EXCEPTION WHEN undefined_table THEN
|
|
EXECUTE 'CREATE TRIGGER projekte_sync_path_before BEFORE INSERT OR UPDATE OF parent_id ON paliad.projects FOR EACH ROW EXECUTE FUNCTION paliad.projekte_sync_path()';
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
EXECUTE 'CREATE TRIGGER projekte_rewrite_subtree_after AFTER UPDATE OF path ON paliad.projekte FOR EACH ROW EXECUTE FUNCTION paliad.projekte_rewrite_subtree()';
|
|
EXCEPTION WHEN undefined_table THEN
|
|
EXECUTE 'CREATE TRIGGER projekte_rewrite_subtree_after AFTER UPDATE OF path ON paliad.projects FOR EACH ROW EXECUTE FUNCTION paliad.projekte_rewrite_subtree()';
|
|
END $$;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Revert visibility functions to their pre-021 (broken) state
|
|
-- ---------------------------------------------------------------------------
|
|
DROP FUNCTION IF EXISTS paliad.note_is_visible(uuid, uuid, uuid, uuid) CASCADE;
|
|
DROP FUNCTION IF EXISTS paliad.can_see_project(uuid) CASCADE;
|
|
|
|
CREATE FUNCTION paliad.can_see_project(_projekt_id uuid)
|
|
RETURNS boolean
|
|
LANGUAGE sql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
SET search_path = paliad, public
|
|
AS $$
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM paliad.users u
|
|
WHERE u.id = auth.uid() AND u.role = 'admin'
|
|
)
|
|
OR EXISTS (
|
|
SELECT 1
|
|
FROM paliad.projekte target
|
|
JOIN paliad.projekt_teams pt
|
|
ON pt.user_id = auth.uid()
|
|
AND pt.projekt_id = ANY(string_to_array(target.path, '.')::uuid[])
|
|
WHERE target.id = _projekt_id
|
|
);
|
|
$$;
|
|
|
|
CREATE FUNCTION paliad.note_is_visible(
|
|
_projekt_id uuid,
|
|
_frist_id uuid,
|
|
_termin_id uuid,
|
|
_projekt_event_id uuid
|
|
) RETURNS boolean
|
|
LANGUAGE sql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
SET search_path = paliad, public
|
|
AS $$
|
|
SELECT CASE
|
|
WHEN _projekt_id IS NOT NULL THEN paliad.can_see_projekt(_projekt_id)
|
|
WHEN _frist_id IS NOT NULL THEN paliad.can_see_projekt(
|
|
(SELECT projekt_id FROM paliad.fristen WHERE id = _frist_id))
|
|
WHEN _termin_id IS NOT NULL THEN
|
|
CASE
|
|
WHEN (SELECT projekt_id FROM paliad.termine WHERE id = _termin_id) IS NULL
|
|
THEN (SELECT created_by FROM paliad.termine WHERE id = _termin_id) = auth.uid()
|
|
ELSE paliad.can_see_projekt(
|
|
(SELECT projekt_id FROM paliad.termine WHERE id = _termin_id))
|
|
END
|
|
WHEN _projekt_event_id IS NOT NULL THEN paliad.can_see_projekt(
|
|
(SELECT projekt_id FROM paliad.projekt_events WHERE id = _projekt_event_id))
|
|
ELSE false
|
|
END;
|
|
$$;
|