···79798080# Build files
8181bun build <file.html|file.ts|file.css>
8282+8383+# Make a user an admin
8484+bun scripts/make-admin.ts <email>
8285```
83868487Development workflow: `bun dev` runs the server with hot module reloading. Changes to TypeScript, HTML, or CSS files automatically reload.
···4034064. Components self-register as custom elements
4044075. Bun bundles everything automatically
405408409409+## Database Schema & Migrations
410410+411411+Database migrations are managed in `src/db/schema.ts` using a versioned migration system.
412412+413413+**Migration structure:**
414414+```typescript
415415+const migrations = [
416416+ {
417417+ version: 1,
418418+ name: "Description of migration",
419419+ sql: `
420420+ CREATE TABLE IF NOT EXISTS ...;
421421+ CREATE INDEX IF NOT EXISTS ...;
422422+ `,
423423+ },
424424+];
425425+```
426426+427427+**Important migration rules:**
428428+1. **Never modify existing migrations** - they may have already run in production
429429+2. **Always add new migrations** with incrementing version numbers
430430+3. **Drop indexes before dropping columns** - SQLite will error if you try to drop a column with an index still attached
431431+4. **Use IF NOT EXISTS** for CREATE statements to be idempotent
432432+5. **Test migrations** on a copy of production data before deploying
433433+434434+**Example: Dropping a column**
435435+```sql
436436+-- ❌ WRONG: Will error if idx_users_old_column exists
437437+ALTER TABLE users DROP COLUMN old_column;
438438+439439+-- ✅ CORRECT: Drop index first, then column
440440+DROP INDEX IF EXISTS idx_users_old_column;
441441+ALTER TABLE users DROP COLUMN old_column;
442442+```
443443+444444+**Migration workflow:**
445445+1. Add migration to `migrations` array with next version number
446446+2. Migrations auto-apply on server start
447447+3. Check `schema_migrations` table to see applied versions
448448+4. Migrations are transactional and show timing in console
449449+406450## File Organization
407451408452- `src/index.ts`: Main server entry point with `Bun.serve()` routes
···485529- [Web Components MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
486530- Bun API docs in `node_modules/bun-types/docs/**.md`
487531532532+## Admin System
533533+534534+The application includes a role-based admin system for managing users and transcriptions.
535535+536536+**User roles:**
537537+- `user` - Default role, can create and manage their own transcriptions
538538+- `admin` - Full administrative access to all data and users
539539+540540+**Admin privileges:**
541541+- View all transcriptions (with user info, status, errors)
542542+- Delete transcriptions
543543+- View all users (with emails, join dates, roles)
544544+- Change user roles (user ↔ admin)
545545+- Delete user accounts
546546+- Access admin dashboard at `/admin`
547547+548548+**Making users admin:**
549549+Use the provided script to grant admin access:
550550+```bash
551551+bun scripts/make-admin.ts user@example.com
552552+```
553553+554554+**Admin routes:**
555555+- `/admin` - Admin dashboard (protected by `requireAdmin` middleware)
556556+- `/api/admin/transcriptions` - Get all transcriptions with user info
557557+- `/api/admin/transcriptions/:id` - Delete a transcription (DELETE)
558558+- `/api/admin/users` - Get all users
559559+- `/api/admin/users/:id` - Delete a user account (DELETE)
560560+- `/api/admin/users/:id/role` - Update a user's role (PUT)
561561+562562+**Admin UI features:**
563563+- Statistics cards (total users, total/failed transcriptions)
564564+- Tabbed interface (Transcriptions / Users)
565565+- Status badges for transcription states
566566+- Delete buttons for transcriptions with confirmation
567567+- Role dropdown for changing user roles
568568+- Delete buttons for user accounts with confirmation
569569+- User avatars and info display
570570+- Timestamp formatting
571571+- Admin badge on user listings
572572+573573+**Implementation notes:**
574574+- `role` column in users table ('user' or 'admin', default 'user')
575575+- `requireAdmin()` middleware checks authentication + admin role
576576+- Returns 403 if non-admin tries to access admin routes
577577+- Admin link shows in auth menu only for admin users
578578+- Redirects to home page if non-admin accesses admin page
579579+488580## Future Additions
489581490582As the codebase grows, document:
···494586- Transcription service integration details
495587- Deployment process
496588- Environment variables needed
589589+
+31
scripts/make-admin.ts
···11+#!/usr/bin/env bun
22+33+import db from "../src/db/schema";
44+55+const email = process.argv[2];
66+77+if (!email) {
88+ console.error("Usage: bun scripts/make-admin.ts <email>");
99+ process.exit(1);
1010+}
1111+1212+const user = db
1313+ .query<{ id: number; email: string; role: string }, [string]>(
1414+ "SELECT id, email, role FROM users WHERE email = ?",
1515+ )
1616+ .get(email);
1717+1818+if (!user) {
1919+ console.error(`User with email ${email} not found`);
2020+ process.exit(1);
2121+}
2222+2323+if (user.role === "admin") {
2424+ console.log(`User ${email} is already an admin`);
2525+ process.exit(0);
2626+}
2727+2828+db.run("UPDATE users SET role = 'admin' WHERE id = ?", [user.id]);
2929+3030+console.log(`✅ Successfully made ${email} an admin`);
3131+console.log(` User should refresh their browser to see admin access`);
···8989 CREATE INDEX IF NOT EXISTS idx_rate_limit_key_timestamp ON rate_limit_attempts(key, timestamp);
9090 `,
9191 },
9292+ {
9393+ version: 6,
9494+ name: "Add role-based auth system",
9595+ sql: `
9696+ -- Add role column (default to 'user')
9797+ ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user';
9898+9999+ -- Create index on role
100100+ CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
101101+ `,
102102+ },
92103];
9310494105function getCurrentVersion(): number {