Legger til steg 4 i recompute_access: når en bruker melder seg inn i et team (member_of-edge), arver brukeren all tilgang teamet allerede har. Tidligere håndterte funksjonen kun retningen "team får ny tilgang → propager til eksisterende medlemmer" (steg 3). Nå håndteres begge retninger: - Steg 3: Team får tilgang → alle eksisterende medlemmer arver - Steg 4: Ny bruker melder seg inn → arver teamets eksisterende tilgang Testet med scenario: Trond → Podcastteamet → Sidelinja → Episode 42. Trond arver member-tilgang til alle tre noder via team-transitivitet. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
5 KiB
PL/PgSQL
128 lines
5 KiB
PL/PgSQL
-- 001_initial_schema.sql
|
|
-- Oppretter kjerneskjema for Synops: enums, nodes, edges, node_access, auth_identities.
|
|
-- Ref: docs/primitiver/nodes.md, docs/primitiver/edges.md, docs/retninger/bruker_ikke_workspace.md
|
|
|
|
BEGIN;
|
|
|
|
-- =============================================================================
|
|
-- Enums
|
|
-- =============================================================================
|
|
|
|
CREATE TYPE visibility AS ENUM ('hidden', 'discoverable', 'readable', 'open');
|
|
CREATE TYPE access_level AS ENUM ('reader', 'member', 'admin', 'owner');
|
|
|
|
-- =============================================================================
|
|
-- Nodes
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE nodes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
node_kind TEXT NOT NULL DEFAULT 'content',
|
|
title TEXT,
|
|
content TEXT,
|
|
visibility visibility NOT NULL DEFAULT 'hidden',
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
created_by UUID REFERENCES nodes(id)
|
|
);
|
|
|
|
CREATE INDEX idx_nodes_kind ON nodes (node_kind);
|
|
CREATE INDEX idx_nodes_created_by ON nodes (created_by);
|
|
CREATE INDEX idx_nodes_visibility ON nodes (visibility);
|
|
|
|
-- =============================================================================
|
|
-- Edges
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE edges (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
source_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
target_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
edge_type TEXT NOT NULL,
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
|
system BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
created_by UUID REFERENCES nodes(id),
|
|
|
|
UNIQUE (source_id, target_id, edge_type)
|
|
);
|
|
|
|
CREATE INDEX idx_edges_source ON edges (source_id);
|
|
CREATE INDEX idx_edges_target ON edges (target_id);
|
|
CREATE INDEX idx_edges_type ON edges (edge_type);
|
|
|
|
-- =============================================================================
|
|
-- Tilgangsmatrise (node_access)
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE node_access (
|
|
subject_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
object_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
access access_level NOT NULL,
|
|
via_edge UUID REFERENCES edges(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (subject_id, object_id)
|
|
);
|
|
|
|
CREATE INDEX idx_na_subject ON node_access (subject_id);
|
|
|
|
-- =============================================================================
|
|
-- Auth-identiteter (bro mellom HTTP-sesjon og graf)
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE auth_identities (
|
|
node_id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
|
authentik_sub TEXT UNIQUE NOT NULL,
|
|
email TEXT UNIQUE NOT NULL
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- Tilgangsberegning (recompute_access)
|
|
-- Oppdaterer node_access-matrisen når edges endres.
|
|
-- Ref: docs/retninger/bruker_ikke_workspace.md
|
|
-- =============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION recompute_access(
|
|
p_subject_id UUID,
|
|
p_root_node_id UUID,
|
|
p_access access_level,
|
|
p_via_edge UUID
|
|
) RETURNS void AS $$
|
|
BEGIN
|
|
-- Direkte tilgang til roten
|
|
INSERT INTO node_access (subject_id, object_id, access, via_edge)
|
|
VALUES (p_subject_id, p_root_node_id, p_access, p_via_edge)
|
|
ON CONFLICT (subject_id, object_id)
|
|
DO UPDATE SET access = GREATEST(node_access.access, p_access);
|
|
|
|
-- Transitiv: noder som tilhører roten
|
|
INSERT INTO node_access (subject_id, object_id, access, via_edge)
|
|
SELECT p_subject_id, e.source_id, p_access, p_via_edge
|
|
FROM edges e
|
|
WHERE e.target_id = p_root_node_id
|
|
AND e.edge_type = 'belongs_to'
|
|
ON CONFLICT (subject_id, object_id)
|
|
DO UPDATE SET access = GREATEST(node_access.access, p_access);
|
|
|
|
-- Hvis subject er et team: propager til alle teammedlemmer
|
|
INSERT INTO node_access (subject_id, object_id, access, via_edge)
|
|
SELECT e.source_id, na.object_id, na.access, na.via_edge
|
|
FROM node_access na
|
|
JOIN edges e ON e.target_id = p_subject_id
|
|
AND e.edge_type = 'member_of'
|
|
WHERE na.subject_id = p_subject_id
|
|
ON CONFLICT (subject_id, object_id)
|
|
DO UPDATE SET access = GREATEST(node_access.access, EXCLUDED.access);
|
|
|
|
-- Team-transitivitet: arv tilgang fra teamet.
|
|
-- Når en bruker melder seg inn i et team, arver brukeren
|
|
-- all tilgang teamet allerede har til andre noder.
|
|
INSERT INTO node_access (subject_id, object_id, access, via_edge)
|
|
SELECT p_subject_id, na.object_id, na.access, na.via_edge
|
|
FROM node_access na
|
|
WHERE na.subject_id = p_root_node_id
|
|
ON CONFLICT (subject_id, object_id)
|
|
DO UPDATE SET access = GREATEST(node_access.access, EXCLUDED.access);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMIT;
|