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 config:*)",
|
||||||
"Bash(git remote:*)",
|
"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(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-volumer (flyktige, ikke i Git)
|
||||||
.docker-data/
|
.docker-data/
|
||||||
|
|
||||||
|
# Scratch (testfiler, notater, midlertidig)
|
||||||
|
.scratch/
|
||||||
|
|
||||||
# Miljovariabler
|
# Miljovariabler
|
||||||
.env.local
|
.env.local
|
||||||
.env
|
.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