synops/migrations/001_initial_schema.sql
vegard a30a484076 Opprett PostgreSQL-skjema for Synops (oppgave 1.1)
Oppretter database `synops` på serveren med kjerneskjemaet:
- Enums: visibility (hidden/discoverable/readable/open),
  access_level (reader/member/admin/owner)
- Tabeller: nodes, edges, node_access, auth_identities
- Funksjon: recompute_access for tilgangsmatrise-oppdatering
- Indekser iht. docs/primitiver/nodes.md og edges.md

Migrasjonen er kjørt og verifisert på produksjonsserver
(sidelinja-postgres-1, database: synops).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:53:31 +01:00

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