408 lines
20 KiB
SQL
408 lines
20 KiB
SQL
-- =====================================================
|
|
-- Supabase migration v2: Knex SQLite -> PostgreSQL
|
|
-- Knex and the authoritative cloud schema both use UUID tenant identifiers.
|
|
-- Knex users permits dining_staff; the current cloud users role CHECK does not.
|
|
-- Existing CREATE TABLE IF NOT EXISTS definitions are not rewritten because that
|
|
-- would not alter existing cloud columns; only Knex tables missing from cloud are created.
|
|
-- =====================================================
|
|
|
|
-- =====================================================
|
|
-- 0. PREREQUISITES
|
|
-- =====================================================
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
-- =====================================================
|
|
-- 1. TENANTS (root, no RLS policy needs to be added yet)
|
|
-- =====================================================
|
|
CREATE TABLE IF NOT EXISTS tenants (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
school_name TEXT NOT NULL,
|
|
logo_url TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
);
|
|
|
|
-- =====================================================
|
|
-- 2. TABLES THAT ARE IN Knex BUT NOT YET IN SUPABASE
|
|
-- (CREATE TABLE IF NOT EXISTS — safe to re-run)
|
|
-- =====================================================
|
|
CREATE TABLE IF NOT EXISTS tenant_licenses (
|
|
id TEXT PRIMARY KEY,
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
license_key TEXT UNIQUE NOT NULL,
|
|
term_name TEXT NOT NULL,
|
|
academic_year TEXT NOT NULL,
|
|
start_date DATE NOT NULL,
|
|
end_date DATE NOT NULL,
|
|
grace_period_days INTEGER DEFAULT 7,
|
|
max_students INTEGER DEFAULT 1000,
|
|
max_staff INTEGER DEFAULT 200,
|
|
status TEXT DEFAULT 'active',
|
|
override_reason TEXT,
|
|
issued_by TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS school_settings (
|
|
key TEXT PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
value TEXT,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS homework (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
subject_id INTEGER NOT NULL REFERENCES subjects(id),
|
|
class_id INTEGER REFERENCES classes(id),
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
instructions TEXT,
|
|
due_date TIMESTAMP WITH TIME ZONE,
|
|
max_score REAL DEFAULT 100,
|
|
allow_late_submission INTEGER DEFAULT 0,
|
|
is_published INTEGER DEFAULT 0,
|
|
status TEXT DEFAULT 'draft' CHECK(status IN ('draft','published','closed')),
|
|
created_by INTEGER REFERENCES users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS homework_submissions (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
homework_id BIGINT NOT NULL REFERENCES homework(id),
|
|
student_id INTEGER NOT NULL REFERENCES users(id),
|
|
content TEXT,
|
|
submitted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
status TEXT DEFAULT 'submitted' CHECK(status IN ('submitted','graded','returned','late')),
|
|
grade REAL,
|
|
feedback TEXT,
|
|
graded_by INTEGER REFERENCES users(id),
|
|
graded_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0,
|
|
UNIQUE(homework_id, student_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS attachments (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
parent_kind TEXT NOT NULL CHECK(parent_kind IN ('subject','assignment','homework','test','exam_paper','submission','marked_script')),
|
|
parent_id INTEGER NOT NULL,
|
|
original_filename TEXT NOT NULL,
|
|
stored_filename TEXT NOT NULL,
|
|
mime_type TEXT NOT NULL,
|
|
size_bytes INTEGER NOT NULL,
|
|
uploaded_by INTEGER REFERENCES users(id),
|
|
uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
caption TEXT,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tests (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
subject_id INTEGER NOT NULL REFERENCES subjects(id),
|
|
class_id INTEGER REFERENCES classes(id),
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
instructions TEXT,
|
|
test_date TIMESTAMP WITH TIME ZONE,
|
|
duration_minutes INTEGER,
|
|
max_score REAL DEFAULT 100,
|
|
allow_late_submission INTEGER DEFAULT 0,
|
|
is_published INTEGER DEFAULT 0,
|
|
status TEXT DEFAULT 'draft' CHECK(status IN ('draft','published','closed')),
|
|
created_by INTEGER REFERENCES users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS test_submissions (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
test_id BIGINT NOT NULL REFERENCES tests(id),
|
|
student_id INTEGER NOT NULL REFERENCES users(id),
|
|
content TEXT,
|
|
submitted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
status TEXT DEFAULT 'submitted' CHECK(status IN ('submitted','graded','returned','late')),
|
|
grade REAL,
|
|
feedback TEXT,
|
|
graded_by INTEGER REFERENCES users(id),
|
|
graded_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0,
|
|
UNIQUE(test_id, student_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
conversation_id TEXT,
|
|
sender_id INTEGER REFERENCES users(id),
|
|
recipient_id INTEGER REFERENCES users(id),
|
|
message TEXT,
|
|
read_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending',
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS student_medical_profiles (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
student_id INTEGER NOT NULL UNIQUE REFERENCES users(id),
|
|
blood_group TEXT CHECK(blood_group IN ('A+','A-','B+','B-','AB+','AB-','O+','O-','Unknown')),
|
|
allergies TEXT,
|
|
dietary_restrictions TEXT,
|
|
contraindications TEXT,
|
|
chronic_conditions TEXT,
|
|
emergency_instructions TEXT,
|
|
updated_by INTEGER REFERENCES users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS student_medication_logs (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
student_id INTEGER NOT NULL REFERENCES users(id),
|
|
medication_name TEXT NOT NULL,
|
|
dosage TEXT NOT NULL,
|
|
administered_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
administered_by INTEGER NOT NULL REFERENCES users(id),
|
|
reason TEXT,
|
|
notes TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS student_medical_history (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
student_id INTEGER NOT NULL REFERENCES users(id),
|
|
event_type TEXT NOT NULL CHECK(event_type IN ('illness','injury','hospitalization')),
|
|
description TEXT NOT NULL,
|
|
severity TEXT DEFAULT 'moderate' CHECK(severity IN ('mild','moderate','severe')),
|
|
onset_date DATE,
|
|
resolution_date DATE,
|
|
treatment_details TEXT,
|
|
doctor_notes TEXT,
|
|
is_visible_to_teachers INTEGER DEFAULT 1,
|
|
reported_by INTEGER REFERENCES users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS education_level_configs (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
level TEXT NOT NULL CHECK(level IN ('primary','secondary','high_school','tertiary')),
|
|
grading_scale TEXT,
|
|
display_name TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
is_active INTEGER DEFAULT 1,
|
|
is_default INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS exam_review_audit (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
exam_group_id INTEGER NOT NULL REFERENCES exam_groups(id) ON DELETE CASCADE,
|
|
schedule_id INTEGER REFERENCES exam_schedules(id) ON DELETE SET NULL,
|
|
action TEXT NOT NULL CHECK(action IN ('submit','reject','publish')),
|
|
actor_id INTEGER NOT NULL REFERENCES users(id),
|
|
reason TEXT,
|
|
metadata TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_exam_review_audit_group ON exam_review_audit(exam_group_id, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
uid TEXT UNIQUE,
|
|
recipient_id INTEGER NOT NULL REFERENCES users(id),
|
|
type TEXT NOT NULL CHECK(type IN ('in_app','email','sms')),
|
|
subject TEXT,
|
|
body TEXT,
|
|
related_resource TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','sent','failed','read')),
|
|
sent_at TIMESTAMP WITH TIME ZONE,
|
|
read_at TIMESTAMP WITH TIME ZONE,
|
|
error_message TEXT,
|
|
metadata TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
last_synced_at TIMESTAMP WITH TIME ZONE,
|
|
sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')),
|
|
is_deleted INTEGER DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_notifications_recipient_unread ON notifications(recipient_id, status, created_at DESC) WHERE is_deleted = 0;
|
|
|
|
ALTER TABLE lesson_plans ADD COLUMN IF NOT EXISTS meet_url TEXT;
|
|
ALTER TABLE lesson_plans ADD COLUMN IF NOT EXISTS classroom_url TEXT;
|
|
ALTER TABLE student_fees ADD COLUMN IF NOT EXISTS plan_id INTEGER REFERENCES fee_plans(id);
|
|
ALTER TABLE student_fees ADD COLUMN IF NOT EXISTS applied_by INTEGER REFERENCES users(id);
|
|
ALTER TABLE student_fees ADD COLUMN IF NOT EXISTS academic_year TEXT;
|
|
ALTER TABLE exam_groups ADD COLUMN IF NOT EXISTS status TEXT DEFAULT 'approved';
|
|
ALTER TABLE exam_groups ADD COLUMN IF NOT EXISTS rejection_reason TEXT;
|
|
ALTER TABLE exam_groups ADD COLUMN IF NOT EXISTS reviewed_at TIMESTAMP WITH TIME ZONE;
|
|
ALTER TABLE exam_groups ADD COLUMN IF NOT EXISTS reviewed_by INTEGER REFERENCES users(id);
|
|
ALTER TABLE grades ADD COLUMN IF NOT EXISTS grading_scale TEXT;
|
|
ALTER TABLE exam_groups ADD COLUMN IF NOT EXISTS grading_scale TEXT;
|
|
ALTER TABLE course_grades ADD COLUMN IF NOT EXISTS grading_scale TEXT;
|
|
|
|
-- =====================================================
|
|
-- 3. tenant_id INJECTION (idempotent ALTER ADD COLUMN IF NOT EXISTS)
|
|
-- =====================================================
|
|
DO $$
|
|
DECLARE
|
|
t_name TEXT;
|
|
domain_tables TEXT[] := ARRAY[
|
|
'users','departments','classes','subjects','subjects_new','courses','enrollments','course_enrolments','attendance','fee_groups','student_fees','payments','messages','events','expenses','grades','course_grades','assignments','submissions','homework','homework_submissions','attachments','tests','test_submissions','settings','system_settings','school_settings','audit_logs','staff_roles','salary_grades','staff_records','vacancies','applicants','leave_types','leave_requests','staff_attendance','payroll_runs','payslips','item_categories','suppliers','store_locations','items','item_stock','stock_transactions','item_issues','exam_groups','question_banks','exam_schedules','exam_attempts','exam_answers','exam_review_audit','notifications','hostels','room_types','rooms','room_assignments','hostel_fees','vehicles','routes','pickup_points','vehicle_routes','transport_allocations','admission_enquiries','visitor_logs','phone_call_logs','postal_dispatch','complaints','clubs','club_memberships','club_announcements','club_attendance','club_chats','chat_groups','chat_group_members','chat_messages','hostel_attendance','transport_attendance','parent_students','syllabuses','schemes_of_work','focus_points','lesson_plans','student_social_docs','student_medical_profiles','student_medication_logs','student_medical_history','crossword_games','crossword_clues','crossword_sessions','student_groups','student_group_members','chart_of_accounts','bank_accounts','bank_reconciliations','bank_reconciliation_lines','invoices','trips','trip_payments','fee_plans','fee_plan_installments','discounts','student_fee_discounts','library_books','library_issues','library_reservations','library_fines','education_level_configs','tenant_licenses'
|
|
];
|
|
BEGIN
|
|
FOREACH t_name IN ARRAY domain_tables LOOP
|
|
IF to_regclass(format('public.%I', t_name)) IS NOT NULL THEN
|
|
EXECUTE format('ALTER TABLE public.%I ADD COLUMN IF NOT EXISTS tenant_id UUID REFERENCES public.tenants(id) ON DELETE CASCADE', t_name);
|
|
END IF;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- =====================================================
|
|
-- 4. INDEXES on tenant_id
|
|
-- =====================================================
|
|
DO $$
|
|
DECLARE
|
|
r RECORD;
|
|
BEGIN
|
|
FOR r IN
|
|
SELECT c.table_name
|
|
FROM information_schema.columns c
|
|
WHERE c.table_schema = 'public' AND c.column_name = 'tenant_id' AND c.table_name <> 'tenants'
|
|
LOOP
|
|
EXECUTE format('CREATE INDEX IF NOT EXISTS %I ON public.%I(tenant_id)', 'idx_' || r.table_name || '_tenant_id', r.table_name);
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- =====================================================
|
|
-- 5. RLS ENABLE + POLICIES
|
|
-- =====================================================
|
|
DO $$
|
|
DECLARE
|
|
r RECORD;
|
|
tenant_expr TEXT := '(tenant_id = ((auth.jwt() -> ''user_metadata'' ->> ''tenant_id'')::uuid) OR auth.jwt() ->> ''role'' = ''super_admin'' OR current_setting(''app.is_super_admin'', true) = ''true'')';
|
|
BEGIN
|
|
FOR r IN
|
|
SELECT c.table_name
|
|
FROM information_schema.columns c
|
|
WHERE c.table_schema = 'public' AND c.column_name = 'tenant_id'
|
|
AND c.table_name NOT IN ('tenants','sync_logs','sync_metadata','sync_config')
|
|
LOOP
|
|
EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', r.table_name);
|
|
EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', 'tenant_isolation_' || r.table_name, r.table_name);
|
|
EXECUTE format('CREATE POLICY %I ON public.%I FOR ALL TO authenticated USING (%s) WITH CHECK (%s)', 'tenant_isolation_' || r.table_name, r.table_name, tenant_expr, tenant_expr);
|
|
EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', 'service_role_full_access_' || r.table_name, r.table_name);
|
|
EXECUTE format('CREATE POLICY %I ON public.%I FOR ALL TO service_role USING (true) WITH CHECK (true)', 'service_role_full_access_' || r.table_name, r.table_name);
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- =====================================================
|
|
-- 6. STORAGE POLICIES (mirror supabase_migration.md §4)
|
|
-- =====================================================
|
|
DO $$
|
|
BEGIN
|
|
IF to_regclass('storage.buckets') IS NOT NULL THEN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='storage' AND tablename='buckets' AND policyname='Allow authenticated bucket management') THEN
|
|
CREATE POLICY "Allow authenticated bucket management" ON storage.buckets FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='storage' AND tablename='buckets' AND policyname='Allow public bucket read') THEN
|
|
CREATE POLICY "Allow public bucket read" ON storage.buckets FOR SELECT TO anon, authenticated USING (true);
|
|
END IF;
|
|
END IF;
|
|
IF to_regclass('storage.objects') IS NOT NULL THEN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='storage' AND tablename='objects' AND policyname='Allow authenticated object management') THEN
|
|
CREATE POLICY "Allow authenticated object management" ON storage.objects FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='storage' AND tablename='objects' AND policyname='Allow public object read') THEN
|
|
CREATE POLICY "Allow public object read" ON storage.objects FOR SELECT TO anon, authenticated USING (true);
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- =====================================================
|
|
-- 7. DROP LIST — UNCOMMENT BEFORE RUNNING
|
|
-- Each line tagged with its FK risk; the user must
|
|
-- verify before uncommenting.
|
|
-- =====================================================
|
|
-- Canonical tables are subjects, attendance, messages, and events.
|
|
-- subjects_new is NOT in this list: Knex explicitly creates it and cloud courses,
|
|
-- crossword_games, schemes_of_work, lesson_plans, syllabuses, and edutainment tables reference it.
|
|
-- attendance_new: referenced only by no Knex-defined table; its own FKs target users/subjects_new.
|
|
-- DROP TABLE IF EXISTS attendance_new CASCADE;
|
|
-- messages_new: referenced only by no Knex-defined table; its own FKs target users.
|
|
-- DROP TABLE IF EXISTS messages_new CASCADE;
|
|
-- calendar_events: referenced only by no Knex-defined table; its own FK targets users.
|
|
-- DROP TABLE IF EXISTS calendar_events CASCADE;
|
|
-- assignment_submissions: overlaps canonical submissions; its own FKs target assignments/users.
|
|
-- DROP TABLE IF EXISTS assignment_submissions CASCADE;
|
|
-- edutainment_content: no Knex-defined inbound FK; its own FKs target subjects_new/users.
|
|
-- DROP TABLE IF EXISTS edutainment_content CASCADE;
|
|
-- edutainment_sessions must be dropped before/with edutainment_games; it references edutainment_games/users.
|
|
-- DROP TABLE IF EXISTS edutainment_sessions CASCADE;
|
|
-- edutainment_games is referenced by edutainment_sessions; CASCADE removes that FK.
|
|
-- DROP TABLE IF EXISTS edutainment_games CASCADE;
|
|
|
|
-- =====================================================
|
|
-- 8. SEED test tenant
|
|
-- =====================================================
|
|
INSERT INTO tenants (id, school_name)
|
|
VALUES ('5b39bcb5-506a-456d-b7cb-91730b397cc1', 'Default School Tenant')
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
INSERT INTO education_level_configs (uid, level, grading_scale, display_name, sort_order, is_active, is_default, tenant_id)
|
|
VALUES
|
|
('uid-elc-primary','primary','zimsec_primary','Primary',10,1,0,'5b39bcb5-506a-456d-b7cb-91730b397cc1'),
|
|
('uid-elc-secondary','secondary','zimsec_olevel','Secondary (O-Level)',20,1,1,'5b39bcb5-506a-456d-b7cb-91730b397cc1'),
|
|
('uid-elc-highschool','high_school','zimsec_alevel','High School (A-Level)',30,1,0,'5b39bcb5-506a-456d-b7cb-91730b397cc1'),
|
|
('uid-elc-tertiary','tertiary','cambridge_alevel','Tertiary',40,1,0,'5b39bcb5-506a-456d-b7cb-91730b397cc1')
|
|
ON CONFLICT (uid) DO NOTHING;
|