TutorLoom
The learning platform for companies, colleges and training institutes. Free and open source, with an Enterprise edition.
Free notes, past-paper practice and study communities for medical students — built in Kenya, open everywhere.
EducationJavaScript · HTMLGitHub @WandagoOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Your medical school, all in one place — free study resources, exam practice, unit study groups, communities and messaging for medical students in Kenya.
Plain HTML/CSS/JS (no framework, no bundler).
| Concern | Service | Notes |
|---|---|---|
| Authentication | Clerk | Sign-up, sign-in, sessions, account management |
| Data + files | Supabase | Postgres with Row Level Security, Storage for uploads |
| Hosting | Vercel | Static deploy; scripts/build.mjs injects keys at build time |
Supabase never handles logins — it trusts Clerk session tokens through Clerk's official
Supabase integration. Inside the database the
signed-in user is auth.jwt() ->> 'sub' (their Clerk user ID), and every table is locked down with RLS.
pk_test_…).supabase/schema.sql → Run.
This creates all tables, security policies, the 4 starter communities, the resources and site-media
storage buckets, analytics + activity logging, roles and the admin functions.service_role / secret key).Project → Settings → Environment Variables, add:
| Name | Value |
|---|---|
CLERK_PUBLISHABLE_KEY | pk_test_… from Clerk |
SUPABASE_URL | https://<ref>.supabase.co |
SUPABASE_PUBLISHABLE_KEY | publishable / anon key |
Redeploy. vercel.json runs node scripts/build.mjs, which copies the site into dist/ and
writes dist/config.js from those variables.
Clerk development keys (
pk_test_…) work on any*.vercel.appURL — perfect for testing and sharing. Clerk production keys require a domain you own.
cp config.example.js config.js # then fill in the three values
npx serve . # or any static file server
app.js runtime: config checks, Clerk boot, Supabase client, all queries, shared UI
data.js static catalogue: Kenyan schools, countries, courses, units, interests, news
data/universities/ universities by country (one JSON file per country, loaded on demand)
theme.js light / dark theme (runs before first paint)
styles.css design system landing.css landing page only
admin.html/js/css admin dashboard (analytics, activity, users & roles, content, images)
img/ default site photos (Unsplash, see img/CREDITS.md) — replaceable from the admin page
*.html one file per page; each calls MedLink.run({ page }, async ({ api, me }) => …)
supabase/schema.sql database schema + RLS + storage bucket
scripts/build.mjs Vercel build: copies files to dist/ and writes config.js from env vars
data.js.admin.html)insert into public.user_roles (user_id, role)
select id, 'super_admin' from public.profiles where username = 'your_username';
After that, give other people roles from Admin → Users & roles.
| Role | Can |
|---|---|
| Super admin | Everything, including making admins / super admins and suspending staff |
| Admin | Site images, communities, edit profiles, suspend students, make moderators |
| Moderator | Analytics, activity, reports; edit or remove posts, comments and resources |
Every rule is enforced in the database (RLS + set_user_role, set_user_suspended, admin_stats,
admin_users in schema.sql), not just by hiding buttons.
?utm_source=whatsapp&utm_campaign=…).Admin → Users & roles → Add admin or moderator lets you search anyone on MedLink and give them a role.
To give a role to someone who hasn't joined yet, invite their email in the same window — they get the role the
next time they sign in. For that, Clerk must put the (verified) email in its session token:
Clerk dashboard → Sessions → Customize session token → add { "email": "{{user.primary_email_address}}" } → Save.
Admin → Exam bank:
Q: / A) … * format).Students pick their country during onboarding. Kenya shows a curated list of medical schools filtered by
programme (including Aga Khan University's Medical College and School of Nursing & Midwifery), with
"My school isn't listed" for anything else, e.g. KMTC. Every other country gets a searchable list from
data/universities/ — 10,000+ universities in 200 countries from the MIT-licensed
Hipo university list (refresh with node scripts/universities.mjs);
if a school is missing, students type its name.
Kept broad on purpose: one per course, one per country (created automatically the first time someone from that
country joins), a few topics, and MedLink Global for everyone. New students join their course, their country
and the global community automatically; year, course and university show on every post. Every
community has an invite link (join.html?c=…) with WhatsApp / Telegram / X / Facebook / email / SMS buttons —
new students are walked through sign-up and land inside the community. Each share is tagged
(utm_source=whatsapp etc.) so Admin → Traffic shows which channels bring people in.
app.js records page views, clicks on anything marked data-track, links to other sites, key actions
(sign-up, sign-in/out, uploads, downloads, views, saves, likes, comments, posts, follows, joins, searches,
messages sent, quiz starts/results), time actively spent on each page, and JavaScript errors — together with the
visit's traffic source (referrer + UTM), landing page, device, browser, OS, screen width, language and time zone.
Visitors get a random anonymous ID; signed-in events are linked to the user by the database itself.
Only staff can read the data. The landing-page footer tells visitors anonymous statistics are collected.
Re-run the whole of supabase/schema.sql in the SQL Editor — it only adds what's missing.