Files
Heritage/db/schema.sql
T

119 lines
5.1 KiB
PL/PgSQL

-- ============================================================
-- Родословное дерево семьи — SQL-схема (PostgreSQL 15+)
-- ============================================================
-- Кодировка UTF-8. Запуск: psql -U postgres -d family_tree -f schema.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- быстрый поиск по имени/фамилии
-- ---------- Пользователи и роли ----------
CREATE TYPE user_role AS ENUM ('admin', 'editor', 'viewer');
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
display_name TEXT NOT NULL,
role user_role NOT NULL DEFAULT 'viewer',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- ---------- Персоны ----------
CREATE TYPE gender_t AS ENUM ('male', 'female', 'other');
CREATE TABLE persons (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
first_name TEXT NOT NULL,
last_name TEXT,
middle_name TEXT,
gender gender_t NOT NULL DEFAULT 'other',
birth_date DATE,
death_date DATE,
birth_place TEXT,
photo_url TEXT,
bio TEXT,
contacts TEXT,
notes TEXT,
context TEXT, -- произвольный дополнительный контекст
resource_url TEXT, -- ссылка на подробный внешний ресурс
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_persons_last_name ON persons USING gin (last_name gin_trgm_ops);
CREATE INDEX idx_persons_first_name ON persons USING gin (first_name gin_trgm_ops);
-- ---------- Связь родитель → ребёнок (вкл. приёмные) ----------
CREATE TYPE parent_kind AS ENUM ('biological', 'adoptive', 'step', 'guardian');
CREATE TABLE parent_child (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
parent_id UUID NOT NULL REFERENCES persons(id) ON DELETE CASCADE,
child_id UUID NOT NULL REFERENCES persons(id) ON DELETE CASCADE,
kind parent_kind NOT NULL DEFAULT 'biological',
UNIQUE (parent_id, child_id),
CHECK (parent_id <> child_id)
);
CREATE INDEX idx_pc_parent ON parent_child(parent_id);
CREATE INDEX idx_pc_child ON parent_child(child_id);
-- ---------- Браки / партнёрства (несколько браков поддерживаются) ----------
CREATE TYPE union_status AS ENUM ('married', 'divorced', 'partner', 'widowed');
CREATE TABLE unions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
partner_a UUID NOT NULL REFERENCES persons(id) ON DELETE CASCADE,
partner_b UUID NOT NULL REFERENCES persons(id) ON DELETE CASCADE,
status union_status NOT NULL DEFAULT 'married',
start_date DATE,
end_date DATE,
UNIQUE (partner_a, partner_b),
CHECK (partner_a <> partner_b)
);
-- ---------- Галерея фотографий ----------
CREATE TABLE media (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
person_id UUID NOT NULL REFERENCES persons(id) ON DELETE CASCADE,
url TEXT NOT NULL,
caption TEXT,
sort_order INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_media_person ON media(person_id);
-- ---------- История изменений (аудит) ----------
CREATE TABLE change_log (
id BIGSERIAL PRIMARY KEY,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
entity TEXT NOT NULL, -- 'person' | 'union' | 'parent_child' | ...
entity_id UUID,
action TEXT NOT NULL, -- 'create' | 'update' | 'delete'
diff JSONB, -- было/стало
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_log_entity ON change_log(entity, entity_id);
-- ---------- Авто-обновление updated_at ----------
CREATE OR REPLACE FUNCTION touch_updated_at() RETURNS trigger AS $$
BEGIN NEW.updated_at = now(); RETURN NEW; END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_persons_touch BEFORE UPDATE ON persons
FOR EACH ROW EXECUTE FUNCTION touch_updated_at();
CREATE TRIGGER trg_users_touch BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION touch_updated_at();
-- ---------- Удобное представление дерева ----------
CREATE VIEW person_relations AS
SELECT p.id,
p.first_name, p.last_name, p.middle_name, p.gender,
p.birth_date, p.death_date,
(SELECT array_agg(pc.parent_id) FROM parent_child pc WHERE pc.child_id = p.id) AS parent_ids,
(SELECT array_agg(pc.child_id) FROM parent_child pc WHERE pc.parent_id = p.id) AS child_ids,
(SELECT array_agg(CASE WHEN u.partner_a = p.id THEN u.partner_b ELSE u.partner_a END)
FROM unions u WHERE u.partner_a = p.id OR u.partner_b = p.id) AS spouse_ids
FROM persons p;