Family Tree: превью + full-stack (frontend, backend, SQL, Docker)
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
-- ============================================================
|
||||
-- Родословное дерево семьи — 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;
|
||||
Reference in New Issue
Block a user