···
···
import { handleError, ValidationErrors } from "./lib/errors";
import { requireAdmin, requireAuth } from "./lib/middleware";
···
original_filename: string;
-
class_name: string | null;
-
"SELECT id, filename, original_filename, class_name, status, progress, created_at FROM transcriptions WHERE user_id = ? ORDER BY created_at DESC",
···
filename: t.original_filename,
-
class_name: t.class_name,
created_at: t.created_at,
···
const formData = await req.formData();
const file = formData.get("audio") as File;
-
const className = formData.get("class_name") as string | null;
if (!file) throw ValidationErrors.missingField("audio");
const fileExtension = file.name.split(".").pop()?.toLowerCase();
const allowedExtensions = [
···
const uploadDir = "./uploads";
await Bun.write(`${uploadDir}/${filename}`, file);
-
// Create database record with optional class_name
-
if (className?.trim()) {
-
"INSERT INTO transcriptions (id, user_id, filename, original_filename, class_name, status) VALUES (?, ?, ?, ?, ?, ?)",
-
"INSERT INTO transcriptions (id, user_id, filename, original_filename, status) VALUES (?, ?, ?, ?, ?)",
-
[transcriptionId, user.id, filename, file.name, "uploading"],
-
// Start transcription in background
-
whisperService.startTranscription(transcriptionId, filename);
-
message: "Upload successful, transcription started",
return handleError(error);
···
user_email: user?.email || "Unknown",
user_name: user?.name || null,
return handleError(error);
···
···
+
getMeetingTimesForClass,
+
getTranscriptionsForClass,
+
} from "./lib/classes";
import { handleError, ValidationErrors } from "./lib/errors";
import { requireAdmin, requireAuth } from "./lib/middleware";
···
original_filename: string;
+
class_id: string | null;
+
"SELECT id, filename, original_filename, class_id, status, progress, created_at FROM transcriptions WHERE user_id = ? ORDER BY created_at DESC",
···
filename: t.original_filename,
created_at: t.created_at,
···
const formData = await req.formData();
const file = formData.get("audio") as File;
+
const classId = formData.get("class_id") as string | null;
+
const meetingTimeId = formData.get("meeting_time_id") as string | null;
if (!file) throw ValidationErrors.missingField("audio");
+
// If class_id provided, verify user is enrolled (or admin)
+
const enrolled = isUserEnrolledInClass(user.id, classId);
+
if (!enrolled && user.role !== "admin") {
+
{ error: "Not enrolled in this class" },
+
const classInfo = getClassById(classId);
+
{ error: "Class not found" },
+
// Check if class is archived
+
if (classInfo.archived) {
+
{ error: "Cannot upload to archived class" },
const fileExtension = file.name.split(".").pop()?.toLowerCase();
const allowedExtensions = [
···
const uploadDir = "./uploads";
await Bun.write(`${uploadDir}/${filename}`, file);
+
// Create database record
+
"INSERT INTO transcriptions (id, user_id, class_id, meeting_time_id, filename, original_filename, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
+
// Don't auto-start transcription - admin will select recordings
+
// whisperService.startTranscription(transcriptionId, filename);
+
message: "Upload successful",
return handleError(error);
···
user_email: user?.email || "Unknown",
user_name: user?.name || null,
+
return handleError(error);
+
const user = requireAuth(req);
+
const classes = getClassesForUser(user.id, user.role === "admin");
+
// Group by semester/year
+
for (const cls of classes) {
+
const key = `${cls.semester} ${cls.year}`;
+
course_code: cls.course_code,
+
professor: cls.professor,
+
semester: cls.semester,
+
archived: cls.archived,
+
return Response.json({ classes: grouped });
+
return handleError(error);
+
const body = await req.json();
+
const { course_code, name, professor, semester, year } = body;
+
if (!course_code || !name || !professor || !semester || !year) {
+
{ error: "Missing required fields" },
+
const newClass = createClass({
+
return Response.json(newClass);
+
return handleError(error);
+
const user = requireAuth(req);
+
const classId = req.params.id;
+
const classInfo = getClassById(classId);
+
return Response.json({ error: "Class not found" }, { status: 404 });
+
// Check enrollment or admin
+
const isEnrolled = isUserEnrolledInClass(user.id, classId);
+
if (!isEnrolled && user.role !== "admin") {
+
{ error: "Not enrolled in this class" },
+
const meetingTimes = getMeetingTimesForClass(classId);
+
const transcriptions = getTranscriptionsForClass(classId);
+
return handleError(error);
+
DELETE: async (req) => {
+
const classId = req.params.id;
+
return Response.json({ success: true });
+
return handleError(error);
+
"/api/classes/:id/archive": {
+
const classId = req.params.id;
+
const body = await req.json();
+
const { archived } = body;
+
if (typeof archived !== "boolean") {
+
{ error: "archived must be a boolean" },
+
toggleClassArchive(classId, archived);
+
return Response.json({ success: true });
+
return handleError(error);
+
"/api/classes/:id/members": {
+
const classId = req.params.id;
+
const members = getClassMembers(classId);
+
return Response.json({ members });
+
return handleError(error);
+
const classId = req.params.id;
+
const body = await req.json();
+
const { email } = body;
+
return Response.json({ error: "Email required" }, { status: 400 });
+
const user = getUserByEmail(email);
+
return Response.json({ error: "User not found" }, { status: 404 });
+
enrollUserInClass(user.id, classId);
+
return Response.json({ success: true });
+
return handleError(error);
+
"/api/classes/:id/members/:userId": {
+
DELETE: async (req) => {
+
const classId = req.params.id;
+
const userId = Number.parseInt(req.params.userId, 10);
+
if (Number.isNaN(userId)) {
+
return Response.json({ error: "Invalid user ID" }, { status: 400 });
+
removeUserFromClass(userId, classId);
+
return Response.json({ success: true });
+
return handleError(error);
+
"/api/classes/:id/meetings": {
+
const user = requireAuth(req);
+
const classId = req.params.id;
+
// Check enrollment or admin
+
const isEnrolled = isUserEnrolledInClass(user.id, classId);
+
if (!isEnrolled && user.role !== "admin") {
+
{ error: "Not enrolled in this class" },
+
const meetingTimes = getMeetingTimesForClass(classId);
+
return Response.json({ meetings: meetingTimes });
+
return handleError(error);
+
const classId = req.params.id;
+
const body = await req.json();
+
const { label } = body;
+
return Response.json({ error: "Label required" }, { status: 400 });
+
const meetingTime = createMeetingTime(classId, label);
+
return Response.json(meetingTime);
+
return handleError(error);
+
const meetingId = req.params.id;
+
const body = await req.json();
+
const { label } = body;
+
return Response.json({ error: "Label required" }, { status: 400 });
+
updateMeetingTime(meetingId, label);
+
return Response.json({ success: true });
+
return handleError(error);
+
DELETE: async (req) => {
+
const meetingId = req.params.id;
+
deleteMeetingTime(meetingId);
+
return Response.json({ success: true });
+
return handleError(error);
+
"/api/transcripts/:id/select": {
+
const transcriptId = req.params.id;
+
// Update status to 'selected' and start transcription
+
db.run("UPDATE transcriptions SET status = ? WHERE id = ?", [
+
// Get filename to start transcription
+
const transcription = db
+
.query<{ filename: string }, [string]>(
+
"SELECT filename FROM transcriptions WHERE id = ?",
+
whisperService.startTranscription(transcriptId, transcription.filename);
+
return Response.json({ success: true });
return handleError(error);