Files
BoostAI/Backend/db/migrations/001_init.sql
2026-05-25 17:05:06 +01:00

161 lines
5.9 KiB
PL/PgSQL

-- +goose Up
CREATE TYPE user_role AS ENUM ('student', 'teacher');
CREATE TYPE question_status AS ENUM ('draft', 'published', 'archived');
CREATE TYPE assignment_status AS ENUM ('draft', 'assigned', 'closed');
CREATE TYPE answer_status AS ENUM ('not_started', 'in_progress', 'submitted', 'reviewed');
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash TEXT,
role user_role NOT NULL,
full_name VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE classrooms (
id BIGSERIAL PRIMARY KEY,
teacher_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
code VARCHAR(50) UNIQUE,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE classroom_students (
classroom_id BIGINT NOT NULL REFERENCES classrooms(id) ON DELETE CASCADE,
student_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (classroom_id, student_id)
);
CREATE TABLE questions (
id BIGSERIAL PRIMARY KEY,
author_teacher_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
title VARCHAR(255) NOT NULL,
prompt TEXT NOT NULL,
subject VARCHAR(100),
source TEXT,
status question_status NOT NULL DEFAULT 'draft',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE tags (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE question_tags (
question_id BIGINT NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (question_id, tag_id)
);
CREATE TABLE assignments (
id BIGSERIAL PRIMARY KEY,
classroom_id BIGINT NOT NULL REFERENCES classrooms(id) ON DELETE CASCADE,
teacher_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
title VARCHAR(255) NOT NULL,
instructions TEXT,
status assignment_status NOT NULL DEFAULT 'draft',
due_at TIMESTAMPTZ,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE assignment_assignees (
assignment_id BIGINT NOT NULL REFERENCES assignments(id) ON DELETE CASCADE,
student_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (assignment_id, student_id)
);
CREATE TABLE assignment_questions (
assignment_id BIGINT NOT NULL REFERENCES assignments(id) ON DELETE CASCADE,
question_id BIGINT NOT NULL REFERENCES questions(id) ON DELETE RESTRICT,
position INTEGER NOT NULL,
PRIMARY KEY (assignment_id, question_id),
CONSTRAINT assignment_questions_assignment_position_key UNIQUE (assignment_id, position)
);
CREATE TABLE student_answers (
id BIGSERIAL PRIMARY KEY,
assignment_id BIGINT NOT NULL REFERENCES assignments(id) ON DELETE CASCADE,
question_id BIGINT NOT NULL REFERENCES questions(id) ON DELETE RESTRICT,
student_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
answer_text TEXT,
ai_feedback TEXT,
teacher_feedback TEXT,
status answer_status NOT NULL DEFAULT 'not_started',
submitted_at TIMESTAMPTZ,
reviewed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT student_answers_assignment_question_student_key UNIQUE (assignment_id, question_id, student_id)
);
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_classrooms_teacher_id ON classrooms(teacher_id);
CREATE INDEX idx_questions_author_teacher_id ON questions(author_teacher_id);
CREATE INDEX idx_questions_status ON questions(status);
CREATE INDEX idx_assignments_classroom_id ON assignments(classroom_id);
CREATE INDEX idx_assignments_teacher_id ON assignments(teacher_id);
CREATE INDEX idx_assignment_assignees_student_id ON assignment_assignees(student_id);
CREATE INDEX idx_student_answers_student_id ON student_answers(student_id);
CREATE INDEX idx_student_answers_assignment_id ON student_answers(assignment_id);
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- +goose StatementEnd
CREATE TRIGGER users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER classrooms_updated_at BEFORE UPDATE ON classrooms
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER questions_updated_at BEFORE UPDATE ON questions
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER assignments_updated_at BEFORE UPDATE ON assignments
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER student_answers_updated_at BEFORE UPDATE ON student_answers
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- +goose Down
DROP TRIGGER IF EXISTS student_answers_updated_at ON student_answers;
DROP TRIGGER IF EXISTS assignments_updated_at ON assignments;
DROP TRIGGER IF EXISTS questions_updated_at ON questions;
DROP TRIGGER IF EXISTS classrooms_updated_at ON classrooms;
DROP TRIGGER IF EXISTS users_updated_at ON users;
DROP FUNCTION IF EXISTS update_updated_at();
DROP TABLE IF EXISTS student_answers;
DROP TABLE IF EXISTS assignment_questions;
DROP TABLE IF EXISTS assignment_assignees;
DROP TABLE IF EXISTS assignments;
DROP TABLE IF EXISTS question_tags;
DROP TABLE IF EXISTS tags;
DROP TABLE IF EXISTS questions;
DROP TABLE IF EXISTS classroom_students;
DROP TABLE IF EXISTS classrooms;
DROP TABLE IF EXISTS users;
DROP TYPE IF EXISTS answer_status;
DROP TYPE IF EXISTS assignment_status;
DROP TYPE IF EXISTS question_status;
DROP TYPE IF EXISTS user_role;