Boost Azure Demo

This commit is contained in:
MangoPig
2026-05-25 17:05:06 +01:00
parent 675285e99d
commit 4f79137d89
230 changed files with 43275 additions and 2644 deletions

View File

@@ -0,0 +1,160 @@
-- +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;

View File

@@ -0,0 +1,44 @@
-- +goose Up
CREATE TABLE profiles (
user_id BIGINT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
preferred_name VARCHAR(100),
profile_icon_url TEXT,
headline VARCHAR(255),
bio TEXT,
timezone VARCHAR(100),
locale VARCHAR(20),
grade_level VARCHAR(100),
learning_goal TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO profiles (user_id)
SELECT id
FROM users
ON CONFLICT (user_id) DO NOTHING;
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION ensure_profile_for_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO profiles (user_id)
VALUES (NEW.id)
ON CONFLICT (user_id) DO NOTHING;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- +goose StatementEnd
CREATE TRIGGER profiles_updated_at BEFORE UPDATE ON profiles
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER users_ensure_profile_after_insert AFTER INSERT ON users
FOR EACH ROW EXECUTE FUNCTION ensure_profile_for_user();
-- +goose Down
DROP TRIGGER IF EXISTS users_ensure_profile_after_insert ON users;
DROP TRIGGER IF EXISTS profiles_updated_at ON profiles;
DROP FUNCTION IF EXISTS ensure_profile_for_user();
DROP TABLE IF EXISTS profiles;

View File

@@ -0,0 +1,48 @@
-- +goose Up
CREATE TABLE message_threads (
id BIGSERIAL PRIMARY KEY,
created_by_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
subject VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT message_threads_subject_not_blank CHECK (length(btrim(subject)) > 0)
);
CREATE TABLE message_thread_participants (
thread_id BIGINT NOT NULL REFERENCES message_threads(id) ON DELETE CASCADE,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_read_at TIMESTAMPTZ,
archived_at TIMESTAMPTZ,
PRIMARY KEY (thread_id, user_id)
);
CREATE TABLE messages (
id BIGSERIAL PRIMARY KEY,
thread_id BIGINT NOT NULL REFERENCES message_threads(id) ON DELETE CASCADE,
sender_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
body TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT messages_body_not_blank CHECK (length(btrim(body)) > 0)
);
CREATE INDEX idx_message_threads_created_by_user_id ON message_threads(created_by_user_id);
CREATE INDEX idx_message_threads_updated_at ON message_threads(updated_at DESC);
CREATE INDEX idx_message_thread_participants_user_id ON message_thread_participants(user_id);
CREATE INDEX idx_messages_thread_id_created_at ON messages(thread_id, created_at DESC);
CREATE INDEX idx_messages_sender_user_id ON messages(sender_user_id);
CREATE TRIGGER message_threads_updated_at BEFORE UPDATE ON message_threads
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER messages_updated_at BEFORE UPDATE ON messages
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- +goose Down
DROP TRIGGER IF EXISTS messages_updated_at ON messages;
DROP TRIGGER IF EXISTS message_threads_updated_at ON message_threads;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS message_thread_participants;
DROP TABLE IF EXISTS message_threads;

View File

@@ -0,0 +1,12 @@
-- +goose Up
ALTER TABLE student_answers
ADD COLUMN solve_mode TEXT NOT NULL DEFAULT 'just_answer',
ADD COLUMN working_steps TEXT,
ADD CONSTRAINT student_answers_solve_mode_check
CHECK (solve_mode IN ('just_answer', 'step_by_step', 'solve_together', 'handwritten'));
-- +goose Down
ALTER TABLE student_answers
DROP CONSTRAINT IF EXISTS student_answers_solve_mode_check,
DROP COLUMN IF EXISTS working_steps,
DROP COLUMN IF EXISTS solve_mode;

View File

@@ -0,0 +1,13 @@
-- +goose Up
ALTER TABLE questions
ADD COLUMN correct_answer TEXT;
ALTER TABLE student_answers
ADD COLUMN is_correct BOOLEAN;
-- +goose Down
ALTER TABLE student_answers
DROP COLUMN IF EXISTS is_correct;
ALTER TABLE questions
DROP COLUMN IF EXISTS correct_answer;

View File

@@ -0,0 +1,53 @@
-- +goose Up
ALTER TABLE assignment_assignees
ADD COLUMN ai_feedback TEXT,
ADD COLUMN teacher_feedback TEXT;
UPDATE assignment_assignees aa
SET ai_feedback = aggregated.ai_feedback
FROM (
SELECT
sa.assignment_id,
sa.student_id,
string_agg(
format('Question %s: %s', aq.position, btrim(sa.ai_feedback)),
E'\n\n'
ORDER BY aq.position ASC
) AS ai_feedback
FROM student_answers sa
JOIN assignment_questions aq
ON aq.assignment_id = sa.assignment_id
AND aq.question_id = sa.question_id
WHERE NULLIF(btrim(sa.ai_feedback), '') IS NOT NULL
GROUP BY sa.assignment_id, sa.student_id
) AS aggregated
WHERE aa.assignment_id = aggregated.assignment_id
AND aa.student_id = aggregated.student_id;
UPDATE assignment_assignees aa
SET teacher_feedback = aggregated.teacher_feedback
FROM (
SELECT
sa.assignment_id,
sa.student_id,
string_agg(
format('Question %s: %s', aq.position, btrim(sa.teacher_feedback)),
E'\n\n'
ORDER BY aq.position ASC
) AS teacher_feedback
FROM student_answers sa
JOIN assignment_questions aq
ON aq.assignment_id = sa.assignment_id
AND aq.question_id = sa.question_id
WHERE NULLIF(btrim(sa.teacher_feedback), '') IS NOT NULL
GROUP BY sa.assignment_id, sa.student_id
) AS aggregated
WHERE aa.assignment_id = aggregated.assignment_id
AND aa.student_id = aggregated.student_id;
-- +goose Down
ALTER TABLE assignment_assignees
DROP COLUMN IF EXISTS teacher_feedback,
DROP COLUMN IF EXISTS ai_feedback;

View File

@@ -0,0 +1,66 @@
-- +goose Up
CREATE TYPE assignment_pass_status AS ENUM ('pending', 'pass', 'no_pass');
ALTER TABLE student_answers
ADD COLUMN review_needs_attention BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN review_issue_reason TEXT,
ADD COLUMN review_correctness_score NUMERIC(4,3),
ADD COLUMN review_understanding_score NUMERIC(4,3),
ADD COLUMN review_question_score NUMERIC(4,3),
ADD COLUMN review_confidence NUMERIC(4,3),
ADD COLUMN review_tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
ADD CONSTRAINT student_answers_review_correctness_score_range_check
CHECK (review_correctness_score IS NULL OR (review_correctness_score >= 0 AND review_correctness_score <= 1)),
ADD CONSTRAINT student_answers_review_understanding_score_range_check
CHECK (review_understanding_score IS NULL OR (review_understanding_score >= 0 AND review_understanding_score <= 1)),
ADD CONSTRAINT student_answers_review_question_score_range_check
CHECK (review_question_score IS NULL OR (review_question_score >= 0 AND review_question_score <= 1)),
ADD CONSTRAINT student_answers_review_confidence_range_check
CHECK (review_confidence IS NULL OR (review_confidence >= 0 AND review_confidence <= 1));
UPDATE student_answers
SET review_correctness_score = CASE
WHEN is_correct IS TRUE THEN 1.000
WHEN is_correct IS FALSE THEN 0.000
ELSE NULL
END,
review_question_score = CASE
WHEN is_correct IS TRUE THEN 1.000
WHEN is_correct IS FALSE THEN 0.000
ELSE NULL
END
WHERE is_correct IS NOT NULL;
ALTER TABLE assignment_assignees
ADD COLUMN overall_score NUMERIC(5,2),
ADD COLUMN pass_threshold NUMERIC(5,2) NOT NULL DEFAULT 8.00,
ADD COLUMN pass_status assignment_pass_status NOT NULL DEFAULT 'pending',
ADD CONSTRAINT assignment_assignees_overall_score_range_check
CHECK (overall_score IS NULL OR (overall_score >= 0 AND overall_score <= 10)),
ADD CONSTRAINT assignment_assignees_pass_threshold_range_check
CHECK (pass_threshold >= 0 AND pass_threshold <= 10);
-- +goose Down
ALTER TABLE assignment_assignees
DROP CONSTRAINT IF EXISTS assignment_assignees_pass_threshold_range_check,
DROP CONSTRAINT IF EXISTS assignment_assignees_overall_score_range_check,
DROP COLUMN IF EXISTS pass_status,
DROP COLUMN IF EXISTS pass_threshold,
DROP COLUMN IF EXISTS overall_score;
ALTER TABLE student_answers
DROP CONSTRAINT IF EXISTS student_answers_review_confidence_range_check,
DROP CONSTRAINT IF EXISTS student_answers_review_question_score_range_check,
DROP CONSTRAINT IF EXISTS student_answers_review_understanding_score_range_check,
DROP CONSTRAINT IF EXISTS student_answers_review_correctness_score_range_check,
DROP COLUMN IF EXISTS review_tags,
DROP COLUMN IF EXISTS review_confidence,
DROP COLUMN IF EXISTS review_question_score,
DROP COLUMN IF EXISTS review_understanding_score,
DROP COLUMN IF EXISTS review_correctness_score,
DROP COLUMN IF EXISTS review_issue_reason,
DROP COLUMN IF EXISTS review_needs_attention;
DROP TYPE IF EXISTS assignment_pass_status;

View File

@@ -0,0 +1,22 @@
-- +goose Up
ALTER TABLE assignments
ADD COLUMN pass_threshold NUMERIC(5,2) NOT NULL DEFAULT 8.00,
ADD CONSTRAINT assignments_pass_threshold_range_check
CHECK (pass_threshold >= 0 AND pass_threshold <= 10);
UPDATE assignments a
SET pass_threshold = COALESCE(
(
SELECT MAX(aa.pass_threshold)
FROM assignment_assignees aa
WHERE aa.assignment_id = a.id
),
8.00
);
-- +goose Down
ALTER TABLE assignments
DROP CONSTRAINT IF EXISTS assignments_pass_threshold_range_check,
DROP COLUMN IF EXISTS pass_threshold;

View File

@@ -0,0 +1,9 @@
-- +goose Up
ALTER TABLE assignment_assignees
ADD COLUMN pass_status_override assignment_pass_status;
-- +goose Down
ALTER TABLE assignment_assignees
DROP COLUMN IF EXISTS pass_status_override;

View File

@@ -0,0 +1,13 @@
-- +goose Up
CREATE TYPE assignment_next_step_outcome AS ENUM ('redo', 'accept', 'support');
ALTER TABLE assignment_assignees
ADD COLUMN next_step_outcome assignment_next_step_outcome;
-- +goose Down
ALTER TABLE assignment_assignees
DROP COLUMN IF EXISTS next_step_outcome;
DROP TYPE IF EXISTS assignment_next_step_outcome;

View File

@@ -0,0 +1,80 @@
-- +goose Up
UPDATE questions AS q
SET correct_answer = seeded.correct_answer
FROM (
VALUES
(1001, '700'),
(1002, '25000'),
(1003, '7/100'),
(1004, '0.37'),
(1101, '383'),
(1102, '456'),
(1103, '2627'),
(1104, '196'),
(1105, '24'),
(1106, '3744'),
(1201, '3'),
(1202, '-5'),
(1203, '-4'),
(1301, '11'),
(1302, '22'),
(1303, '19'),
(1401, '1/2'),
(1402, '2/3'),
(1403, '5/8'),
(1411, '5/6'),
(1412, '3/4'),
(1413, '11/15'),
(1414, '11/12'),
(1415, '1/2'),
(1416, '29/24'),
(1421, '1/6'),
(1422, '1/2'),
(1423, '28'),
(1501, '5'),
(1502, '7'),
(1503, '6'),
(1511, '14'),
(1512, '31'),
(1513, '3n+2'),
(1601, '28'),
(1602, '40'),
(1603, '27'),
(1611, '70'),
(1612, '75'),
(1613, '720'),
(1701, '8'),
(1702, '5'),
(1703, '7'),
(1711, '3/8'),
(1712, '1/3'),
(1801, '15'),
(1802, '3/8'),
(1803, '51'),
(1804, '23'),
(1805, '96')
) AS seeded(id, correct_answer)
WHERE q.id = seeded.id
AND (q.correct_answer IS NULL OR BTRIM(q.correct_answer) = '');
-- +goose Down
UPDATE questions
SET correct_answer = NULL
WHERE id IN (
1001, 1002, 1003, 1004,
1101, 1102, 1103, 1104, 1105, 1106,
1201, 1202, 1203,
1301, 1302, 1303,
1401, 1402, 1403,
1411, 1412, 1413, 1414, 1415, 1416,
1421, 1422, 1423,
1501, 1502, 1503,
1511, 1512, 1513,
1601, 1602, 1603,
1611, 1612, 1613,
1701, 1702, 1703,
1711, 1712,
1801, 1802, 1803, 1804, 1805
);

View File

@@ -0,0 +1,123 @@
-- +goose Up
CREATE TYPE question_topic AS ENUM (
'place_value',
'arithmetic',
'negative_numbers',
'bidmas',
'fractions',
'algebra',
'geometry',
'data'
);
CREATE TYPE question_difficulty AS ENUM ('easy', 'medium', 'hard');
ALTER TABLE questions
ADD COLUMN topic question_topic,
ADD COLUMN difficulty question_difficulty;
UPDATE questions
SET topic = CASE BTRIM(COALESCE(subject, ''))
WHEN 'Place Value' THEN 'place_value'::question_topic
WHEN 'Arithmetic' THEN 'arithmetic'::question_topic
WHEN 'Negative Numbers' THEN 'negative_numbers'::question_topic
WHEN 'BIDMAS' THEN 'bidmas'::question_topic
WHEN 'Fractions' THEN 'fractions'::question_topic
WHEN 'Algebra' THEN 'algebra'::question_topic
WHEN 'Geometry' THEN 'geometry'::question_topic
WHEN 'Data' THEN 'data'::question_topic
ELSE NULL
END
WHERE topic IS NULL;
UPDATE questions AS q
SET difficulty = seeded.difficulty::question_difficulty
FROM (
VALUES
(1001, 'easy'),
(1002, 'medium'),
(1003, 'medium'),
(1004, 'hard'),
(1101, 'easy'),
(1102, 'medium'),
(1103, 'hard'),
(1104, 'medium'),
(1105, 'medium'),
(1106, 'hard'),
(1201, 'easy'),
(1202, 'medium'),
(1203, 'hard'),
(1301, 'easy'),
(1302, 'medium'),
(1303, 'hard'),
(1401, 'easy'),
(1402, 'medium'),
(1403, 'hard'),
(1411, 'easy'),
(1412, 'easy'),
(1413, 'medium'),
(1414, 'medium'),
(1415, 'medium'),
(1416, 'hard'),
(1421, 'easy'),
(1422, 'medium'),
(1423, 'hard'),
(1501, 'easy'),
(1502, 'medium'),
(1503, 'hard'),
(1511, 'easy'),
(1512, 'medium'),
(1513, 'hard'),
(1601, 'easy'),
(1602, 'medium'),
(1603, 'hard'),
(1611, 'easy'),
(1612, 'medium'),
(1613, 'hard'),
(1701, 'easy'),
(1702, 'medium'),
(1703, 'easy'),
(1711, 'medium'),
(1712, 'hard'),
(1801, 'easy'),
(1802, 'medium'),
(1803, 'medium'),
(1804, 'hard'),
(1805, 'hard')
) AS seeded(id, difficulty)
WHERE q.id = seeded.id
AND q.difficulty IS NULL;
ALTER TABLE assignments
ALTER COLUMN pass_threshold SET DEFAULT 6.00;
UPDATE assignments
SET pass_threshold = 6.00;
ALTER TABLE assignment_assignees
ALTER COLUMN pass_threshold SET DEFAULT 6.00;
UPDATE assignment_assignees
SET pass_threshold = 6.00;
-- +goose Down
UPDATE assignment_assignees
SET pass_threshold = 8.00;
ALTER TABLE assignment_assignees
ALTER COLUMN pass_threshold SET DEFAULT 8.00;
UPDATE assignments
SET pass_threshold = 8.00;
ALTER TABLE assignments
ALTER COLUMN pass_threshold SET DEFAULT 8.00;
ALTER TABLE questions
DROP COLUMN IF EXISTS difficulty,
DROP COLUMN IF EXISTS topic;
DROP TYPE IF EXISTS question_difficulty;
DROP TYPE IF EXISTS question_topic;

View File

@@ -0,0 +1,11 @@
-- +goose Up
ALTER TABLE assignment_assignees
ADD COLUMN redo_plan TEXT,
ADD COLUMN redo_plan_generated_at TIMESTAMPTZ;
-- +goose Down
ALTER TABLE assignment_assignees
DROP COLUMN IF EXISTS redo_plan_generated_at,
DROP COLUMN IF EXISTS redo_plan;

View File

@@ -0,0 +1,30 @@
-- +goose Up
CREATE TABLE assignment_student_questions (
id BIGSERIAL PRIMARY KEY,
assignment_id BIGINT NOT NULL,
student_id BIGINT NOT NULL,
question_id BIGINT NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
position INTEGER NOT NULL CHECK (position > 0),
source_bucket TEXT NOT NULL CHECK (btrim(source_bucket) <> ''),
source_topic question_topic,
source_difficulty question_difficulty,
generator_seed BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT assignment_student_questions_assignment_student_fkey
FOREIGN KEY (assignment_id, student_id)
REFERENCES assignment_assignees(assignment_id, student_id)
ON DELETE CASCADE,
CONSTRAINT assignment_student_questions_assignment_student_question_key
UNIQUE (assignment_id, student_id, question_id),
CONSTRAINT assignment_student_questions_assignment_student_position_key
UNIQUE (assignment_id, student_id, position)
);
CREATE INDEX idx_assignment_student_questions_assignment_student
ON assignment_student_questions (assignment_id, student_id);
-- +goose Down
DROP INDEX IF EXISTS idx_assignment_student_questions_assignment_student;
DROP TABLE IF EXISTS assignment_student_questions;