teaching-python/
├── index.html ← Full app — never hardcode keys here
├── manifest.json ← Registry of test files
├── vercel.json ← Vercel routing + JSON headers
├── .env.example ← Template for local dev (safe to commit)
├── .gitignore ← Keeps .env.local out of git
├── api/
│ └── config.js ← Serverless function: serves keys from env vars
└── tests/
├── test1.json
├── test2.json
├── test3.json
├── test4.json
├── test5.json
└── test6.json
Vercel Dashboard
→ SUPABASE_URL & SUPABASE_ANON_KEY (set once, never in code)
↓
api/config.js (serverless function)
↓ GET /api/config
index.html (browser fetches this on load, initialises Supabase)
Your keys are never in your repo, never in browser source. The Supabase anon key is safe to expose to browsers, but this pattern is best practice and lets you rotate keys without touching code.
-
Go to https://supabase.com → New project → name it
pcep-quiz -
Wait ~2 min for provisioning
-
SQL Editor → New query → paste and run
sql_migration.sql(included in the repo). This is the single source of truth for all three tables (test_results,pcep_progress,pcep_quiz_log) — safe to run on a brand-new project or re-run on an existing one, since it guards every statement withCREATE TABLE IF NOT EXISTS/ADD COLUMN IF NOT EXISTS/DROP POLICY IF EXISTS.Quick version of just the
test_resultsportion:
CREATE TABLE test_results (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL,
email TEXT,
test_id INTEGER NOT NULL,
score INTEGER NOT NULL,
total INTEGER NOT NULL,
percentage INTEGER NOT NULL,
passed BOOLEAN NOT NULL DEFAULT false,
answers JSONB,
completed_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(user_id, test_id)
);
ALTER TABLE test_results ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read own" ON test_results FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "insert own" ON test_results FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "update own" ON test_results FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "delete own" ON test_results FOR DELETE USING (auth.uid() = user_id);- Settings → API → copy:
- Project URL →
https://xxxxx.supabase.co - anon public key →
eyJhbGci...
- Project URL →
- https://console.cloud.google.com → New project
- APIs & Services → OAuth consent screen → External → fill app name
- APIs & Services → Credentials → Create → OAuth Client ID → Web application
- Authorised redirect URIs — add (you'll get the exact ref from Supabase):
https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback - Copy Client ID and Client Secret
- Supabase → Authentication → Providers → Google → Enable → paste both
- https://vercel.com → New Project → Import Git Repository
- Select
AutoShiftOps/teaching-python - Framework preset: Other
- Root directory:
/(leave as default) - Click Deploy — it will fail on first deploy because env vars aren't set yet, that's fine
Vercel → Project → Settings → Environment Variables → add both:
| Name | Value | Environments |
|---|---|---|
SUPABASE_URL |
https://xxxxx.supabase.co |
Production, Preview |
SUPABASE_ANON_KEY |
eyJhbGci... |
Production, Preview |
APP_URL |
https://teaching-python.vercel.app |
Production, Preview |
APP_URLis used for Google OAuth redirect. Must exactly match the URL you add to Google OAuth origins and Supabase redirect URLs.
If Google OAuth is already working in production without
APP_URLset, the fallback (req.headers.origin) is handling it.APP_URLis optional but recommended for explicitness.
Then: Deployments → top deployment → Redeploy
https://teaching-python.vercel.app
(or a custom domain you configure)
Google Cloud Console → Credentials → your OAuth Client → edit:
- Authorised JavaScript origins:
https://teaching-python.vercel.app - Authorised redirect URIs:
https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback
Supabase → Authentication → URL Configuration:
- Site URL:
https://teaching-python.vercel.app - Redirect URLs:
https://teaching-python.vercel.app
- Open
https://teaching-python.vercel.app - Open browser DevTools → Network → look for
/api/config→ should return 200 with JSON - Click "Continue with Google" → sign in → redirected to dashboard
- Take a test → finish → check Supabase Table Editor → row should appear in
test_results - Refresh page → results should persist
- Click "Reset all progress" → row deleted from Supabase
# Install Vercel CLI
npm i -g vercel
# Clone and enter repo
git clone https://github.com/AutoShiftOps/teaching-python.git
cd teaching-python
# Create local env file (gitignored)
cp .env.example .env.local
# Edit .env.local → paste your actual Supabase URL and anon key
# Run locally (serves index.html + api/config.js with env vars)
vercel dev
# Opens at http://localhost:3000| Task | What to do |
|---|---|
| Edit a question | Open tests/testN.json, change the text/options/answer, commit + push |
| Add a question | Add object to questions[] array in the right JSON file, commit + push |
| Add a new test | Create tests/test7.json, add path to manifest.json, commit + push |
| Remove a question | Delete the object from the array, commit + push |
Vercel auto-deploys on every push to main. Changes are live in ~30 seconds.
| Symptom | Fix |
|---|---|
/api/config returns 500 |
Check env vars are set in Vercel dashboard and redeployed |
| Google sign-in redirect error | Confirm Vercel URL is in Google OAuth origins AND Supabase redirect URLs |
| Tests show "could not load" | Check manifest.json paths match actual filenames in tests/ |
| Results don't save | Check Supabase RLS policies were created (Step 1 SQL) |
| Works locally but not on Vercel | Env vars not set — check Vercel → Settings → Environment Variables |
GitHub Pages cannot run serverless functions (api/config.js won't work there).
It also runs Jekyll which interferes with JSON file serving.
Vercel is the right host for this app.
If you still want the repo listed under autoshiftops.com projects, that still works — autoshiftops.com just links to it. The actual app runs on Vercel.
sql_migration.sql (run once in Step 1) already creates both practice tables
alongside test_results:
pcep_progress— one row per user (keyed byuser_id), tracks streak / best scores / weak areaspcep_quiz_log— one row per attempt (exam and practice), with RLS policies
No separate file or second run is needed — Step 1's migration covers all three
tables in one pass. Verify in Table Editor — you should see all three tables:
test_results, pcep_progress, pcep_quiz_log.
- Sign in with Google OAuth
- Complete a practice drill → check
pcep_quiz_logfor a new row withmode = 'practice' - Complete an exam → check
test_resultsandpcep_quiz_logfor rows - Check
pcep_progressfor streak and weak_areas updates - Click "Reset all progress" → confirm all three tables cleared for that user
In index.html, find buildPracticeTest and change:
// Before
name: sourceTest.name // shows "Test 1 — Python Fundamentals"
// After
name: pt.label // shows "Fundamentals"One line. Commit message: fix: practice results subtitle uses topic label not exam name
| Item | Status | Notes |
|---|---|---|
| Vercel deployment | ✅ Live | teaching-python.vercel.app |
| Google OAuth | ✅ Working | Verified in browser |
| Supabase tables + RLS | ✅ Done | All 3 tables with user_id + RLS |
| Practice mode (UI) | ✅ Live | 5 topic drills + 6 fixed exams |
| Tier 2 (real user data) | ⏳ Pending | Run after first real login |
| sql_migration.sql | ✅ In repo | Use for recreation |