@studious-lms/server 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/API_SPECIFICATION.md +1117 -0
  2. package/dist/exportType.js +1 -2
  3. package/dist/index.js +25 -30
  4. package/dist/lib/fileUpload.d.ts.map +1 -1
  5. package/dist/lib/fileUpload.js +31 -29
  6. package/dist/lib/googleCloudStorage.js +9 -14
  7. package/dist/lib/prisma.js +4 -7
  8. package/dist/lib/thumbnailGenerator.js +12 -20
  9. package/dist/middleware/auth.d.ts.map +1 -1
  10. package/dist/middleware/auth.js +17 -22
  11. package/dist/middleware/logging.js +5 -9
  12. package/dist/routers/_app.d.ts +3483 -1801
  13. package/dist/routers/_app.d.ts.map +1 -1
  14. package/dist/routers/_app.js +28 -27
  15. package/dist/routers/agenda.d.ts +13 -8
  16. package/dist/routers/agenda.d.ts.map +1 -1
  17. package/dist/routers/agenda.js +14 -17
  18. package/dist/routers/announcement.d.ts +4 -3
  19. package/dist/routers/announcement.d.ts.map +1 -1
  20. package/dist/routers/announcement.js +28 -31
  21. package/dist/routers/assignment.d.ts +282 -196
  22. package/dist/routers/assignment.d.ts.map +1 -1
  23. package/dist/routers/assignment.js +256 -202
  24. package/dist/routers/attendance.d.ts +5 -4
  25. package/dist/routers/attendance.d.ts.map +1 -1
  26. package/dist/routers/attendance.js +31 -34
  27. package/dist/routers/auth.d.ts +1 -0
  28. package/dist/routers/auth.d.ts.map +1 -1
  29. package/dist/routers/auth.js +80 -75
  30. package/dist/routers/class.d.ts +284 -14
  31. package/dist/routers/class.d.ts.map +1 -1
  32. package/dist/routers/class.js +435 -164
  33. package/dist/routers/event.d.ts +47 -38
  34. package/dist/routers/event.d.ts.map +1 -1
  35. package/dist/routers/event.js +76 -79
  36. package/dist/routers/file.d.ts +71 -1
  37. package/dist/routers/file.d.ts.map +1 -1
  38. package/dist/routers/file.js +267 -32
  39. package/dist/routers/folder.d.ts +296 -0
  40. package/dist/routers/folder.d.ts.map +1 -0
  41. package/dist/routers/folder.js +693 -0
  42. package/dist/routers/notifications.d.ts +103 -0
  43. package/dist/routers/notifications.d.ts.map +1 -0
  44. package/dist/routers/notifications.js +91 -0
  45. package/dist/routers/school.d.ts +208 -0
  46. package/dist/routers/school.d.ts.map +1 -0
  47. package/dist/routers/school.js +481 -0
  48. package/dist/routers/section.d.ts +1 -0
  49. package/dist/routers/section.d.ts.map +1 -1
  50. package/dist/routers/section.js +30 -33
  51. package/dist/routers/user.d.ts +2 -1
  52. package/dist/routers/user.d.ts.map +1 -1
  53. package/dist/routers/user.js +21 -24
  54. package/dist/seedDatabase.d.ts +22 -0
  55. package/dist/seedDatabase.d.ts.map +1 -0
  56. package/dist/seedDatabase.js +57 -0
  57. package/dist/socket/handlers.js +26 -30
  58. package/dist/trpc.d.ts +5 -0
  59. package/dist/trpc.d.ts.map +1 -1
  60. package/dist/trpc.js +35 -26
  61. package/dist/types/trpc.js +1 -2
  62. package/dist/utils/email.js +2 -8
  63. package/dist/utils/generateInviteCode.js +1 -5
  64. package/dist/utils/logger.d.ts.map +1 -1
  65. package/dist/utils/logger.js +13 -9
  66. package/dist/utils/prismaErrorHandler.d.ts +9 -0
  67. package/dist/utils/prismaErrorHandler.d.ts.map +1 -0
  68. package/dist/utils/prismaErrorHandler.js +234 -0
  69. package/dist/utils/prismaWrapper.d.ts +14 -0
  70. package/dist/utils/prismaWrapper.d.ts.map +1 -0
  71. package/dist/utils/prismaWrapper.js +64 -0
  72. package/package.json +11 -4
  73. package/prisma/migrations/20250807062924_init/migration.sql +436 -0
  74. package/prisma/migrations/migration_lock.toml +3 -0
  75. package/prisma/schema.prisma +67 -0
  76. package/src/index.ts +2 -2
  77. package/src/lib/fileUpload.ts +16 -7
  78. package/src/middleware/auth.ts +0 -2
  79. package/src/routers/_app.ts +5 -1
  80. package/src/routers/assignment.ts +82 -22
  81. package/src/routers/auth.ts +80 -54
  82. package/src/routers/class.ts +330 -36
  83. package/src/routers/file.ts +283 -20
  84. package/src/routers/folder.ts +755 -0
  85. package/src/routers/notifications.ts +93 -0
  86. package/src/seedDatabase.ts +66 -0
  87. package/src/socket/handlers.ts +4 -4
  88. package/src/trpc.ts +13 -0
  89. package/src/utils/logger.ts +14 -4
  90. package/src/utils/prismaErrorHandler.ts +275 -0
  91. package/src/utils/prismaWrapper.ts +91 -0
  92. package/tests/auth.test.ts +25 -0
  93. package/tests/class.test.ts +281 -0
  94. package/tests/setup.ts +98 -0
  95. package/tests/startup.test.ts +5 -0
  96. package/tsconfig.json +2 -1
  97. package/vitest.config.ts +11 -0
  98. package/dist/logger.d.ts +0 -26
  99. package/dist/logger.d.ts.map +0 -1
  100. package/dist/logger.js +0 -135
  101. package/src/logger.ts +0 -163
