diff --git a/migrations/001_example.sql b/migrations/001_example.sql deleted file mode 100644 index 27bce96..0000000 --- a/migrations/001_example.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Example Migration File --- You can write raw SQL here to fix your schema mismatches --- For example, if you added a new column in models.py, you would add it here: - --- ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "trial_end" TIMESTAMP WITHOUT TIME ZONE; - --- Then run: ./migrate.sh migrations/001_example.sql diff --git a/migrations/001_full_schema_sync.sql b/migrations/001_full_schema_sync.sql new file mode 100644 index 0000000..8c51a8f --- /dev/null +++ b/migrations/001_full_schema_sync.sql @@ -0,0 +1,294 @@ +-- ============================================================================= +-- Pragativeer Full Schema Sync Migration +-- File: migrations/001_full_schema_sync.sql +-- +-- HOW TO RUN: +-- Windows: .\migrate.bat migrations\001_full_schema_sync.sql +-- Linux/Mac (server): ./migrate.sh migrations/001_full_schema_sync.sql +-- +-- This script is 100% idempotent (safe to run multiple times). +-- Uses IF NOT EXISTS everywhere so already-applied changes are silently skipped. +-- ============================================================================= + +-- ── ENUM TYPES ───────────────────────────────────────────────────────────────── +-- Note: All enum values in DB are UPPERCASE (e.g. FREE, PRO, AI_COACH, JEE_MAIN) + +DO $$ BEGIN + CREATE TYPE classenum AS ENUM ('ELEVENTH', 'TWELFTH', 'DROPPER'); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE examenum AS ENUM ('JEE_MAIN', 'JEE_ADVANCED', 'NEET', 'JEE_NEET'); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + ALTER TYPE examenum ADD VALUE IF NOT EXISTS 'JEE_NEET'; +EXCEPTION WHEN others THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE mediumenum AS ENUM ('ENGLISH', 'HINDI', 'BILINGUAL'); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE tierenum AS ENUM ('FREE', 'PRO', 'AI_COACH'); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE questiontypeenum AS ENUM ('MCQ_SINGLE', 'MCQ_MULTIPLE', 'NUMERICAL'); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE sourcetypeenum AS ENUM ('DPS', 'TEST', 'MICROTEST'); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE errortypeenum AS ENUM ('CONCEPTUAL', 'CALCULATION', 'READING', 'TIME_PRESSURE'); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + + +-- ── TABLE: user ──────────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS "user" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT UNIQUE, + phone TEXT UNIQUE, + password_hash TEXT NOT NULL, + name TEXT NOT NULL, + class classenum NOT NULL, + target_exam examenum NOT NULL, + medium mediumenum NOT NULL, + school TEXT, + city TEXT NOT NULL, + state TEXT NOT NULL, + tier tierenum NOT NULL DEFAULT 'FREE', + trial_end TIMESTAMP WITHOUT TIME ZONE, + subscription_end TIMESTAMP WITHOUT TIME ZONE, + profile_image_url TEXT, + streak_count INTEGER NOT NULL DEFAULT 0, + longest_streak INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(), + last_active TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(), + active_session_token TEXT +); + +ALTER TABLE "user" + ADD COLUMN IF NOT EXISTS trial_end TIMESTAMP WITHOUT TIME ZONE, + ADD COLUMN IF NOT EXISTS subscription_end TIMESTAMP WITHOUT TIME ZONE, + ADD COLUMN IF NOT EXISTS profile_image_url TEXT, + ADD COLUMN IF NOT EXISTS streak_count INTEGER NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS longest_streak INTEGER NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS active_session_token TEXT, + ADD COLUMN IF NOT EXISTS school TEXT; + + +-- ── TABLE: parent ───────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS parent ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT NOT NULL, + phone TEXT NOT NULL, + password_hash TEXT NOT NULL, + name TEXT NOT NULL, + language TEXT NOT NULL DEFAULT 'English', + linked_students JSONB NOT NULL DEFAULT '[]', + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now() +); + +ALTER TABLE parent + ADD COLUMN IF NOT EXISTS language TEXT NOT NULL DEFAULT 'English', + ADD COLUMN IF NOT EXISTS linked_students JSONB NOT NULL DEFAULT '[]'; + + +-- ── TABLE: question ─────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS question ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + subject TEXT NOT NULL, + topic TEXT NOT NULL, + subtopic TEXT NOT NULL, + difficulty TEXT NOT NULL, + type questiontypeenum NOT NULL, + question_text TEXT NOT NULL, + question_text_hi TEXT, + options JSONB, + options_hi JSONB, + correct_answer JSONB NOT NULL, + solution_text TEXT NOT NULL, + solution_text_hi TEXT, + language mediumenum NOT NULL, + year INTEGER, + exam examenum, + tags TEXT, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_by UUID NOT NULL, + verified_by UUID +); + +ALTER TABLE question + ADD COLUMN IF NOT EXISTS question_text_hi TEXT, + ADD COLUMN IF NOT EXISTS options_hi JSONB, + ADD COLUMN IF NOT EXISTS solution_text_hi TEXT, + ADD COLUMN IF NOT EXISTS tags TEXT, + ADD COLUMN IF NOT EXISTS verified_by UUID; + + +-- ── TABLE: dps ──────────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS dps ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + date DATE NOT NULL, + title TEXT NOT NULL, + questions JSONB NOT NULL DEFAULT '[]', + difficulty TEXT NOT NULL, + time_limit INTEGER NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_by UUID NOT NULL +); + + +-- ── TABLE: test ─────────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS test ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title TEXT NOT NULL, + type TEXT NOT NULL, + exam_pattern examenum NOT NULL, + sections JSONB NOT NULL, + total_time INTEGER NOT NULL, + difficulty TEXT NOT NULL, + scheduled_date TIMESTAMP WITHOUT TIME ZONE, + end_date TIMESTAMP WITHOUT TIME ZONE, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_by UUID NOT NULL +); + +ALTER TABLE test + ADD COLUMN IF NOT EXISTS end_date TIMESTAMP WITHOUT TIME ZONE; + + +-- ── TABLE: transaction ──────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS transaction ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES "user"(id), + amount DOUBLE PRECISION NOT NULL, + tier TEXT NOT NULL, + status TEXT NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now() +); + + +-- ── TABLE: coupon ───────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS coupon ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + code TEXT UNIQUE NOT NULL, + discount_percent DOUBLE PRECISION NOT NULL, + max_discount_amount DOUBLE PRECISION NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + expires_at TIMESTAMP WITHOUT TIME ZONE, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now() +); + + +-- ── TABLE: attempt ──────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS attempt ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES "user"(id), + source_type sourcetypeenum NOT NULL, + source_id UUID NOT NULL, + started_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + submitted_at TIMESTAMP WITHOUT TIME ZONE, + time_taken INTEGER, + answers JSONB, + score INTEGER NOT NULL DEFAULT 0, + total_questions INTEGER NOT NULL, + accuracy DOUBLE PRECISION NOT NULL DEFAULT 0.0, + status TEXT NOT NULL DEFAULT 'In_Progress', + is_retake BOOLEAN NOT NULL DEFAULT FALSE +); + +ALTER TABLE attempt + ADD COLUMN IF NOT EXISTS is_retake BOOLEAN NOT NULL DEFAULT FALSE; + + +-- ── TABLE: mistake ──────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS mistake ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES "user"(id), + question_id UUID NOT NULL REFERENCES question(id), + attempt_id UUID NOT NULL REFERENCES attempt(id), + error_type errortypeenum NOT NULL, + topic TEXT NOT NULL, + subtopic TEXT NOT NULL, + next_review TIMESTAMP WITHOUT TIME ZONE NOT NULL, + review_count INTEGER NOT NULL DEFAULT 0, + consecutive_correct INTEGER NOT NULL DEFAULT 0, + is_mastered BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now() +); + +ALTER TABLE mistake + ADD COLUMN IF NOT EXISTS consecutive_correct INTEGER NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS is_mastered BOOLEAN NOT NULL DEFAULT FALSE; + + +-- ── TABLE: studyplan ────────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS studyplan ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES "user"(id), + date DATE NOT NULL, + tasks JSONB NOT NULL, + is_completed BOOLEAN NOT NULL DEFAULT FALSE, + generated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(), + feedback TEXT +); + +ALTER TABLE studyplan + ADD COLUMN IF NOT EXISTS feedback TEXT; + + +-- ── TABLE: subscription ─────────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS subscription ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES "user"(id), + tier tierenum NOT NULL, + billing_cycle TEXT NOT NULL, + start_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + end_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + amount INTEGER NOT NULL, + payment_id TEXT NOT NULL, + status TEXT NOT NULL, + auto_renew BOOLEAN NOT NULL DEFAULT TRUE, + coupon_code TEXT +); + +ALTER TABLE subscription + ADD COLUMN IF NOT EXISTS coupon_code TEXT, + ADD COLUMN IF NOT EXISTS auto_renew BOOLEAN NOT NULL DEFAULT TRUE; + + +-- ── TABLE: systemsettings ───────────────────────────────────────────────────── + +CREATE TABLE IF NOT EXISTS systemsettings ( + id TEXT PRIMARY KEY DEFAULT 'global', + maintenance_mode BOOLEAN NOT NULL DEFAULT FALSE, + allow_new_registrations BOOLEAN NOT NULL DEFAULT TRUE, + enable_free_trial BOOLEAN NOT NULL DEFAULT TRUE, + trial_duration_days INTEGER NOT NULL DEFAULT 15, + trial_tier tierenum NOT NULL DEFAULT 'AI_COACH', + updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now() +); + +ALTER TABLE systemsettings + ADD COLUMN IF NOT EXISTS enable_free_trial BOOLEAN NOT NULL DEFAULT TRUE, + ADD COLUMN IF NOT EXISTS trial_duration_days INTEGER NOT NULL DEFAULT 15, + ADD COLUMN IF NOT EXISTS trial_tier tierenum NOT NULL DEFAULT 'AI_COACH'; + +INSERT INTO systemsettings (id, maintenance_mode, allow_new_registrations, enable_free_trial, trial_duration_days, trial_tier, updated_at) +VALUES ('global', FALSE, TRUE, TRUE, 15, 'AI_COACH', now()) +ON CONFLICT (id) DO NOTHING;