feat(backend): bootstrap Go module, config, and data plane
Introduce configuration validation, Postgres migrations, Redis/storage adapters, and the seed sample video.
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
-- 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)
|
||||
);
|
||||
Reference in New Issue
Block a user