-- backend/db/schema.sql -- Run once via: uv run python backend/db/apply_schema.py -- batches first (no FK to users yet — circular dependency resolved below) CREATE TABLE IF NOT EXISTS batches ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, instructor_id UUID NOT NULL, class_code TEXT NOT NULL UNIQUE DEFAULT upper(substring(gen_random_uuid()::text, 1, 8)), created_at TIMESTAMPTZ DEFAULT NOW() ); -- users references batches CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), full_name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('student', 'instructor')), batch_id UUID REFERENCES batches(id) ON DELETE SET NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); -- add FK from batches.instructor_id → users.id (deferred until users exists) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'batches_instructor_id_fkey' AND table_name = 'batches' ) THEN ALTER TABLE batches ADD CONSTRAINT batches_instructor_id_fkey FOREIGN KEY (instructor_id) REFERENCES users(id) ON DELETE CASCADE; END IF; END $$; CREATE TABLE IF NOT EXISTS refresh_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS topics ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), batch_id UUID NOT NULL REFERENCES batches(id) ON DELETE CASCADE, name TEXT NOT NULL, is_unlocked BOOLEAN NOT NULL DEFAULT FALSE, order_index INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS questions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), topic_id UUID NOT NULL REFERENCES topics(id) ON DELETE CASCADE, question_text TEXT NOT NULL, difficulty TEXT NOT NULL CHECK (difficulty IN ('easy', 'medium', 'hard')) DEFAULT 'medium', created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS interview_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), student_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, topic_id UUID NOT NULL REFERENCES topics(id) ON DELETE CASCADE, status TEXT NOT NULL CHECK (status IN ('active', 'completed')) DEFAULT 'active', score INTEGER CHECK (score >= 0 AND score <= 100), feedback JSONB, started_at TIMESTAMPTZ DEFAULT NOW(), completed_at TIMESTAMPTZ );