LearnSpace: full-stack educational whiteboard platform

Node.js/Express backend + vanilla JS frontend.
Features: real-time collaborative whiteboard (SSE), multi-page support,
LaTeX formulas, shapes/connectors, coordinate systems, number lines,
compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with
rotation & resize controls, minimap navigation overlay, auto-measurements,
multi-page thumbnails sidebar, PNG export, page templates.
Student/teacher workflows: classes, assignments, library, dashboard.
Mobile responsive. SQLite (better-sqlite3).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-04-12 10:10:37 +03:00
commit be4d43105e
204 changed files with 118117 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
-- =============================================
-- LearnSpace — Initial schema
-- =============================================
-- Расширения
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ── Пользователи ──────────────────────────────
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'student'
CHECK (role IN ('student', 'teacher', 'admin')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login TIMESTAMPTZ
);
-- ── Предметы ──────────────────────────────────
CREATE TABLE IF NOT EXISTS subjects (
id SERIAL PRIMARY KEY,
slug VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
icon VARCHAR(10)
);
INSERT INTO subjects (slug, name, icon) VALUES
('bio', 'Биология', 'dna'),
('chem', 'Химия', 'atom'),
('math', 'Математика', 'compass'),
('phys', 'Физика', 'zap')
ON CONFLICT DO NOTHING;
-- ── Темы ──────────────────────────────────────
CREATE TABLE IF NOT EXISTS topics (
id SERIAL PRIMARY KEY,
subject_id INT NOT NULL REFERENCES subjects(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
order_index INT NOT NULL DEFAULT 0
);
-- ── Банк вопросов ─────────────────────────────
CREATE TABLE IF NOT EXISTS questions (
id SERIAL PRIMARY KEY,
subject_id INT NOT NULL REFERENCES subjects(id) ON DELETE CASCADE,
topic_id INT REFERENCES topics(id) ON DELETE SET NULL,
text TEXT NOT NULL,
difficulty SMALLINT NOT NULL DEFAULT 1 CHECK (difficulty BETWEEN 1 AND 3),
year SMALLINT,
explanation TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Варианты ответов ──────────────────────────
CREATE TABLE IF NOT EXISTS options (
id SERIAL PRIMARY KEY,
question_id INT NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
text TEXT NOT NULL,
is_correct BOOLEAN NOT NULL DEFAULT FALSE,
order_index SMALLINT NOT NULL DEFAULT 0
);
-- ── Сессии тестирования ───────────────────────
CREATE TABLE IF NOT EXISTS test_sessions (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
subject_id INT REFERENCES subjects(id) ON DELETE SET NULL,
mode VARCHAR(20) NOT NULL DEFAULT 'exam'
CHECK (mode IN ('exam', 'practice', 'topic', 'random')),
total INT NOT NULL,
score INT,
status VARCHAR(20) NOT NULL DEFAULT 'in_progress'
CHECK (status IN ('in_progress', 'completed', 'abandoned')),
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
finished_at TIMESTAMPTZ
);
-- ── Вопросы сессии ────────────────────────────
CREATE TABLE IF NOT EXISTS session_questions (
id SERIAL PRIMARY KEY,
session_id INT NOT NULL REFERENCES test_sessions(id) ON DELETE CASCADE,
question_id INT NOT NULL REFERENCES questions(id),
order_index INT NOT NULL
);
-- ── Ответы пользователя ───────────────────────
CREATE TABLE IF NOT EXISTS user_answers (
id SERIAL PRIMARY KEY,
session_id INT NOT NULL REFERENCES test_sessions(id) ON DELETE CASCADE,
question_id INT NOT NULL REFERENCES questions(id),
chosen_option_id INT REFERENCES options(id),
is_correct BOOLEAN,
time_spent_sec SMALLINT,
answered_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Индексы ───────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_questions_subject ON questions(subject_id);
CREATE INDEX IF NOT EXISTS idx_questions_topic ON questions(topic_id);
CREATE INDEX IF NOT EXISTS idx_sessions_user ON test_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_answers_session ON user_answers(session_id);
@@ -0,0 +1,3 @@
-- Уникальный индекс для upsert ответов
CREATE UNIQUE INDEX IF NOT EXISTS idx_answers_session_question
ON user_answers (session_id, question_id);