PostgreSQL-skjema: kunnskapsgraf, meldinger, jobbkø og mediefiler
Første migrasjon (0001) med komplett databasearkitektur: - nodes/graph_edges grafmodell med relasjonstyper - Faktoider og meldinger med stemme-system (opp/ned) - Chat med redigeringshistorikk, tråding og research-clips - Content-addressable mediefiler (SHA-256 deduplisering) - PostgreSQL-basert jobbkø med SKIP LOCKED - Brukertabell som tynn cache fra Authentik Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4b56560bf9
commit
2100184f4e
3 changed files with 225 additions and 1 deletions
|
|
@ -59,7 +59,8 @@
|
|||
"Bash(git config:*)",
|
||||
"Bash(git remote:*)",
|
||||
"Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\ncd /tmp\ngit clone ssh://git@git.sidelinja.org:222/sidelinja/sidelinja.git test-clone 2>&1\nls test-clone/\nrm -rf test-clone\necho \"DONE\"\nREMOTE)",
|
||||
"Bash(git add:*)"
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,6 +1,9 @@
|
|||
# Docker-volumer (flyktige, ikke i Git)
|
||||
.docker-data/
|
||||
|
||||
# Scratch (testfiler, notater, midlertidig)
|
||||
.scratch/
|
||||
|
||||
# Miljovariabler
|
||||
.env.local
|
||||
.env
|
||||
|
|
|
|||
220
migrations/0001_initial_schema.sql
Normal file
220
migrations/0001_initial_schema.sql
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
-- Sidelinja: Initial database schema
|
||||
-- Kunnskapsgraf, jobbkø, meldinger, mediefiler
|
||||
--
|
||||
-- Kjøres mot en tom PostgreSQL-database.
|
||||
-- Krever: PostgreSQL 15+ med pgcrypto (gen_random_uuid)
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- Extensions
|
||||
-- ============================================================
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- gen_random_uuid()
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- uuid_generate_v5() for deterministiske segment-UUIDs
|
||||
|
||||
-- ============================================================
|
||||
-- 1. Brukere (tynn cache fra Authentik)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE users (
|
||||
authentik_id TEXT PRIMARY KEY,
|
||||
display_name TEXT NOT NULL,
|
||||
avatar_url TEXT,
|
||||
cached_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. Nodes — supertabell for alle grafentiteter
|
||||
-- ============================================================
|
||||
|
||||
CREATE TYPE node_type AS ENUM (
|
||||
'tema', 'aktør', 'faktoide', 'episode', 'segment', 'melding'
|
||||
);
|
||||
|
||||
CREATE TABLE nodes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
node_type node_type NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 3. Relasjonstyper (seeded + utvidbar)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE relation_types (
|
||||
name TEXT PRIMARY KEY,
|
||||
label TEXT NOT NULL,
|
||||
description TEXT,
|
||||
system BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
INSERT INTO relation_types (name, label, description, system) VALUES
|
||||
('MENTIONS', 'Nevner', 'Refererer til denne aktøren/temaet', true),
|
||||
('ABOUT', 'Handler om', 'Faktoide eller klipp som omhandler', true),
|
||||
('DISCUSSED_IN', 'Diskutert i', 'Tema diskutert i dette segmentet', true),
|
||||
('WORKS_FOR', 'Jobber for', 'Person tilknyttet organisasjon', true),
|
||||
('CONTRADICTS', 'Motsier', 'Står i motsetning til', true),
|
||||
('PART_OF', 'Del av', 'Tilhører / er underordnet', true);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. Graph edges
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE graph_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,
|
||||
relation_type TEXT NOT NULL REFERENCES relation_types(name),
|
||||
context_id UUID REFERENCES nodes(id) ON DELETE SET NULL,
|
||||
confidence REAL CHECK (confidence BETWEEN 0.0 AND 1.0),
|
||||
created_by TEXT REFERENCES users(authentik_id) ON DELETE SET NULL,
|
||||
origin TEXT NOT NULL DEFAULT 'system',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT no_self_reference CHECK (source_id != target_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_edges_source ON graph_edges(source_id);
|
||||
CREATE INDEX idx_edges_target ON graph_edges(target_id);
|
||||
CREATE INDEX idx_edges_relation ON graph_edges(relation_type);
|
||||
|
||||
-- ============================================================
|
||||
-- 5. Detailtabeller — aktører, temaer, episoder, segmenter
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE actors (
|
||||
id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT -- 'person', 'organisasjon', etc.
|
||||
);
|
||||
|
||||
CREATE TABLE topics (
|
||||
id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE episodes (
|
||||
id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
published_at TIMESTAMPTZ,
|
||||
guid TEXT UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE segments (
|
||||
id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
||||
episode_id UUID NOT NULL REFERENCES episodes(id) ON DELETE CASCADE,
|
||||
start_time INTERVAL NOT NULL,
|
||||
end_time INTERVAL NOT NULL,
|
||||
transcript TEXT,
|
||||
CONSTRAINT valid_timerange CHECK (end_time > start_time)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_segments_episode ON segments(episode_id);
|
||||
CREATE INDEX idx_segments_fts ON segments USING GIN (to_tsvector('norwegian', transcript));
|
||||
|
||||
-- ============================================================
|
||||
-- 6. Faktoider
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE factoids (
|
||||
id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
||||
body TEXT NOT NULL,
|
||||
source_url TEXT,
|
||||
created_by TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE factoid_votes (
|
||||
factoid_id UUID NOT NULL REFERENCES factoids(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE CASCADE,
|
||||
vote SMALLINT NOT NULL CHECK (vote IN (-1, 1)),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (factoid_id, user_id)
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 7. Meldinger (chat)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TYPE message_type AS ENUM (
|
||||
'text', -- Vanlig Markdown-melding
|
||||
'research_clip', -- Innlimt artikkel (Ctrl+A)
|
||||
'factoid', -- Faktoid delt i chat
|
||||
'system' -- Automatisk systemmelding
|
||||
);
|
||||
|
||||
CREATE TABLE messages (
|
||||
id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
||||
topic_id UUID NOT NULL REFERENCES topics(id) ON DELETE CASCADE,
|
||||
reply_to UUID REFERENCES messages(id) ON DELETE SET NULL,
|
||||
author_id TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE SET NULL,
|
||||
message_type message_type NOT NULL DEFAULT 'text',
|
||||
body TEXT NOT NULL,
|
||||
metadata JSONB,
|
||||
edited_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_messages_topic ON messages(topic_id, created_at);
|
||||
|
||||
CREATE TABLE message_revisions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
||||
body TEXT NOT NULL,
|
||||
edited_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT revision_order UNIQUE (message_id, edited_at)
|
||||
);
|
||||
|
||||
CREATE TABLE message_votes (
|
||||
message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE CASCADE,
|
||||
vote SMALLINT NOT NULL CHECK (vote IN (-1, 1)),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (message_id, user_id)
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 8. Mediefiler (content-addressable)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE media_files (
|
||||
sha256 TEXT PRIMARY KEY,
|
||||
mime_type TEXT NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
uploaded_by TEXT NOT NULL REFERENCES users(authentik_id) ON DELETE SET NULL,
|
||||
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE message_attachments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
||||
sha256 TEXT NOT NULL REFERENCES media_files(sha256),
|
||||
sort_order SMALLINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 9. Jobbkø
|
||||
-- ============================================================
|
||||
|
||||
CREATE TYPE job_status AS ENUM ('pending', 'running', 'completed', 'error', 'retry');
|
||||
|
||||
CREATE TABLE job_queue (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
job_type TEXT NOT NULL,
|
||||
payload JSONB NOT NULL,
|
||||
status job_status NOT NULL DEFAULT 'pending',
|
||||
priority SMALLINT NOT NULL DEFAULT 0,
|
||||
result JSONB,
|
||||
error_msg TEXT,
|
||||
attempts SMALLINT NOT NULL DEFAULT 0,
|
||||
max_attempts SMALLINT NOT NULL DEFAULT 3,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
scheduled_for TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_job_queue_pending ON job_queue (priority DESC, scheduled_for ASC)
|
||||
WHERE status IN ('pending', 'retry');
|
||||
|
||||
COMMIT;
|
||||
Loading…
Add table
Reference in a new issue