···
···
37
+
getMeetingTimesForClass,
38
+
getTranscriptionsForClass,
39
+
isUserEnrolledInClass,
40
+
removeUserFromClass,
43
+
} from "./lib/classes";
import { handleError, ValidationErrors } from "./lib/errors";
import { requireAdmin, requireAuth } from "./lib/middleware";
···
original_filename: string;
920
-
class_name: string | null;
937
+
class_id: string | null;
927
-
"SELECT id, filename, original_filename, class_name, status, progress, created_at FROM transcriptions WHERE user_id = ? ORDER BY created_at DESC",
944
+
"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,
937
-
class_name: t.class_name,
954
+
class_id: t.class_id,
created_at: t.created_at,
···
const formData = await req.formData();
const file = formData.get("audio") as File;
956
-
const className = formData.get("class_name") as string | null;
973
+
const classId = formData.get("class_id") as string | null;
974
+
const meetingTimeId = formData.get("meeting_time_id") as string | null;
if (!file) throw ValidationErrors.missingField("audio");
978
+
// If class_id provided, verify user is enrolled (or admin)
980
+
const enrolled = isUserEnrolledInClass(user.id, classId);
981
+
if (!enrolled && user.role !== "admin") {
982
+
return Response.json(
983
+
{ error: "Not enrolled in this class" },
988
+
// Verify class exists
989
+
const classInfo = getClassById(classId);
991
+
return Response.json(
992
+
{ error: "Class not found" },
997
+
// Check if class is archived
998
+
if (classInfo.archived) {
999
+
return Response.json(
1000
+
{ error: "Cannot upload to archived class" },
const fileExtension = file.name.split(".").pop()?.toLowerCase();
const allowedExtensions = [
···
const uploadDir = "./uploads";
await Bun.write(`${uploadDir}/${filename}`, file);
995
-
// Create database record with optional class_name
996
-
if (className?.trim()) {
998
-
"INSERT INTO transcriptions (id, user_id, filename, original_filename, class_name, status) VALUES (?, ?, ?, ?, ?, ?)",
1010
-
"INSERT INTO transcriptions (id, user_id, filename, original_filename, status) VALUES (?, ?, ?, ?, ?)",
1011
-
[transcriptionId, user.id, filename, file.name, "uploading"],
1041
+
// Create database record
1043
+
"INSERT INTO transcriptions (id, user_id, class_id, meeting_time_id, filename, original_filename, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
1015
-
// Start transcription in background
1016
-
whisperService.startTranscription(transcriptionId, filename);
1055
+
// Don't auto-start transcription - admin will select recordings
1056
+
// whisperService.startTranscription(transcriptionId, filename);
1020
-
message: "Upload successful, transcription started",
1060
+
message: "Upload successful",
return handleError(error);
···
user_email: user?.email || "Unknown",
user_name: user?.name || null,
1425
+
return handleError(error);
1430
+
GET: async (req) => {
1432
+
const user = requireAuth(req);
1433
+
const classes = getClassesForUser(user.id, user.role === "admin");
1435
+
// Group by semester/year
1436
+
const grouped: Record<
1440
+
course_code: string;
1442
+
professor: string;
1445
+
archived: boolean;
1449
+
for (const cls of classes) {
1450
+
const key = `${cls.semester} ${cls.year}`;
1451
+
if (!grouped[key]) {
1452
+
grouped[key] = [];
1454
+
grouped[key]?.push({
1456
+
course_code: cls.course_code,
1458
+
professor: cls.professor,
1459
+
semester: cls.semester,
1461
+
archived: cls.archived,
1465
+
return Response.json({ classes: grouped });
1467
+
return handleError(error);
1470
+
POST: async (req) => {
1472
+
requireAdmin(req);
1473
+
const body = await req.json();
1474
+
const { course_code, name, professor, semester, year } = body;
1476
+
if (!course_code || !name || !professor || !semester || !year) {
1477
+
return Response.json(
1478
+
{ error: "Missing required fields" },
1483
+
const newClass = createClass({
1491
+
return Response.json(newClass);
1493
+
return handleError(error);
1497
+
"/api/classes/:id": {
1498
+
GET: async (req) => {
1500
+
const user = requireAuth(req);
1501
+
const classId = req.params.id;
1503
+
const classInfo = getClassById(classId);
1505
+
return Response.json({ error: "Class not found" }, { status: 404 });
1508
+
// Check enrollment or admin
1509
+
const isEnrolled = isUserEnrolledInClass(user.id, classId);
1510
+
if (!isEnrolled && user.role !== "admin") {
1511
+
return Response.json(
1512
+
{ error: "Not enrolled in this class" },
1517
+
const meetingTimes = getMeetingTimesForClass(classId);
1518
+
const transcriptions = getTranscriptionsForClass(classId);
1520
+
return Response.json({
1526
+
return handleError(error);
1529
+
DELETE: async (req) => {
1531
+
requireAdmin(req);
1532
+
const classId = req.params.id;
1534
+
deleteClass(classId);
1535
+
return Response.json({ success: true });
1537
+
return handleError(error);
1541
+
"/api/classes/:id/archive": {
1542
+
PUT: async (req) => {
1544
+
requireAdmin(req);
1545
+
const classId = req.params.id;
1546
+
const body = await req.json();
1547
+
const { archived } = body;
1549
+
if (typeof archived !== "boolean") {
1550
+
return Response.json(
1551
+
{ error: "archived must be a boolean" },
1556
+
toggleClassArchive(classId, archived);
1557
+
return Response.json({ success: true });
1559
+
return handleError(error);
1563
+
"/api/classes/:id/members": {
1564
+
GET: async (req) => {
1566
+
requireAdmin(req);
1567
+
const classId = req.params.id;
1569
+
const members = getClassMembers(classId);
1570
+
return Response.json({ members });
1572
+
return handleError(error);
1575
+
POST: async (req) => {
1577
+
requireAdmin(req);
1578
+
const classId = req.params.id;
1579
+
const body = await req.json();
1580
+
const { email } = body;
1583
+
return Response.json({ error: "Email required" }, { status: 400 });
1586
+
const user = getUserByEmail(email);
1588
+
return Response.json({ error: "User not found" }, { status: 404 });
1591
+
enrollUserInClass(user.id, classId);
1592
+
return Response.json({ success: true });
1594
+
return handleError(error);
1598
+
"/api/classes/:id/members/:userId": {
1599
+
DELETE: async (req) => {
1601
+
requireAdmin(req);
1602
+
const classId = req.params.id;
1603
+
const userId = Number.parseInt(req.params.userId, 10);
1605
+
if (Number.isNaN(userId)) {
1606
+
return Response.json({ error: "Invalid user ID" }, { status: 400 });
1609
+
removeUserFromClass(userId, classId);
1610
+
return Response.json({ success: true });
1612
+
return handleError(error);
1616
+
"/api/classes/:id/meetings": {
1617
+
GET: async (req) => {
1619
+
const user = requireAuth(req);
1620
+
const classId = req.params.id;
1622
+
// Check enrollment or admin
1623
+
const isEnrolled = isUserEnrolledInClass(user.id, classId);
1624
+
if (!isEnrolled && user.role !== "admin") {
1625
+
return Response.json(
1626
+
{ error: "Not enrolled in this class" },
1631
+
const meetingTimes = getMeetingTimesForClass(classId);
1632
+
return Response.json({ meetings: meetingTimes });
1634
+
return handleError(error);
1637
+
POST: async (req) => {
1639
+
requireAdmin(req);
1640
+
const classId = req.params.id;
1641
+
const body = await req.json();
1642
+
const { label } = body;
1645
+
return Response.json({ error: "Label required" }, { status: 400 });
1648
+
const meetingTime = createMeetingTime(classId, label);
1649
+
return Response.json(meetingTime);
1651
+
return handleError(error);
1655
+
"/api/meetings/:id": {
1656
+
PUT: async (req) => {
1658
+
requireAdmin(req);
1659
+
const meetingId = req.params.id;
1660
+
const body = await req.json();
1661
+
const { label } = body;
1664
+
return Response.json({ error: "Label required" }, { status: 400 });
1667
+
updateMeetingTime(meetingId, label);
1668
+
return Response.json({ success: true });
1670
+
return handleError(error);
1673
+
DELETE: async (req) => {
1675
+
requireAdmin(req);
1676
+
const meetingId = req.params.id;
1678
+
deleteMeetingTime(meetingId);
1679
+
return Response.json({ success: true });
1681
+
return handleError(error);
1685
+
"/api/transcripts/:id/select": {
1686
+
PUT: async (req) => {
1688
+
requireAdmin(req);
1689
+
const transcriptId = req.params.id;
1691
+
// Update status to 'selected' and start transcription
1692
+
db.run("UPDATE transcriptions SET status = ? WHERE id = ?", [
1697
+
// Get filename to start transcription
1698
+
const transcription = db
1699
+
.query<{ filename: string }, [string]>(
1700
+
"SELECT filename FROM transcriptions WHERE id = ?",
1702
+
.get(transcriptId);
1704
+
if (transcription) {
1705
+
whisperService.startTranscription(transcriptId, transcription.filename);
1708
+
return Response.json({ success: true });
return handleError(error);