Files
ahp f76c7a110b feat(backend): bootstrap Go module, config, and data plane
Introduce configuration validation, Postgres migrations, Redis/storage adapters, and the seed sample video.
2026-10-07 19:40:26 +03:30

205 lines
7.3 KiB
SQL

-- 0001_init.sql
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TYPE user_role AS ENUM ('student', 'mentor', 'admin');
CREATE TYPE notif_category AS ENUM ('progress', 'gamification', 'mentor', 'event');
CREATE TYPE quiz_status AS ENUM ('in_progress', 'passed', 'failed');
CREATE TYPE event_type AS ENUM ('meet', 'zoom', 'workshop');
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
name TEXT NOT NULL,
role user_role NOT NULL DEFAULT 'student',
xp INT NOT NULL DEFAULT 0,
hearts INT NOT NULL DEFAULT 3,
hearts_updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
streak_current INT NOT NULL DEFAULT 0,
streak_longest INT NOT NULL DEFAULT 0,
last_activity_date DATE,
is_locked BOOLEAN NOT NULL DEFAULT false,
locked_at TIMESTAMPTZ,
unlocked_by BIGINT REFERENCES users(id),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE chapters (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
icon TEXT NOT NULL DEFAULT 'leaf',
sort_order INT NOT NULL DEFAULT 0
);
CREATE TABLE lessons (
id BIGSERIAL PRIMARY KEY,
chapter_id BIGINT NOT NULL REFERENCES chapters(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
video_key TEXT NOT NULL DEFAULT '',
duration_seconds INT NOT NULL DEFAULT 0,
completion_threshold_pct INT NOT NULL DEFAULT 85,
requires_lesson_id BIGINT REFERENCES lessons(id),
x INT NOT NULL DEFAULT 0,
y INT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
xp_reward INT NOT NULL DEFAULT 50,
is_active BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE mcq_questions (
id BIGSERIAL PRIMARY KEY,
lesson_id BIGINT NOT NULL REFERENCES lessons(id) ON DELETE CASCADE,
position INT NOT NULL DEFAULT 0,
question TEXT NOT NULL,
options JSONB NOT NULL,
answer_index INT NOT NULL,
explanation TEXT NOT NULL DEFAULT ''
);
CREATE TABLE watch_sessions (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
lesson_id BIGINT NOT NULL REFERENCES lessons(id),
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_position FLOAT NOT NULL DEFAULT 0,
UNIQUE (user_id, lesson_id)
);
CREATE TABLE watch_heartbeats (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
lesson_id BIGINT NOT NULL REFERENCES lessons(id),
session_id BIGINT NOT NULL REFERENCES watch_sessions(id),
position FLOAT NOT NULL,
delta FLOAT NOT NULL,
seq INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (user_id, lesson_id, seq)
);
CREATE TABLE lesson_progress (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
lesson_id BIGINT NOT NULL REFERENCES lessons(id),
watched_seconds FLOAT NOT NULL DEFAULT 0,
watched_pct FLOAT NOT NULL DEFAULT 0,
last_position FLOAT NOT NULL DEFAULT 0,
quiz_unlocked BOOLEAN NOT NULL DEFAULT false,
passed_quiz BOOLEAN NOT NULL DEFAULT false,
quiz_completed_at TIMESTAMPTZ,
UNIQUE (user_id, lesson_id)
);
CREATE TABLE quiz_attempts (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
lesson_id BIGINT NOT NULL REFERENCES lessons(id),
answers JSONB NOT NULL DEFAULT '[]',
correct_count INT NOT NULL DEFAULT 0,
total INT NOT NULL DEFAULT 0,
score_pct FLOAT NOT NULL DEFAULT 0,
status quiz_status NOT NULL DEFAULT 'in_progress',
hearts_lost INT NOT NULL DEFAULT 0,
passed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE xp_events (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
amount INT NOT NULL,
source TEXT NOT NULL,
ref_id BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE weekly_leaderboard (
week TEXT NOT NULL,
user_id BIGINT NOT NULL REFERENCES users(id),
xp INT NOT NULL DEFAULT 0,
PRIMARY KEY (week, user_id)
);
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
event_type event_type NOT NULL DEFAULT 'meet',
external_url TEXT NOT NULL DEFAULT '',
starts_at TIMESTAMPTZ NOT NULL,
ends_at TIMESTAMPTZ NOT NULL,
ics_uuid UUID NOT NULL DEFAULT gen_random_uuid(),
is_active BOOLEAN NOT NULL DEFAULT true,
created_by BIGINT REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE event_rsvps (
id BIGSERIAL PRIMARY KEY,
event_id BIGINT NOT NULL REFERENCES events(id) ON DELETE CASCADE,
user_id BIGINT NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (event_id, user_id)
);
CREATE TABLE chat_messages (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
mentor_id BIGINT NOT NULL REFERENCES users(id),
sender_role user_role NOT NULL DEFAULT 'student',
body TEXT NOT NULL,
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE notifications (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
category notif_category NOT NULL,
type TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT NOT NULL DEFAULT '',
route TEXT NOT NULL DEFAULT '',
data JSONB NOT NULL DEFAULT '{}',
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_notifications_user ON notifications (user_id, created_at DESC);
CREATE TABLE notification_prefs (
user_id BIGINT PRIMARY KEY REFERENCES users(id),
progress BOOLEAN NOT NULL DEFAULT true,
gamification BOOLEAN NOT NULL DEFAULT true,
mentor BOOLEAN NOT NULL DEFAULT true,
event BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE video_assets (
id BIGSERIAL PRIMARY KEY,
key TEXT UNIQUE NOT NULL,
mime TEXT NOT NULL DEFAULT 'video/mp4',
size_bytes BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE refresh_tokens (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
token_hash TEXT UNIQUE NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE reminders_sent (
user_id BIGINT NOT NULL REFERENCES users(id),
kind TEXT NOT NULL,
ref TEXT NOT NULL,
sent_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, kind, ref)
);