🏥 Platform Health
Real-time health metrics across the platform.
Recent Submissions
Pending Teacher Approvals
Top Teachers (By Submissions)
Submissions (Last 14 Days)
Daily submission volume platform-wide.
Platform Score Distribution
Students by score band across all exams.
📢 Platform Announcement
Current notice visible to all users.
Pending Teacher Approvals
New teachers awaiting your confirmation before they can access the platform.
All Teachers
Manage accounts, roles, and access.
All Exams — Platform Wide
Every assessment created by all teachers.
| Subject | Teacher | Class | Term | Type | Status | Questions | Created | Actions |
|---|
All Results — Platform Wide
Every student submission across all teachers.
| Student | Class | Subject | Teacher | Score | % | Mode | Integrity | Time | Date |
|---|
Platform Activity Log
🛡️ Security & Deployment Centre
Free browser-based checks for admin readiness, RLS/RPC availability, static deployment assets, and export tools. No paid AI API.
This section helps the school owner/admin verify that the platform is deployed safely. It checks whether admin session exists, RPC data loaded, HTTPS is active, service-role keys are not exposed, required PWA/static files exist, and whether platform records are accessible. It also provides downloadable operational checklists.
RLS-firstNo service_role keyFree hosting readyNo AI API⚙️ Complete CBT Pro Setup Guide
This guide contains every SQL statement needed to set up CBT Pro from scratch on any Supabase project. Follow every step in order. Each step explains what it does and why.
1. Go to supabase.com and open your project
2. Click SQL Editor in the left sidebar
3. Click New Query
4. Paste the SQL block and click Run
5. A green "Success" message confirms it worked
6. Run each step below in order — do not skip any step
Step 2 — Create the results table
Step 3 — Create the profiles table (teacher accounts)
Step 4 — Create the students table (class rosters)
Step 5 — Enable Row Level Security on all tables
Step 6 — RLS policies for exams (data isolation per teacher)
Step 7 — RLS policies for results (data isolation per teacher)
Step 8 — RLS policies for profiles
Step 9 — RLS policies for students
Step 10 — Grant permissions so the trigger can write profiles
Step 11 — Auto-create pending profile on teacher signup (FIXED trigger)
Step 12 — Admin RPC functions (admin sees all teachers and data)
Step 13 — Migrate existing users (if platform already has teachers)
Step 14 — Set up the admin account
Step 15 — Disable email confirmation for instant teacher access
Step 16 — Verify everything is working
This table stores every exam created by every teacher. Each row is one exam.
• teacher_id — links each exam to the teacher who created it. Used by RLS to isolate data.
• subject — stores all metadata (subject, class, term, topic, type, session, passmark) as a pipe-separated string.
• csv_data — stores the entire question bank as JSON inside the database.
• exam_mode — either 'open' (anyone can sit) or 'registered' (verified students only).
• close_at — optional scheduled auto-close date and time.
• is_open — controls whether students can currently access the exam.
This table stores every student submission. Each row is one student's completed exam attempt.
• exam_id — links the result to its exam. If the exam is deleted, results are also deleted (CASCADE).
• student_id_ref — the school ID used by registered-mode students to verify identity.
• student_type — either 'open' (anonymous) or 'registered' (identity verified).
• answers_data — JSON storing each question's answer, time spent, and flag status.
• violations and violation_log — anti-cheat integrity data recorded during the exam.
This table manages teacher accounts. Every teacher has exactly one row here.
• status controls login access: 'pending' (awaiting admin approval), 'active' (can log in), 'inactive' (suspended).
• role is either 'teacher' or 'admin'.
When a teacher signs up, the trigger in Step 11 automatically creates a pending row here.
The admin must approve from the Pending Approvals page before the teacher can access the dashboard.
This table stores each teacher's class roster for registered-mode exams.
• student_id — the school ID students enter to verify their identity (e.g. SS/2024/001).
• UNIQUE (teacher_id, student_id) — prevents duplicate student IDs within the same teacher's roster.
Students are not Supabase users — they are just records that the teacher manages.
Row Level Security is PostgreSQL's built-in data isolation system. When enabled, Supabase automatically enforces that each user can only access rows they are permitted to see — even if someone sends a direct API request.
This is the most important step. Without it, every teacher sees every other teacher's exams, results, and students.
Run this block to enable RLS on all four tables at once.
These four policies enforce that each teacher can only read, create, edit, and delete their own exams.
The condition auth.uid() = teacher_id means Supabase checks that the logged-in teacher's user ID matches the exam's teacher_id column on every single request — automatically, without any JavaScript needed.
A result belongs to a teacher if it was submitted to an exam that teacher created.
The sub-query (SELECT teacher_id FROM exams WHERE id = exam_id) traces from the result back to its exam to find the owner.
The student INSERT policy uses WITH CHECK (true) so anonymous students (who are not logged in) can still submit their results — this is essential for the exam to function.
Each teacher can only read and update their own profile row. The INSERT policy is open so the auto-signup trigger (Step 11) can create profile rows during signup. Admins read ALL profiles through the special RPC functions in Step 12 — not through these policies.
Each teacher can only read, add, and delete students from their own roster. The last policy allows anonymous users (students sitting a registered-mode exam) to look up their student ID for identity verification — without this, registered-mode exams cannot work.
This step grants the postgres and service_role users full access to the profiles table.
Without this, the auto-signup trigger in Step 11 will fail with a "database error" when a new teacher tries to create an account — even though the trigger is correctly written.
This step is why signups were failing. Run this before creating the trigger.
This is a PostgreSQL trigger — a function that runs automatically inside the database every time a new user is created in Supabase Auth.
When a teacher clicks "Create Account", Supabase creates their auth record, and this trigger immediately creates a profile row with status = 'pending'.
The teacher sees "Awaiting Approval" and cannot access the dashboard until the admin approves them.
Key details of this version:
• SET search_path = public — ensures the trigger always finds the profiles table regardless of schema context.
• EXCEPTION WHEN others — if the profile insert fails for any reason, the signup still succeeds. The teacher gets a pending profile on their first login attempt instead.
• SECURITY DEFINER — the function runs with the privileges of its creator (postgres), not the signing-up user.
These are special database functions that allow the admin panel to read and modify ALL data across ALL teachers — bypassing RLS safely.
Without these functions, the admin panel shows zero teachers, zero exams, and zero results because normal RLS blocks cross-teacher access.
• SECURITY DEFINER — these functions execute with database owner privileges, not the caller's.
• The SELECT functions let the admin read everything. The UPDATE/DELETE functions let the admin approve, suspend, promote, or remove teachers.
Run the entire block as a single query.
These three columns store the correct, wrong, and skipped question counts as computed by the student's browser at exam submission time.
They are the authoritative source of truth — the teacher dashboard reads these directly rather than re-computing from answers_data.
Re-computing from the current question bank can give wrong results if questions were edited after students submitted.
Run this if your teacher dashboard shows different wrong counts than students see on their result screen.
Skip this step if you are setting up a brand new platform with no existing users.
If you already have teachers registered BEFORE the profiles table was created, they have no profile row and will see "Platform setup incomplete" when they try to log in.
Run this SQL once to create active profile rows for all existing auth users.
Before running: change buildingmyictcareer@gmail.com to your actual admin email address — that account will be set to admin role, everyone else becomes an active teacher.
This ensures your admin account has the correct role and active status in the profiles table.
How to get your UUID: Go to Supabase Dashboard → Authentication → Users, find your admin email in the list, and copy the UUID shown next to it.
Paste your UUID in place of YOUR-UUID-HERE below, update the email, then run the query.
By default, Supabase requires new users to click a confirmation link in their email before their account is active.
For a school platform where the admin manually approves every teacher anyway, this extra step is unnecessary and can cause confusion.
This is NOT SQL — do it in the Supabase Dashboard:
1. Go to Supabase Dashboard → Authentication → Providers
2. Click on Email
3. Toggle OFF the "Confirm email" switch
4. Click Save
After this, new teachers who sign up will immediately see "Awaiting Approval" instead of a verification email, and the admin approves them from this panel.
Run these queries to confirm all tables exist, RLS is enabled on all of them, and your profiles are correctly set up.
• The first query should return 4 rows — one for each table — all with rowsecurity = true.
• The second query shows all profiles with their roles and statuses.
• The third query confirms the trigger is attached.
When uploading questions via CSV, your file must follow this exact column order. Row 1 is treated as the header and is automatically skipped. The Explanation column is optional but recommended — it helps students learn from their mistakes.
When importing a class roster on the Students page, the CSV must follow this format. Row 1 is the header and is automatically skipped. Duplicate Student IDs are silently skipped.