@@ -1,16 +1,13 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.classRouter = void 0;
4
- const zod_1 = require("zod");
5
- const trpc_1 = require("../trpc");
6
- const prisma_1 = require("../lib/prisma");
7
- const server_1 = require("@trpc/server");
8
- const generateInviteCode_1 = require("../utils/generateInviteCode");
9
- exports.classRouter = (0, trpc_1.createTRPCRouter)({
10
- getAll: trpc_1.protectedProcedure
1
+ import { z } from "zod";
2
+ import { createTRPCRouter, protectedProcedure, protectedTeacherProcedure, protectedClassMemberProcedure } from "../trpc";
3
+ import { prisma } from "../lib/prisma";
4
+ import { TRPCError } from "@trpc/server";
5
+ import { generateInviteCode } from "../utils/generateInviteCode";
6
+ export const classRouter = createTRPCRouter({
7
+ getAll: protectedProcedure
11
8
  .query(async ({ ctx }) => {
12
9
  const [teacherClasses, studentClasses] = await Promise.all([
13
- prisma_1.prisma.class.findMany({
10
+ prisma.class.findMany({
14
11
  where: {
15
12
  teachers: {
16
13
  some: {
@@ -22,17 +19,19 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
22
19
  assignments: {
23
20
  where: {
24
21
  dueDate: {
25
- equals: new Date(new Date().setHours(0, 0, 0, 0)),
22
+ lte: new Date(new Date().setHours(23, 59, 59, 999)),
26
23
  },
24
+ template: false,
27
25
  },
28
26
  select: {
29
27
  id: true,
30
28
  title: true,
29
+ type: true,
31
30
  },
32
31
  },
33
32
  },
34
33
  }),
35
- prisma_1.prisma.class.findMany({
34
+ prisma.class.findMany({
36
35
  where: {
37
36
  students: {
38
37
  some: {
@@ -44,39 +43,19 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
44
43
  assignments: {
45
44
  where: {
46
45
  dueDate: {
47
- equals: new Date(new Date().setHours(0, 0, 0, 0)),
46
+ lte: new Date(new Date().setHours(23, 59, 59, 999)),
48
47
  },
48
+ template: false,
49
49
  },
50
50
  select: {
51
51
  id: true,
52
52
  title: true,
53
+ type: true,
53
54
  },
54
55
  },
55
56
  },
56
57
  }),
57
58
  ]);
58
- const adminClasses = await prisma_1.prisma.class.findMany({
59
- where: {
60
- teachers: {
61
- some: {
62
- id: ctx.user?.id,
63
- },
64
- },
65
- },
66
- include: {
67
- assignments: {
68
- where: {
69
- dueDate: {
70
- equals: new Date(new Date().setHours(0, 0, 0, 0)),
71
- },
72
- },
73
- select: {
74
- id: true,
75
- title: true,
76
- },
77
- },
78
- },
79
- });
80
59
  return {
81
60
  teacherInClass: teacherClasses.map(cls => ({
82
61
  id: cls.id,
@@ -84,6 +63,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
84
63
  section: cls.section,
85
64
  subject: cls.subject,
86
65
  dueToday: cls.assignments,
66
+ assignments: cls.assignments,
87
67
  color: cls.color,
88
68
  })),
89
69
  studentInClass: studentClasses.map(cls => ({
@@ -92,25 +72,18 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
92
72
  section: cls.section,
93
73
  subject: cls.subject,
94
74
  dueToday: cls.assignments,
95
- color: cls.color,
96
- })),
97
- adminInClass: adminClasses.map(cls => ({
98
- id: cls.id,
99
- name: cls.name,
100
- section: cls.section,
101
- subject: cls.subject,
102
- dueToday: cls.assignments,
75
+ assignments: cls.assignments,
103
76
  color: cls.color,
104
77
  })),
105
78
  };
106
79
  }),
107
- get: trpc_1.protectedProcedure
108
- .input(zod_1.z.object({
109
- classId: zod_1.z.string(),
80
+ get: protectedProcedure
81
+ .input(z.object({
82
+ classId: z.string(),
110
83
  }))
111
84
  .query(async ({ ctx, input }) => {
112
85
  const { classId } = input;
113
- const classData = await prisma_1.prisma.class.findUnique({
86
+ const classData = await prisma.class.findUnique({
114
87
  where: {
115
88
  id: classId,
116
89
  },
@@ -154,6 +127,8 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
154
127
  graded: true,
155
128
  maxGrade: true,
156
129
  instructions: true,
130
+ inProgress: true,
131
+ template: false,
157
132
  section: {
158
133
  select: {
159
134
  id: true,
@@ -188,7 +163,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
188
163
  },
189
164
  },
190
165
  });
191
- const sections = await prisma_1.prisma.section.findMany({
166
+ const sections = await prisma.section.findMany({
192
167
  where: {
193
168
  classId: classId,
194
169
  },
@@ -209,16 +184,16 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
209
184
  },
210
185
  };
211
186
  }),
212
- update: trpc_1.protectedTeacherProcedure
213
- .input(zod_1.z.object({
214
- classId: zod_1.z.string(),
215
- name: zod_1.z.string().optional(),
216
- section: zod_1.z.string().optional(),
217
- subject: zod_1.z.string().optional(),
187
+ update: protectedTeacherProcedure
188
+ .input(z.object({
189
+ classId: z.string(),
190
+ name: z.string().optional(),
191
+ section: z.string().optional(),
192
+ subject: z.string().optional(),
218
193
  }))
219
194
  .mutation(async ({ ctx, input }) => {
220
195
  const { classId, ...updateData } = input;
221
- const updatedClass = await prisma_1.prisma.class.update({
196
+ const updatedClass = await prisma.class.update({
222
197
  where: {
223
198
  id: classId,
224
199
  },
@@ -234,22 +209,24 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
234
209
  updatedClass,
235
210
  };
236
211
  }),
237
- create: trpc_1.protectedProcedure
238
- .input(zod_1.z.object({
239
- students: zod_1.z.array(zod_1.z.string()).optional(),
240
- teachers: zod_1.z.array(zod_1.z.string()).optional(),
241
- name: zod_1.z.string(),
242
- section: zod_1.z.string(),
243
- subject: zod_1.z.string(),
212
+ create: protectedProcedure
213
+ .input(z.object({
214
+ students: z.array(z.string()).optional(),
215
+ teachers: z.array(z.string()).optional(),
216
+ name: z.string(),
217
+ section: z.string(),
218
+ subject: z.string(),
219
+ color: z.string().optional(),
244
220
  }))
245
221
  .mutation(async ({ ctx, input }) => {
246
- const { students, teachers, name, section, subject } = input;
222
+ const { students, teachers, name, section, subject, color } = input;
247
223
  if (teachers && teachers.length > 0 && students && students.length > 0) {
248
- const newClass = await prisma_1.prisma.class.create({
224
+ const newClass = await prisma.class.create({
249
225
  data: {
250
226
  name,
251
227
  section,
252
228
  subject,
229
+ color,
253
230
  teachers: {
254
231
  connect: teachers.map(teacher => ({ id: teacher })),
255
232
  },
@@ -264,11 +241,12 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
264
241
  });
265
242
  return newClass;
266
243
  }
267
- const newClass = await prisma_1.prisma.class.create({
244
+ const newClass = await prisma.class.create({
268
245
  data: {
269
246
  name,
270
247
  section,
271
248
  subject,
249
+ color,
272
250
  teachers: {
273
251
  connect: {
274
252
  id: ctx.user?.id,
@@ -278,14 +256,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
278
256
  });
279
257
  return newClass;
280
258
  }),
281
- delete: trpc_1.protectedTeacherProcedure
282
- .input(zod_1.z.object({
283
- classId: zod_1.z.string(),
284
- id: zod_1.z.string(),
259
+ delete: protectedTeacherProcedure
260
+ .input(z.object({
261
+ classId: z.string(),
262
+ id: z.string(),
285
263
  }))
286
264
  .mutation(async ({ ctx, input }) => {
287
265
  // Verify user is the teacher of this class
288
- const classToDelete = await prisma_1.prisma.class.findFirst({
266
+ const classToDelete = await prisma.class.findFirst({
289
267
  where: {
290
268
  id: input.id,
291
269
  },
@@ -293,7 +271,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
293
271
  if (!classToDelete) {
294
272
  throw new Error("Class not found or you don't have permission to delete it");
295
273
  }
296
- await prisma_1.prisma.class.delete({
274
+ await prisma.class.delete({
297
275
  where: {
298
276
  id: input.id,
299
277
  },
@@ -304,14 +282,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
304
282
  }
305
283
  };
306
284
  }),
307
- addStudent: trpc_1.protectedTeacherProcedure
308
- .input(zod_1.z.object({
309
- classId: zod_1.z.string(),
310
- studentId: zod_1.z.string(),
285
+ addStudent: protectedTeacherProcedure
286
+ .input(z.object({
287
+ classId: z.string(),
288
+ studentId: z.string(),
311
289
  }))
312
290
  .mutation(async ({ ctx, input }) => {
313
291
  const { classId, studentId } = input;
314
- const student = await prisma_1.prisma.user.findUnique({
292
+ const student = await prisma.user.findUnique({
315
293
  where: {
316
294
  id: studentId,
317
295
  },
@@ -319,7 +297,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
319
297
  if (!student) {
320
298
  throw new Error("Student not found");
321
299
  }
322
- const updatedClass = await prisma_1.prisma.class.update({
300
+ const updatedClass = await prisma.class.update({
323
301
  where: {
324
302
  id: classId,
325
303
  },
@@ -342,15 +320,15 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
342
320
  newStudent: student,
343
321
  };
344
322
  }),
345
- changeRole: trpc_1.protectedTeacherProcedure
346
- .input(zod_1.z.object({
347
- classId: zod_1.z.string(),
348
- userId: zod_1.z.string(),
349
- type: zod_1.z.enum(['teacher', 'student']),
323
+ changeRole: protectedTeacherProcedure
324
+ .input(z.object({
325
+ classId: z.string(),
326
+ userId: z.string(),
327
+ type: z.enum(['teacher', 'student']),
350
328
  }))
351
329
  .mutation(async ({ ctx, input }) => {
352
330
  const { classId, userId, type } = input;
353
- const user = await prisma_1.prisma.user.findUnique({
331
+ const user = await prisma.user.findUnique({
354
332
  where: { id: userId },
355
333
  select: {
356
334
  id: true,
@@ -360,7 +338,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
360
338
  if (!user) {
361
339
  throw new Error("User not found");
362
340
  }
363
- const updatedClass = await prisma_1.prisma.class.update({
341
+ const updatedClass = await prisma.class.update({
364
342
  where: { id: classId },
365
343
  data: {
366
344
  [type === 'teacher' ? 'teachers' : 'students']: {
@@ -379,14 +357,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
379
357
  },
380
358
  };
381
359
  }),
382
- removeMember: trpc_1.protectedTeacherProcedure
383
- .input(zod_1.z.object({
384
- classId: zod_1.z.string(),
385
- userId: zod_1.z.string(),
360
+ removeMember: protectedTeacherProcedure
361
+ .input(z.object({
362
+ classId: z.string(),
363
+ userId: z.string(),
386
364
  }))
387
365
  .mutation(async ({ ctx, input }) => {
388
366
  const { classId, userId } = input;
389
- const updatedClass = await prisma_1.prisma.class.update({
367
+ const updatedClass = await prisma.class.update({
390
368
  where: { id: classId },
391
369
  data: {
392
370
  teachers: {
@@ -402,13 +380,13 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
402
380
  removedUserId: userId,
403
381
  };
404
382
  }),
405
- join: trpc_1.protectedProcedure
406
- .input(zod_1.z.object({
407
- classCode: zod_1.z.string(),
383
+ join: protectedProcedure
384
+ .input(z.object({
385
+ classCode: z.string(),
408
386
  }))
409
387
  .mutation(async ({ ctx, input }) => {
410
388
  const { classCode } = input;
411
- const session = await prisma_1.prisma.session.findFirst({
389
+ const session = await prisma.session.findFirst({
412
390
  where: {
413
391
  id: classCode,
414
392
  },
@@ -419,7 +397,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
419
397
  if (session.expiresAt && session.expiresAt < new Date()) {
420
398
  throw new Error("Session expired");
421
399
  }
422
- const updatedClass = await prisma_1.prisma.class.update({
400
+ const updatedClass = await prisma.class.update({
423
401
  where: { id: session.classId },
424
402
  data: {
425
403
  students: {
@@ -437,21 +415,21 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
437
415
  joinedClass: updatedClass,
438
416
  };
439
417
  }),
440
- getInviteCode: trpc_1.protectedTeacherProcedure
441
- .input(zod_1.z.object({
442
- classId: zod_1.z.string(),
418
+ getInviteCode: protectedTeacherProcedure
419
+ .input(z.object({
420
+ classId: z.string(),
443
421
  }))
444
422
  .query(async ({ ctx, input }) => {
445
423
  const { classId } = input;
446
- const session = await prisma_1.prisma.session.findFirst({
424
+ const session = await prisma.session.findFirst({
447
425
  where: {
448
426
  classId,
449
427
  },
450
428
  });
451
429
  if ((session?.expiresAt && session.expiresAt < new Date()) || !session) {
452
- const newSession = await prisma_1.prisma.session.create({
430
+ const newSession = await prisma.session.create({
453
431
  data: {
454
- id: (0, generateInviteCode_1.generateInviteCode)(),
432
+ id: generateInviteCode(),
455
433
  classId,
456
434
  expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
457
435
  }
@@ -464,16 +442,16 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
464
442
  code: session?.id,
465
443
  };
466
444
  }),
467
- createInviteCode: trpc_1.protectedTeacherProcedure
468
- .input(zod_1.z.object({
469
- classId: zod_1.z.string(),
445
+ createInviteCode: protectedTeacherProcedure
446
+ .input(z.object({
447
+ classId: z.string(),
470
448
  }))
471
449
  .mutation(async ({ ctx, input }) => {
472
450
  const { classId } = input;
473
451
  // Create a new session for the invite code
474
- const session = await prisma_1.prisma.session.create({
452
+ const session = await prisma.session.create({
475
453
  data: {
476
- id: (0, generateInviteCode_1.generateInviteCode)(),
454
+ id: generateInviteCode(),
477
455
  classId,
478
456
  expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
479
457
  }
@@ -482,14 +460,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
482
460
  code: session.id,
483
461
  };
484
462
  }),
485
- getGrades: trpc_1.protectedClassMemberProcedure
486
- .input(zod_1.z.object({
487
- classId: zod_1.z.string(),
488
- userId: zod_1.z.string(),
463
+ getGrades: protectedClassMemberProcedure
464
+ .input(z.object({
465
+ classId: z.string(),
466
+ userId: z.string(),
489
467
  }))
490
468
  .query(async ({ ctx, input }) => {
491
469
  const { classId, userId } = input;
492
- const isTeacher = await prisma_1.prisma.class.findFirst({
470
+ const isTeacher = await prisma.class.findFirst({
493
471
  where: {
494
472
  id: classId,
495
473
  teachers: {
@@ -499,12 +477,12 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
499
477
  });
500
478
  // If student, only allow viewing their own grades
501
479
  if (ctx.user?.id !== userId && !isTeacher) {
502
- throw new server_1.TRPCError({
480
+ throw new TRPCError({
503
481
  code: 'UNAUTHORIZED',
504
482
  message: 'You can only view your own grades',
505
483
  });
506
484
  }
507
- const grades = await prisma_1.prisma.submission.findMany({
485
+ const grades = await prisma.submission.findMany({
508
486
  where: {
509
487
  studentId: userId,
510
488
  assignment: {
@@ -527,17 +505,17 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
527
505
  grades,
528
506
  };
529
507
  }),
530
- updateGrade: trpc_1.protectedTeacherProcedure
531
- .input(zod_1.z.object({
532
- classId: zod_1.z.string(),
533
- assignmentId: zod_1.z.string(),
534
- submissionId: zod_1.z.string(),
535
- gradeReceived: zod_1.z.number().nullable(),
508
+ updateGrade: protectedTeacherProcedure
509
+ .input(z.object({
510
+ classId: z.string(),
511
+ assignmentId: z.string(),
512
+ submissionId: z.string(),
513
+ gradeReceived: z.number().nullable(),
536
514
  }))
537
515
  .mutation(async ({ ctx, input }) => {
538
516
  const { classId, assignmentId, submissionId, gradeReceived } = input;
539
517
  // Update the grade
540
- const updatedSubmission = await prisma_1.prisma.submission.update({
518
+ const updatedSubmission = await prisma.submission.update({
541
519
  where: {
542
520
  id: submissionId,
543
521
  assignmentId: assignmentId,
@@ -558,13 +536,13 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
558
536
  });
559
537
  return updatedSubmission;
560
538
  }),
561
- getEvents: trpc_1.protectedTeacherProcedure
562
- .input(zod_1.z.object({
563
- classId: zod_1.z.string(),
539
+ getEvents: protectedTeacherProcedure
540
+ .input(z.object({
541
+ classId: z.string(),
564
542
  }))
565
543
  .query(async ({ ctx, input }) => {
566
544
  const { classId } = input;
567
- const events = await prisma_1.prisma.event.findMany({
545
+ const events = await prisma.event.findMany({
568
546
  where: {
569
547
  class: {
570
548
  id: classId,
@@ -578,28 +556,28 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
578
556
  });
579
557
  return events;
580
558
  }),
581
- listMarkSchemes: trpc_1.protectedTeacherProcedure
582
- .input(zod_1.z.object({
583
- classId: zod_1.z.string(),
559
+ listMarkSchemes: protectedTeacherProcedure
560
+ .input(z.object({
561
+ classId: z.string(),
584
562
  }))
585
563
  .query(async ({ ctx, input }) => {
586
564
  const { classId } = input;
587
- const markSchemes = await prisma_1.prisma.markScheme.findMany({
565
+ const markSchemes = await prisma.markScheme.findMany({
588
566
  where: {
589
567
  classId: classId,
590
568
  },
591
569
  });
592
570
  return markSchemes;
593
571
  }),
594
- createMarkScheme: trpc_1.protectedTeacherProcedure
595
- .input(zod_1.z.object({
596
- classId: zod_1.z.string(),
597
- structure: zod_1.z.string(),
572
+ createMarkScheme: protectedTeacherProcedure
573
+ .input(z.object({
574
+ classId: z.string(),
575
+ structure: z.string(),
598
576
  }))
599
577
  .mutation(async ({ ctx, input }) => {
600
578
  const { classId, structure } = input;
601
579
  const validatedStructure = structure.replace(/\\n/g, '\n');
602
- const markScheme = await prisma_1.prisma.markScheme.create({
580
+ const markScheme = await prisma.markScheme.create({
603
581
  data: {
604
582
  classId: classId,
605
583
  structured: validatedStructure,
@@ -607,55 +585,55 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
607
585
  });
608
586
  return markScheme;
609
587
  }),
610
- updateMarkScheme: trpc_1.protectedTeacherProcedure
611
- .input(zod_1.z.object({
612
- classId: zod_1.z.string(),
613
- markSchemeId: zod_1.z.string(),
614
- structure: zod_1.z.string(),
588
+ updateMarkScheme: protectedTeacherProcedure
589
+ .input(z.object({
590
+ classId: z.string(),
591
+ markSchemeId: z.string(),
592
+ structure: z.string(),
615
593
  }))
616
594
  .mutation(async ({ ctx, input }) => {
617
595
  const { classId, markSchemeId, structure } = input;
618
596
  const validatedStructure = structure.replace(/\\n/g, '\n');
619
- const markScheme = await prisma_1.prisma.markScheme.update({
597
+ const markScheme = await prisma.markScheme.update({
620
598
  where: { id: markSchemeId },
621
599
  data: { structured: validatedStructure },
622
600
  });
623
601
  return markScheme;
624
602
  }),
625
- deleteMarkScheme: trpc_1.protectedTeacherProcedure
626
- .input(zod_1.z.object({
627
- classId: zod_1.z.string(),
628
- markSchemeId: zod_1.z.string(),
603
+ deleteMarkScheme: protectedTeacherProcedure
604
+ .input(z.object({
605
+ classId: z.string(),
606
+ markSchemeId: z.string(),
629
607
  }))
630
608
  .mutation(async ({ ctx, input }) => {
631
609
  const { classId, markSchemeId } = input;
632
- const markScheme = await prisma_1.prisma.markScheme.delete({
610
+ const markScheme = await prisma.markScheme.delete({
633
611
  where: { id: markSchemeId },
634
612
  });
635
613
  return markScheme;
636
614
  }),
637
- listGradingBoundaries: trpc_1.protectedTeacherProcedure
638
- .input(zod_1.z.object({
639
- classId: zod_1.z.string(),
615
+ listGradingBoundaries: protectedTeacherProcedure
616
+ .input(z.object({
617
+ classId: z.string(),
640
618
  }))
641
619
  .query(async ({ ctx, input }) => {
642
620
  const { classId } = input;
643
- const gradingBoundaries = await prisma_1.prisma.gradingBoundary.findMany({
621
+ const gradingBoundaries = await prisma.gradingBoundary.findMany({
644
622
  where: {
645
623
  classId: classId,
646
624
  },
647
625
  });
648
626
  return gradingBoundaries;
649
627
  }),
650
- createGradingBoundary: trpc_1.protectedTeacherProcedure
651
- .input(zod_1.z.object({
652
- classId: zod_1.z.string(),
653
- structure: zod_1.z.string(),
628
+ createGradingBoundary: protectedTeacherProcedure
629
+ .input(z.object({
630
+ classId: z.string(),
631
+ structure: z.string(),
654
632
  }))
655
633
  .mutation(async ({ ctx, input }) => {
656
634
  const { classId, structure } = input;
657
635
  const validatedStructure = structure.replace(/\\n/g, '\n');
658
- const gradingBoundary = await prisma_1.prisma.gradingBoundary.create({
636
+ const gradingBoundary = await prisma.gradingBoundary.create({
659
637
  data: {
660
638
  classId: classId,
661
639
  structured: validatedStructure,
@@ -663,31 +641,324 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
663
641
  });
664
642
  return gradingBoundary;
665
643
  }),
666
- updateGradingBoundary: trpc_1.protectedTeacherProcedure
667
- .input(zod_1.z.object({
668
- classId: zod_1.z.string(),
669
- gradingBoundaryId: zod_1.z.string(),
670
- structure: zod_1.z.string(),
644
+ updateGradingBoundary: protectedTeacherProcedure
645
+ .input(z.object({
646
+ classId: z.string(),
647
+ gradingBoundaryId: z.string(),
648
+ structure: z.string(),
671
649
  }))
672
650
  .mutation(async ({ ctx, input }) => {
673
651
  const { classId, gradingBoundaryId, structure } = input;
674
652
  const validatedStructure = structure.replace(/\\n/g, '\n');
675
- const gradingBoundary = await prisma_1.prisma.gradingBoundary.update({
653
+ const gradingBoundary = await prisma.gradingBoundary.update({
676
654
  where: { id: gradingBoundaryId },
677
655
  data: { structured: validatedStructure },
678
656
  });
679
657
  return gradingBoundary;
680
658
  }),
681
- deleteGradingBoundary: trpc_1.protectedTeacherProcedure
682
- .input(zod_1.z.object({
683
- classId: zod_1.z.string(),
684
- gradingBoundaryId: zod_1.z.string(),
659
+ deleteGradingBoundary: protectedTeacherProcedure
660
+ .input(z.object({
661
+ classId: z.string(),
662
+ gradingBoundaryId: z.string(),
685
663
  }))
686
664
  .mutation(async ({ ctx, input }) => {
687
665
  const { classId, gradingBoundaryId } = input;
688
- const gradingBoundary = await prisma_1.prisma.gradingBoundary.delete({
666
+ const gradingBoundary = await prisma.gradingBoundary.delete({
689
667
  where: { id: gradingBoundaryId },
690
668
  });
691
669
  return gradingBoundary;
692
670
  }),
671
+ getSyllabus: protectedClassMemberProcedure
672
+ .input(z.object({
673
+ classId: z.string(),
674
+ }))
675
+ .query(async ({ input }) => {
676
+ const { classId } = input;
677
+ const syllabus = (await prisma.class.findUnique({
678
+ where: {
679
+ id: classId,
680
+ },
681
+ }))?.syllabus;
682
+ const markSchemes = await prisma.markScheme.findMany({
683
+ where: {
684
+ classId,
685
+ }
686
+ });
687
+ const gradingBoundaries = await prisma.gradingBoundary.findMany({
688
+ where: {
689
+ classId,
690
+ }
691
+ });
692
+ return { syllabus, gradingBoundaries, markSchemes };
693
+ }),
694
+ updateSyllabus: protectedTeacherProcedure
695
+ .input(z.object({
696
+ classId: z.string(),
697
+ contents: z.string(),
698
+ }))
699
+ .mutation(async ({ input }) => {
700
+ const { contents, classId } = input;
701
+ if (!contents)
702
+ throw new TRPCError({
703
+ code: 'BAD_REQUEST',
704
+ message: "Missing key contents",
705
+ });
706
+ const updated = await prisma.class.update({
707
+ where: {
708
+ id: classId
709
+ },
710
+ data: {
711
+ syllabus: contents,
712
+ }
713
+ });
714
+ return updated;
715
+ }),
716
+ // Lab Management Endpoints (Assignment-based)
717
+ listLabDrafts: protectedTeacherProcedure
718
+ .input(z.object({
719
+ classId: z.string(),
720
+ }))
721
+ .query(async ({ ctx, input }) => {
722
+ const { classId } = input;
723
+ const labDrafts = await prisma.assignment.findMany({
724
+ where: {
725
+ classId: classId,
726
+ teacherId: ctx.user?.id,
727
+ inProgress: true,
728
+ },
729
+ orderBy: {
730
+ modifiedAt: 'desc',
731
+ },
732
+ });
733
+ return labDrafts;
734
+ }),
735
+ createLabDraft: protectedTeacherProcedure
736
+ .input(z.object({
737
+ classId: z.string(),
738
+ title: z.string(),
739
+ type: z.enum(['LAB', 'HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'OTHER']),
740
+ instructions: z.string(),
741
+ dueDate: z.date().optional(),
742
+ maxGrade: z.number().optional(),
743
+ weight: z.number().optional(),
744
+ graded: z.boolean().optional(),
745
+ sectionId: z.string().optional(),
746
+ markSchemeId: z.string().optional(),
747
+ gradingBoundaryId: z.string().optional(),
748
+ }))
749
+ .mutation(async ({ ctx, input }) => {
750
+ const { classId, ...draftData } = input;
751
+ const labDraft = await prisma.assignment.create({
752
+ data: {
753
+ classId: classId,
754
+ teacherId: ctx.user?.id,
755
+ inProgress: true,
756
+ graded: draftData.graded ?? false,
757
+ maxGrade: draftData.maxGrade ?? 0,
758
+ weight: draftData.weight ?? 1,
759
+ dueDate: draftData.dueDate || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Default 1 week from now
760
+ title: draftData.title,
761
+ instructions: draftData.instructions,
762
+ type: draftData.type,
763
+ ...(draftData.sectionId && { sectionId: draftData.sectionId }),
764
+ ...(draftData.markSchemeId && { markSchemeId: draftData.markSchemeId }),
765
+ ...(draftData.gradingBoundaryId && { gradingBoundaryId: draftData.gradingBoundaryId }),
766
+ },
767
+ });
768
+ return labDraft;
769
+ }),
770
+ updateLabDraft: protectedTeacherProcedure
771
+ .input(z.object({
772
+ classId: z.string(),
773
+ draftId: z.string(),
774
+ title: z.string().optional(),
775
+ instructions: z.string().optional(),
776
+ dueDate: z.date().optional(),
777
+ maxGrade: z.number().optional(),
778
+ weight: z.number().optional(),
779
+ graded: z.boolean().optional(),
780
+ sectionId: z.string().optional(),
781
+ markSchemeId: z.string().optional(),
782
+ gradingBoundaryId: z.string().optional(),
783
+ }))
784
+ .mutation(async ({ ctx, input }) => {
785
+ const { classId, draftId, ...updateData } = input;
786
+ const labDraft = await prisma.assignment.update({
787
+ where: {
788
+ id: draftId,
789
+ classId: classId,
790
+ teacherId: ctx.user?.id,
791
+ inProgress: true,
792
+ },
793
+ data: {
794
+ ...(updateData.title && { title: updateData.title }),
795
+ ...(updateData.instructions && { instructions: updateData.instructions }),
796
+ ...(updateData.dueDate && { dueDate: updateData.dueDate }),
797
+ ...(updateData.maxGrade !== undefined && { maxGrade: updateData.maxGrade }),
798
+ ...(updateData.weight !== undefined && { weight: updateData.weight }),
799
+ ...(updateData.graded !== undefined && { graded: updateData.graded }),
800
+ ...(updateData.sectionId !== undefined && { sectionId: updateData.sectionId }),
801
+ ...(updateData.markSchemeId !== undefined && { markSchemeId: updateData.markSchemeId }),
802
+ ...(updateData.gradingBoundaryId !== undefined && { gradingBoundaryId: updateData.gradingBoundaryId }),
803
+ modifiedAt: new Date(),
804
+ },
805
+ });
806
+ return labDraft;
807
+ }),
808
+ deleteLabDraft: protectedTeacherProcedure
809
+ .input(z.object({
810
+ classId: z.string(),
811
+ draftId: z.string(),
812
+ }))
813
+ .mutation(async ({ ctx, input }) => {
814
+ const { classId, draftId } = input;
815
+ const labDraft = await prisma.assignment.delete({
816
+ where: {
817
+ id: draftId,
818
+ classId: classId,
819
+ teacherId: ctx.user?.id,
820
+ inProgress: true,
821
+ },
822
+ });
823
+ return labDraft;
824
+ }),
825
+ publishLabDraft: protectedTeacherProcedure
826
+ .input(z.object({
827
+ classId: z.string(),
828
+ draftId: z.string(),
829
+ dueDate: z.date().optional(),
830
+ maxGrade: z.number().optional(),
831
+ weight: z.number().optional(),
832
+ graded: z.boolean().optional(),
833
+ }))
834
+ .mutation(async ({ ctx, input }) => {
835
+ const { classId, draftId, ...publishData } = input;
836
+ // Get the lab draft
837
+ const labDraft = await prisma.assignment.findUnique({
838
+ where: {
839
+ id: draftId,
840
+ classId: classId,
841
+ teacherId: ctx.user?.id,
842
+ inProgress: true,
843
+ },
844
+ });
845
+ if (!labDraft) {
846
+ throw new TRPCError({
847
+ code: 'NOT_FOUND',
848
+ message: 'Lab draft not found',
849
+ });
850
+ }
851
+ // Publish the draft by updating it to not be in progress
852
+ const publishedAssignment = await prisma.assignment.update({
853
+ where: { id: draftId },
854
+ data: {
855
+ inProgress: false,
856
+ dueDate: publishData.dueDate || labDraft.dueDate,
857
+ maxGrade: publishData.maxGrade || labDraft.maxGrade || 100,
858
+ weight: publishData.weight || labDraft.weight || 1,
859
+ graded: publishData.graded !== undefined ? publishData.graded : true,
860
+ modifiedAt: new Date(),
861
+ },
862
+ });
863
+ return publishedAssignment;
864
+ }),
865
+ getFiles: protectedClassMemberProcedure
866
+ .input(z.object({
867
+ classId: z.string(),
868
+ }))
869
+ .query(async ({ ctx, input }) => {
870
+ const { classId } = input;
871
+ // Get all assignments with their files and submissions
872
+ const assignments = await prisma.assignment.findMany({
873
+ where: {
874
+ classId: classId,
875
+ },
876
+ include: {
877
+ attachments: {
878
+ select: {
879
+ id: true,
880
+ name: true,
881
+ type: true,
882
+ size: true,
883
+ path: true,
884
+ thumbnailId: true,
885
+ uploadedAt: true,
886
+ user: {
887
+ select: {
888
+ id: true,
889
+ username: true,
890
+ },
891
+ },
892
+ },
893
+ },
894
+ submissions: {
895
+ include: {
896
+ attachments: {
897
+ select: {
898
+ id: true,
899
+ name: true,
900
+ type: true,
901
+ size: true,
902
+ path: true,
903
+ thumbnailId: true,
904
+ uploadedAt: true,
905
+ user: {
906
+ select: {
907
+ id: true,
908
+ username: true,
909
+ },
910
+ },
911
+ },
912
+ },
913
+ annotations: {
914
+ select: {
915
+ id: true,
916
+ name: true,
917
+ type: true,
918
+ size: true,
919
+ path: true,
920
+ thumbnailId: true,
921
+ uploadedAt: true,
922
+ user: {
923
+ select: {
924
+ id: true,
925
+ username: true,
926
+ },
927
+ },
928
+ },
929
+ },
930
+ student: {
931
+ select: {
932
+ id: true,
933
+ username: true,
934
+ },
935
+ },
936
+ },
937
+ },
938
+ teacher: {
939
+ select: {
940
+ id: true,
941
+ username: true,
942
+ },
943
+ },
944
+ },
945
+ orderBy: {
946
+ createdAt: 'desc',
947
+ },
948
+ });
949
+ // Organize files by assignment structure
950
+ const organizedFiles = assignments.map(assignment => ({
951
+ id: assignment.id,
952
+ title: assignment.title,
953
+ teacher: assignment.teacher,
954
+ teacherAttachments: assignment.attachments,
955
+ students: assignment.submissions.map(submission => ({
956
+ id: submission.student.id,
957
+ username: submission.student.username,
958
+ attachments: submission.attachments,
959
+ annotations: submission.annotations,
960
+ })),
961
+ }));
962
+ return organizedFiles;
963
+ }),
693
964
  });