-- 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); END; $$ LANGUAGE plpgsql; COMMIT;