馃 distributed transcription service
thistle.dunkirk.sh
1import { nanoid } from "nanoid";
2import db from "../db/schema";
3
4export interface Class {
5 id: string;
6 course_code: string;
7 name: string;
8 professor: string;
9 semester: string;
10 year: number;
11 archived: boolean;
12 created_at: number;
13}
14
15export interface MeetingTime {
16 id: string;
17 class_id: string;
18 label: string;
19 created_at: number;
20}
21
22export interface ClassMember {
23 class_id: string;
24 user_id: number;
25 enrolled_at: number;
26}
27
28/**
29 * Get all classes for a user (either enrolled or admin sees all)
30 */
31export function getClassesForUser(
32 userId: number,
33 isAdmin: boolean,
34): Class[] {
35 if (isAdmin) {
36 return db
37 .query<Class, []>(
38 "SELECT * FROM classes ORDER BY year DESC, semester DESC, course_code ASC",
39 )
40 .all();
41 }
42
43 return db
44 .query<Class, [number]>(
45 `SELECT c.* FROM classes c
46 INNER JOIN class_members cm ON c.id = cm.class_id
47 WHERE cm.user_id = ?
48 ORDER BY c.year DESC, c.semester DESC, c.course_code ASC`,
49 )
50 .all(userId);
51}
52
53/**
54 * Get a single class by ID
55 */
56export function getClassById(classId: string): Class | null {
57 const result = db
58 .query<Class, [string]>("SELECT * FROM classes WHERE id = ?")
59 .get(classId);
60 return result ?? null;
61}
62
63/**
64 * Check if user is enrolled in a class
65 */
66export function isUserEnrolledInClass(
67 userId: number,
68 classId: string,
69): boolean {
70 const result = db
71 .query<{ count: number }, [string, number]>(
72 "SELECT COUNT(*) as count FROM class_members WHERE class_id = ? AND user_id = ?",
73 )
74 .get(classId, userId);
75 return (result?.count ?? 0) > 0;
76}
77
78/**
79 * Create a new class
80 */
81export function createClass(data: {
82 course_code: string;
83 name: string;
84 professor: string;
85 semester: string;
86 year: number;
87}): Class {
88 const id = nanoid();
89 const now = Math.floor(Date.now() / 1000);
90
91 db.run(
92 "INSERT INTO classes (id, course_code, name, professor, semester, year, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
93 [
94 id,
95 data.course_code,
96 data.name,
97 data.professor,
98 data.semester,
99 data.year,
100 now,
101 ],
102 );
103
104 return {
105 id,
106 course_code: data.course_code,
107 name: data.name,
108 professor: data.professor,
109 semester: data.semester,
110 year: data.year,
111 archived: false,
112 created_at: now,
113 };
114}
115
116/**
117 * Archive or unarchive a class
118 */
119export function toggleClassArchive(classId: string, archived: boolean): void {
120 db.run("UPDATE classes SET archived = ? WHERE id = ?", [
121 archived ? 1 : 0,
122 classId,
123 ]);
124}
125
126/**
127 * Delete a class (cascades to members, meeting times, and transcriptions)
128 */
129export function deleteClass(classId: string): void {
130 db.run("DELETE FROM classes WHERE id = ?", [classId]);
131}
132
133/**
134 * Enroll a user in a class
135 */
136export function enrollUserInClass(userId: number, classId: string): void {
137 const now = Math.floor(Date.now() / 1000);
138 db.run(
139 "INSERT OR IGNORE INTO class_members (class_id, user_id, enrolled_at) VALUES (?, ?, ?)",
140 [classId, userId, now],
141 );
142}
143
144/**
145 * Remove a user from a class
146 */
147export function removeUserFromClass(userId: number, classId: string): void {
148 db.run("DELETE FROM class_members WHERE class_id = ? AND user_id = ?", [
149 classId,
150 userId,
151 ]);
152}
153
154/**
155 * Get all members of a class
156 */
157export function getClassMembers(classId: string) {
158 return db
159 .query<
160 {
161 user_id: number;
162 email: string;
163 name: string | null;
164 avatar: string;
165 enrolled_at: number;
166 },
167 [string]
168 >(
169 `SELECT cm.user_id, u.email, u.name, u.avatar, cm.enrolled_at
170 FROM class_members cm
171 INNER JOIN users u ON cm.user_id = u.id
172 WHERE cm.class_id = ?
173 ORDER BY cm.enrolled_at DESC`,
174 )
175 .all(classId);
176}
177
178/**
179 * Create a meeting time for a class
180 */
181export function createMeetingTime(classId: string, label: string): MeetingTime {
182 const id = nanoid();
183 const now = Math.floor(Date.now() / 1000);
184
185 db.run(
186 "INSERT INTO meeting_times (id, class_id, label, created_at) VALUES (?, ?, ?, ?)",
187 [id, classId, label, now],
188 );
189
190 return {
191 id,
192 class_id: classId,
193 label,
194 created_at: now,
195 };
196}
197
198/**
199 * Get all meeting times for a class
200 */
201export function getMeetingTimesForClass(classId: string): MeetingTime[] {
202 return db
203 .query<MeetingTime, [string]>(
204 "SELECT * FROM meeting_times WHERE class_id = ? ORDER BY created_at ASC",
205 )
206 .all(classId);
207}
208
209/**
210 * Update a meeting time label
211 */
212export function updateMeetingTime(meetingId: string, label: string): void {
213 db.run("UPDATE meeting_times SET label = ? WHERE id = ?", [label, meetingId]);
214}
215
216/**
217 * Delete a meeting time
218 */
219export function deleteMeetingTime(meetingId: string): void {
220 db.run("DELETE FROM meeting_times WHERE id = ?", [meetingId]);
221}
222
223/**
224 * Get transcriptions for a class
225 */
226export function getTranscriptionsForClass(classId: string) {
227 return db
228 .query<
229 {
230 id: string;
231 user_id: number;
232 meeting_time_id: string | null;
233 filename: string;
234 original_filename: string;
235 status: string;
236 progress: number;
237 error_message: string | null;
238 created_at: number;
239 updated_at: number;
240 },
241 [string]
242 >(
243 `SELECT id, user_id, meeting_time_id, filename, original_filename, status, progress, error_message, created_at, updated_at
244 FROM transcriptions
245 WHERE class_id = ?
246 ORDER BY created_at DESC`,
247 )
248 .all(classId);
249}