···11-# Class System Specification
22-33-## Overview
44-55-Restructure Thistle from individual transcript management to class-based transcript organization. Users will manage transcripts grouped by classes, with scheduled meeting times and selective transcription.
66-77-## User Flow
88-99-### 1. Classes Page (Home)
1010-- Replaces the transcript page as the main view after signup
1111-- Displays grid of class cards organized by semester/year
1212-- Each section (semester/year combo) separated by horizontal rules
1313-- Each card shows:
1414- - Course code (e.g., "CS 101")
1515- - Course name (e.g., "Introduction to Computer Science")
1616- - Professor name
1717- - Semester and year (e.g., "Fall 2024")
1818- - Archive indicator (if archived)
1919-- Final card in grid is "Register for Class" with centered plus icon
2020-- Empty state: Only shows register button if user has no classes
2121-2222-### 2. Individual Class Page (`/classes/:id`)
2323-- Lists all recordings and transcripts for the class
2424-- Shows meeting schedule (flexible text, e.g., "Monday Lecture", "Wednesday Lab")
2525-- Displays recordings with statuses:
2626- - **Pending**: Uploaded but not selected for transcription
2727- - **Selected**: Marked for transcription by admin
2828- - **Transcribed**: Processing complete, ready to view
2929- - **Failed**: Transcription failed
3030-- Upload button to add new recordings
3131-- Each recording tagged with meeting time
3232-3333-### 3. Recording Upload
3434-- Any enrolled student can upload recordings
3535-- Must select which meeting time the recording is for
3636-- Recording enters "pending" state
3737-- Does not auto-transcribe
3838-3939-### 4. Admin Workflow
4040-- Admin views pending recordings
4141-- Selects specific recording to transcribe for each meeting
4242-- Only selected recordings get processed
4343-- Can manage classes (create, archive, enrollments)
4444-4545-## Database Schema
4646-4747-### Classes Table
4848-```sql
4949-CREATE TABLE classes (
5050- id TEXT PRIMARY KEY, -- stable random ID (nanoid or similar)
5151- course_code TEXT NOT NULL, -- e.g., "CS 101"
5252- name TEXT NOT NULL, -- e.g., "Introduction to Computer Science"
5353- professor TEXT NOT NULL,
5454- semester TEXT NOT NULL, -- e.g., "Fall", "Spring", "Summer"
5555- year INTEGER NOT NULL, -- e.g., 2024
5656- archived BOOLEAN DEFAULT FALSE,
5757- created_at INTEGER NOT NULL
5858-);
5959-```
6060-6161-### Class Members Table
6262-```sql
6363-CREATE TABLE class_members (
6464- class_id TEXT NOT NULL,
6565- user_id TEXT NOT NULL,
6666- enrolled_at INTEGER NOT NULL,
6767- PRIMARY KEY (class_id, user_id),
6868- FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE,
6969- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
7070-);
7171-```
7272-7373-### Meeting Times Table
7474-```sql
7575-CREATE TABLE meeting_times (
7676- id TEXT PRIMARY KEY,
7777- class_id TEXT NOT NULL,
7878- label TEXT NOT NULL, -- flexible text: "Monday Lecture", "Wednesday Lab", etc.
7979- created_at INTEGER NOT NULL,
8080- FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE
8181-);
8282-```
8383-8484-### Updated Transcripts Table
8585-```sql
8686--- Add new columns to existing transcripts table:
8787-ALTER TABLE transcripts ADD COLUMN class_id TEXT;
8888-ALTER TABLE transcripts ADD COLUMN meeting_time_id TEXT;
8989-ALTER TABLE transcripts ADD COLUMN status TEXT DEFAULT 'pending';
9090--- status: 'pending' | 'selected' | 'transcribed' | 'failed'
9191-9292--- Add foreign keys:
9393-FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE
9494-FOREIGN KEY (meeting_time_id) REFERENCES meeting_times(id) ON DELETE SET NULL
9595-```
9696-9797-**Note**: Add indexes for performance:
9898-- `class_members(user_id)` - lookup user's classes
9999-- `class_members(class_id)` - lookup class members
100100-- `transcripts(class_id)` - lookup class transcripts
101101-- `transcripts(status)` - filter by status
102102-- `meeting_times(class_id)` - lookup class schedule
103103-104104-## Permissions
105105-106106-### Class Access
107107-- Users can only view classes they're enrolled in
108108-- Admins can view all classes
109109-- Non-enrolled users get 403 when accessing `/classes/:id`
110110-111111-### Recording Permissions
112112-- **Upload**: Any enrolled student can upload recordings
113113-- **Delete**: Students can delete their own recordings
114114-- **Select for transcription**: Admin only
115115-- **View**: All enrolled students can view all transcripts in their classes
116116-117117-### Class Management
118118-- **Create**: Admin only (via admin UI)
119119-- **Archive**: Admin only (via admin UI)
120120-- **Enroll students**: Admin only (via admin UI)
121121-- **Remove students**: Admin only (via admin UI)
122122-123123-## Archive Behavior
124124-125125-When a class is archived:
126126-- Students can still view the class and all transcripts
127127-- No new recordings can be uploaded
128128-- No recordings can be deleted
129129-- No transcription selection allowed
130130-- No enrollment changes
131131-- Class appears with archive indicator in UI
132132-- Organized with active classes by semester/year
133133-134134-## API Endpoints
135135-136136-### Classes
137137-- `GET /api/classes` - List user's classes (grouped by semester/year)
138138-- `GET /api/classes/:id` - Get class details (info, meeting times, transcripts)
139139-- `POST /api/classes` (admin) - Create new class
140140-- `PUT /api/classes/:id/archive` (admin) - Archive/unarchive class
141141-- `DELETE /api/classes/:id` (admin) - Delete class
142142-143143-### Class Members
144144-- `POST /api/classes/:id/members` (admin) - Enroll student(s)
145145-- `DELETE /api/classes/:id/members/:userId` (admin) - Remove student
146146-- `GET /api/classes/:id/members` (admin) - List class members
147147-148148-### Meeting Times
149149-- `GET /api/classes/:id/meetings` - List meeting times
150150-- `POST /api/classes/:id/meetings` (admin) - Create meeting time
151151-- `PUT /api/meetings/:id` (admin) - Update meeting time label
152152-- `DELETE /api/meetings/:id` (admin) - Delete meeting time
153153-154154-### Recordings/Transcripts
155155-- `GET /api/classes/:id/transcripts` - List all transcripts for class
156156-- `POST /api/classes/:id/recordings` - Upload recording (enrolled students)
157157-- `PUT /api/transcripts/:id/select` (admin) - Mark recording for transcription
158158-- `DELETE /api/transcripts/:id` - Delete recording (owner or admin)
159159-- `GET /api/transcripts/:id` - View transcript (enrolled students)
160160-161161-## Frontend Components
162162-163163-### Pages
164164-- `/classes` - Classes grid (home page, replaces transcripts page)
165165-- `/classes/:id` - Individual class view
166166-- `/admin` - Update to include class management
167167-168168-### New Components
169169-- `class-card.ts` - Class card component
170170-- `register-card.ts` - Register for class card (plus icon)
171171-- `class-detail.ts` - Individual class page
172172-- `recording-upload.ts` - Recording upload form
173173-- `recording-list.ts` - List of recordings with status
174174-- `admin-classes.ts` - Admin class management interface
175175-176176-### Navigation Updates
177177-- Remove transcript page links
178178-- Add classes link (make it home)
179179-- Update auth redirect after signup to `/classes`
180180-181181-## Migration Strategy
182182-183183-**Breaking change**: Reset database schema to consolidate all migrations.
184184-185185-1. Export any critical production data (if needed)
186186-2. Drop all tables
187187-3. Consolidate migrations in `src/db/schema.ts`:
188188- - Include all previous migrations
189189- - Add new class system tables
190190- - Add new columns to transcripts
191191-4. Restart with version 1
192192-5. Existing transcripts will be lost (acceptable for this phase)
193193-194194-## Admin UI Updates
195195-196196-### Class Management Tab
197197-- Create new class form:
198198- - Course code
199199- - Course name
200200- - Professor
201201- - Semester dropdown (Fall/Spring/Summer/Winter)
202202- - Year input
203203-- List all classes (with archive status)
204204-- Archive/unarchive button per class
205205-- Delete class button
206206-207207-### Enrollment Management
208208-- Search for class
209209-- Add student by email
210210-- Remove enrolled students
211211-- View enrollment list per class
212212-- Future: Bulk CSV import
213213-214214-### Recording Selection
215215-- View pending recordings per class
216216-- Select recording to transcribe for each meeting
217217-- View transcription status
218218-- Handle failed transcriptions
219219-220220-## Empty States
221221-222222-- **No classes**: Show only register card with message "No classes yet"
223223-- **No recordings in class**: Show message "No recordings yet" with upload button
224224-- **No pending recordings**: Show message in admin "All recordings processed"
225225-226226-## Future Enhancements (Out of Scope)
227227-228228-- Share/enrollment links for self-enrollment
229229-- Notifications when transcripts ready
230230-- Auto-transcribe settings per class
231231-- Student/instructor roles
232232-- Search/filter classes
233233-- Bulk enrollment via CSV
234234-- Meeting time templates (MWF, TTh patterns)
235235-- Download all transcripts for a class
236236-237237-## Open Questions
238238-239239-None - spec is complete for initial implementation.
240240-241241-## Implementation Phases
242242-243243-### Phase 1: Database & Backend
244244-1. Consolidate migrations and add new schema
245245-2. Add API endpoints for classes and members
246246-3. Update permissions middleware
247247-4. Add admin endpoints
248248-249249-### Phase 2: Admin UI
250250-1. Class management interface
251251-2. Enrollment management
252252-3. Recording selection interface
253253-254254-### Phase 3: Student UI
255255-1. Classes page with cards
256256-2. Individual class pages
257257-3. Recording upload
258258-4. Update navigation
259259-260260-### Phase 4: Testing & Polish
261261-1. Test permissions thoroughly
262262-2. Test archive behavior
263263-3. Empty states
264264-4. Error handling
···213213 VALUES (0, 'ghosty@thistle.internal', NULL, 'Ghosty', '👻', 'user', strftime('%s', 'now'));
214214 `,
215215 },
216216+ {
217217+ version: 8,
218218+ name: "Add email verification system",
219219+ sql: `
220220+ -- Add email verification flag to users
221221+ ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT 0;
222222+223223+ -- Email verification tokens table
224224+ CREATE TABLE IF NOT EXISTS email_verification_tokens (
225225+ id TEXT PRIMARY KEY,
226226+ user_id INTEGER NOT NULL,
227227+ token TEXT NOT NULL UNIQUE,
228228+ expires_at INTEGER NOT NULL,
229229+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
230230+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
231231+ );
232232+233233+ CREATE INDEX IF NOT EXISTS idx_verification_tokens_user_id ON email_verification_tokens(user_id);
234234+ CREATE INDEX IF NOT EXISTS idx_verification_tokens_token ON email_verification_tokens(token);
235235+236236+ -- Password reset tokens table
237237+ CREATE TABLE IF NOT EXISTS password_reset_tokens (
238238+ id TEXT PRIMARY KEY,
239239+ user_id INTEGER NOT NULL,
240240+ token TEXT NOT NULL UNIQUE,
241241+ expires_at INTEGER NOT NULL,
242242+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
243243+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
244244+ );
245245+246246+ CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user_id ON password_reset_tokens(user_id);
247247+ CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_token ON password_reset_tokens(token);
248248+ `,
249249+ },
216250];
217251218252function getCurrentVersion(): number {