Technical Reference
Complete technical overview of the Rising with Melodies platform โ two systems, two databases, and how they connect.
The platform consists of two independent systems hosted on risingwithmelodies.org (Porkbun). Both are built with vanilla JavaScript and Vite, with no backend server โ all logic runs in the browser and communicates directly with Supabase.
concert.html โ Past-event recap page with a donate CTA (no longer a registration form as of July 2026 โ the June 6 concert has passed)admin.html โ Admin panel with 6 tabssrc/concert-init.js โ Registration/payment logic (Supabase, EmailJS, SMS, Mailchimp). No longer loaded by any page since concert.html's registration form was retired โ kept in the repo, unused, in case registration is ever reintroduced for a future eventsrc/db.js โ All concert Supabase queriessrc/admin.js โ Admin panel logicsrc/config.js โ Concert API credentials| Service | Purpose | Key |
|---|---|---|
| Supabase | Database + Auth | ululnaedwrheymybbzue |
| PayPal | Online payments | Client ID in config.js |
| Zelle | Manual payments | risingwithmelodies@gmail.com |
| EmailJS | Confirmation emails | service_toeljfq |
| Twilio SMS | Admin alerts via Edge Function | Supabase Edge Function /sms |
| Mailchimp | Email campaigns | Supabase Edge Function /mailchimp |
| Formspree | Registration backup | mjgpbrpw |
| Table | Description |
|---|---|
events | Concert event definitions (date, venue, capacity per slot) |
registrations | All registrations โ performers, donors, volunteers |
performers | Performer + piece details linked to a registration |
slot_capacity | Per-slot capacity tracking (11 AM / 12 PM) |
inventory | Equipment and supplies managed in the admin |
template_ir6wkd9 โ Performer confirmation (includes performers list, time slot, amount paid)template_goj792l โ Donor & Volunteer confirmation (registration_type, donation_amount, volunteer_interest)| School | Type | Location | Currency |
|---|---|---|---|
| RWM-US | For-Profit | Alpharetta, GA | USD |
| RWM-PE | Non-Profit | Trujillo, Peru | USD / PEN |
All school tables have a school_id foreign key. The active school is stored in localStorage as rwm_school_id and persists across pages.
| Page | Module | JS File |
|---|---|---|
| school/index.html | Dashboard Analytics | school-app.js |
| school/students.html | Students | school-students.js |
| school/programs.html | Programs | school-programs.js |
| school/staff.html | Staff | school-staff.js |
| school/enrollments.html | Enrollments | school-enrollments.js |
| school/attendance.html | Attendance | school-attendance.js |
| school/gradebook.html | Gradebook | school-gradebook.js |
| school/billing.html | Billing | school-billing.js |
| school/scholarships.html | Scholarships & Donors | school-scholarships.js |
| school/inventory.html | Inventory | school-inventory.js |
| school/communications.html | Communications | school-communications.js |
| school/volunteers.html | Volunteers | school-volunteers.js |
| school/admin.html (ESL Classes section) | ESL admin โ students, sessions, attendance, messaging | school-esl.js |
| school/esl-portal.html | ESL student portal (PIN login, no Supabase Auth) | school-esl-portal.js |
Note: school/students.html, staff.html, enrollments.html, etc. above are legacy standalone pages โ most were converted to tiny redirect stubs (location.replace('admin.html#<section>')) after their original login forms were found to be non-functional. The actual UI lives in the school/admin.html single-page app.
When a Donor or Volunteer registers on the concert page and completes their submission, their data is automatically saved to the school Supabase (RWM-PE non-profit):
donors table in school DBvolunteers table in school DBThis uses a secondary Supabase client in concert-init.js with persistSession: false (no auth required โ public INSERT policies on both tables).
| Key | System | Purpose |
|---|---|---|
rwm_sandbox_mode | Concert | Sandbox/Live toggle for concert site |
rwm_sms_enabled | Concert | Enable/disable Twilio SMS notifications |
rwm_school_id | School | Currently selected school ID |
rwm_school_sandbox | School | Sandbox/Live toggle for school admin |
rwm_program_order | Concert | Locked concert program performer order |
rwm_checklist | Docs | Build checklist checkbox state |
esl_portal_session | ESL Portal | {login_code, pin_hash, expiry} โ silent re-login for 7 days |
esl_portal_lockout | ESL Portal | Failed login attempt count + lockout timestamp |
esl_portal_lang | ESL Portal | EN/ES language preference |
esl_portal_last_seen_<student_id> | ESL Portal | Timestamp used to compute the "N new" unread message count |
The ESL platform lives inside the school Supabase project and adds three layers on top of the existing admin system: a teacher-facing admin section, a student-facing PIN portal, and a shared messaging thread between them. Full history is in docs/esl-platform-plan.html (status banner at the top explains the actual build order vs. the original speculative plan). User-facing guides: docs/esl-teacher-guide.html and docs/esl-student-guide.html.
| Table | Purpose | Key columns |
|---|---|---|
esl_students | One row per ESL student | login_code, pin_hash (SHA-256, client-hashed) โ added in Phase 2 |
esl_sessions | Instructor assignment, Meet link, schedule per student | instructor_id โ volunteers(id) |
esl_attendance | Per-class attendance log | attended (bool), instructor_notes |
esl_messages | Teacher โ student message thread | sender ('teacher'/'student'), sender_name, read_by_teacher, read_by_student |
All four tables use the same blanket RLS policy as the rest of the school system: FOR ALL USING (auth.uid() IS NOT NULL) โ no per-user scoping, consistent with every other table in this project. This means the anon key has zero direct access; the student portal never queries these tables directly (see RPCs below).
The previously-unused users.role column (supabase-school-schema.sql) is read on login in school-admin.js. When role = 'teacher', the sidebar is filtered down to just ESL Classes and Security, and the default landing section becomes ESL Classes instead of the dashboard. Accounts with no matching users row (e.g. pre-existing admins) default to full access โ purely additive, nothing broke for existing users when this was added.
The student portal (school/esl-portal.html / src/school-esl-portal.js) never authenticates with Supabase Auth โ there's no student login session. Instead, every action re-verifies login_code + SHA-256 pin_hash against the table directly inside a SECURITY DEFINER Postgres function, the same pattern the concert project already used for get_slot_capacity/check_duplicate_registration.
| Function | Purpose |
|---|---|
esl_portal_login(login_code, pin_hash) | Verifies credentials, returns student + session + last-10-attendance as one JSON payload. Returns NULL on any mismatch (never reveals whether the code exists). |
esl_portal_get_messages(login_code, pin_hash) | Verifies credentials, returns the message thread, marks teacher messages read_by_student = true as a side effect. |
esl_portal_send_message(login_code, pin_hash, body) | Verifies credentials, inserts a sender = 'student' message. |
Session persistence is just {login_code, pin_hash, expiry} in localStorage (7 days), silently re-calling esl_portal_login on page load โ no server-side session table. Brute-force lockout (5 attempts โ 10 min) is also tracked client-side in localStorage; a soft deterrent given static hosting has no server-side rate limiting.
A generic, reusable email notification system built 2026-07-25 after a volunteer application was silently lost to a broken form (stale JS reference on the public volunteer page โ see the "Broken pages" incident notes). Any table/event in the school project can be wired up to send an email without new code โ just a database row and a small trigger.
| Piece | What it does |
|---|---|
notification_rules table | Generic routing table: table_name, event, recipient_email, subject_template, body_template, active. event has no CHECK constraint โ can be a real Postgres event (INSERT) or a custom app-defined one (e.g. APPROVED). |
supabase/functions/notify/index.ts | Single reusable Edge Function. Verifies a custom Authorization: Bearer <secret> header against the NOTIFY_WEBHOOK_SECRET env var, looks up a matching active rule, renders {{field}} placeholders (in subject, body, and recipient_email) against the triggering row's data, sends via Resend. |
| pg_net triggers | One per table/event wired up, e.g. notify_new_volunteer (any INSERT) and notify_volunteer_approved (only fires when agreement_status transitions into 'approved'). Uses the pg_net extension's net.http_post(...) directly โ not the Dashboard's "Database Webhooks" UI, which wasn't available on this project (its underlying supabase_functions schema didn't exist). |
| Resend | Transactional email provider. Sending domain risingwithmelodies.org is verified (DKIM/SPF/DMARC records added at Porkbun, 2026-07-25) โ emails can go to any address, not just a single test recipient. Sender: notifications@risingwithmelodies.org. |
The Supabase Dashboard's paste-based Edge Function editor proved unreliable for this file (repeated silent truncation/corruption on an ~80-line paste). The Supabase CLI is installed locally (brew install supabase/tap/supabase) and authenticated โ prefer it for any future changes:
supabase functions deploy notify --project-ref cxsmoquefrxswkyjzzni --no-verify-jwt
The --no-verify-jwt flag is required: Database triggers send our own custom secret, not a Supabase JWT, so the platform's default JWT gate would otherwise reject every call before the function's own code (which does its own auth check) ever runs.
notification_rules (table_name, event, recipient_email, subject/body templates using {{field}} placeholders matching that table's columns โ recipient_email also supports templating, e.g. {{email}} to notify the record's own address).supabase-notify-webhook-trigger.sql or supabase-notify-volunteer-approved.sql โ a small plpgsql function calling net.http_post(...) with the same URL/secret header, then CREATE TRIGGER ... AFTER <EVENT> ON public.<table> ... EXECUTE FUNCTION ....No code changes or redeploys needed for a new table โ the notify function itself is fully generic.
auth.uid() IS NOT NULL โ admin login requireddonors and volunteers (for concert cross-save) and registrations (concert)schools (for the concert page to find the non-profit school ID)