@studious-lms/server 1.2.34 → 1.2.36

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.
@@ -38,7 +38,7 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
38
38
  order: number | null;
39
39
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
40
40
  updatedAt: Date;
41
- points: number | null;
41
+ points: number;
42
42
  question: string;
43
43
  worksheetId: string;
44
44
  answer: string;
@@ -128,7 +128,7 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
128
128
  order: number | null;
129
129
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
130
130
  updatedAt: Date;
131
- points: number | null;
131
+ points: number;
132
132
  question: string;
133
133
  worksheetId: string;
134
134
  answer: string;
@@ -166,7 +166,7 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
166
166
  order: number | null;
167
167
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
168
168
  updatedAt: Date;
169
- points: number | null;
169
+ points: number;
170
170
  question: string;
171
171
  worksheetId: string;
172
172
  answer: string;
@@ -186,7 +186,7 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
186
186
  order: number | null;
187
187
  markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
188
188
  updatedAt: Date;
189
- points: number | null;
189
+ points: number;
190
190
  question: string;
191
191
  worksheetId: string;
192
192
  answer: string;
@@ -199,17 +199,47 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
199
199
  worksheetId: string;
200
200
  };
201
201
  output: {
202
- responses: {
202
+ responses: ({
203
+ comments: {
204
+ reactions: {
205
+ type: import(".prisma/client").$Enums.ReactionType;
206
+ user: {
207
+ id: string;
208
+ username: string;
209
+ profile: {
210
+ displayName: string | null;
211
+ profilePicture: string | null;
212
+ profilePictureThumbnail: string | null;
213
+ } | null;
214
+ };
215
+ }[];
216
+ replies: {
217
+ id: string;
218
+ content: string;
219
+ author: {
220
+ id: string;
221
+ username: string;
222
+ profile: {
223
+ displayName: string | null;
224
+ profilePicture: string | null;
225
+ profilePictureThumbnail: string | null;
226
+ } | null;
227
+ };
228
+ }[];
229
+ }[];
230
+ } & {
203
231
  id: string;
204
232
  feedback: string | null;
205
233
  studentId: string;
206
234
  createdAt: Date;
207
- updatedAt: Date;
235
+ updatedAt: Date | null;
236
+ points: number;
208
237
  questionId: string;
209
238
  response: string;
210
- isCorrect: boolean;
211
239
  studentWorksheetResponseId: string | null;
212
- }[];
240
+ isCorrect: boolean;
241
+ markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
242
+ })[];
213
243
  } & {
214
244
  id: string;
215
245
  submissionId: string | null;
@@ -234,11 +264,13 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
234
264
  feedback: string | null;
235
265
  studentId: string;
236
266
  createdAt: Date;
237
- updatedAt: Date;
267
+ updatedAt: Date | null;
268
+ points: number;
238
269
  questionId: string;
239
270
  response: string;
240
- isCorrect: boolean;
241
271
  studentWorksheetResponseId: string | null;
272
+ isCorrect: boolean;
273
+ markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
242
274
  }[];
243
275
  } & {
244
276
  id: string;
@@ -262,55 +294,14 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
262
294
  feedback: string | null;
263
295
  studentId: string;
264
296
  createdAt: Date;
265
- updatedAt: Date;
297
+ updatedAt: Date | null;
298
+ points: number;
266
299
  questionId: string;
267
300
  response: string;
268
- isCorrect: boolean;
269
301
  studentWorksheetResponseId: string | null;
270
- }[];
271
- } & {
272
- id: string;
273
- submissionId: string | null;
274
- studentId: string;
275
- createdAt: Date;
276
- updatedAt: Date;
277
- submittedAt: Date | null;
278
- submitted: boolean;
279
- worksheetId: string;
280
- };
281
- meta: object;
282
- }>;
283
- getOrCreateWorksheetResponse: import("@trpc/server").TRPCMutationProcedure<{
284
- input: {
285
- studentId: string;
286
- worksheetId: string;
287
- };
288
- output: {
289
- responses: ({
290
- question: {
291
- type: import(".prisma/client").$Enums.WorksheetQuestionType;
292
- id: string;
293
- options: import("@prisma/client/runtime/library.js").JsonValue | null;
294
- createdAt: Date;
295
- order: number | null;
296
- markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
297
- updatedAt: Date;
298
- points: number | null;
299
- question: string;
300
- worksheetId: string;
301
- answer: string;
302
- };
303
- } & {
304
- id: string;
305
- feedback: string | null;
306
- studentId: string;
307
- createdAt: Date;
308
- updatedAt: Date;
309
- questionId: string;
310
- response: string;
311
302
  isCorrect: boolean;
312
- studentWorksheetResponseId: string | null;
313
- })[];
303
+ markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
304
+ }[];
314
305
  } & {
315
306
  id: string;
316
307
  submissionId: string | null;
@@ -323,72 +314,34 @@ export declare const worksheetRouter: import("@trpc/server").TRPCBuiltRouter<{
323
314
  };
324
315
  meta: object;
325
316
  }>;
326
- getWorksheetResponse: import("@trpc/server").TRPCQueryProcedure<{
317
+ gradeAnswer: import("@trpc/server").TRPCMutationProcedure<{
327
318
  input: {
328
- worksheetId: string;
319
+ questionId: string;
320
+ studentWorksheetResponseId: string;
321
+ isCorrect: boolean;
322
+ feedback?: string | undefined;
323
+ points?: number | undefined;
324
+ response?: string | undefined;
325
+ responseId?: string | undefined;
326
+ markschemeState?: any;
329
327
  };
330
- output: ({
331
- student: {
332
- id: string;
333
- username: string;
334
- profile: {
335
- displayName: string | null;
336
- profilePicture: string | null;
337
- } | null;
338
- };
339
- responses: ({
340
- question: {
341
- type: import(".prisma/client").$Enums.WorksheetQuestionType;
342
- id: string;
343
- options: import("@prisma/client/runtime/library.js").JsonValue | null;
344
- createdAt: Date;
345
- order: number | null;
346
- markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
347
- updatedAt: Date;
348
- points: number | null;
349
- question: string;
350
- worksheetId: string;
351
- answer: string;
352
- };
353
- } & {
354
- id: string;
355
- feedback: string | null;
356
- studentId: string;
357
- createdAt: Date;
358
- updatedAt: Date;
359
- questionId: string;
360
- response: string;
361
- isCorrect: boolean;
362
- studentWorksheetResponseId: string | null;
363
- })[];
364
- } & {
365
- id: string;
366
- submissionId: string | null;
367
- studentId: string;
368
- createdAt: Date;
369
- updatedAt: Date;
370
- submittedAt: Date | null;
371
- submitted: boolean;
372
- worksheetId: string;
373
- }) | null;
328
+ output: any;
374
329
  meta: object;
375
330
  }>;
376
- gradeAnswer: import("@trpc/server").TRPCMutationProcedure<{
331
+ addComment: import("@trpc/server").TRPCMutationProcedure<{
377
332
  input: {
333
+ comment: string;
378
334
  responseId: string;
379
- isCorrect: boolean;
380
- feedback?: string | undefined;
381
335
  };
382
336
  output: {
383
337
  id: string;
384
- feedback: string | null;
385
- studentId: string;
338
+ content: string;
386
339
  createdAt: Date;
387
- updatedAt: Date;
388
- questionId: string;
389
- response: string;
390
- isCorrect: boolean;
391
- studentWorksheetResponseId: string | null;
340
+ modifiedAt: Date | null;
341
+ announcementId: string | null;
342
+ parentCommentId: string | null;
343
+ authorId: string;
344
+ studentQuestionProgressId: string | null;
392
345
  };
393
346
  meta: object;
394
347
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"worksheet.d.ts","sourceRoot":"","sources":["../../src/routers/worksheet.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBA6KA,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0UhC,CAAC"}
1
+ {"version":3,"file":"worksheet.d.ts","sourceRoot":"","sources":["../../src/routers/worksheet.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBA8KA,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwVhC,CAAC"}
@@ -2,6 +2,7 @@ import { TRPCError } from "@trpc/server";
2
2
  import { createTRPCRouter, protectedProcedure } from "../trpc.js";
3
3
  import { z } from "zod";
4
4
  import { prisma } from "../lib/prisma.js";
5
+ import { commentSelect } from "./comment.js";
5
6
  export const worksheetRouter = createTRPCRouter({
6
7
  // Get a single worksheet with all questions
7
8
  getWorksheet: protectedProcedure
@@ -15,6 +16,7 @@ export const worksheetRouter = createTRPCRouter({
15
16
  include: {
16
17
  questions: {
17
18
  orderBy: { createdAt: 'asc' },
19
+ // select: { id: true, type: true, question: true, answer: true, points: true },
18
20
  },
19
21
  class: true,
20
22
  },
@@ -235,7 +237,13 @@ export const worksheetRouter = createTRPCRouter({
235
237
  worksheetId
236
238
  },
237
239
  include: {
238
- responses: true,
240
+ responses: {
241
+ include: {
242
+ comments: {
243
+ select: commentSelect,
244
+ },
245
+ },
246
+ },
239
247
  },
240
248
  });
241
249
  if (existing) {
@@ -249,7 +257,13 @@ export const worksheetRouter = createTRPCRouter({
249
257
  studentId: submission.studentId,
250
258
  },
251
259
  include: {
252
- responses: true,
260
+ responses: {
261
+ include: {
262
+ comments: {
263
+ select: commentSelect,
264
+ },
265
+ },
266
+ },
253
267
  },
254
268
  });
255
269
  return created;
@@ -344,95 +358,99 @@ export const worksheetRouter = createTRPCRouter({
344
358
  // You could integrate with an AI service to auto-grade certain question types
345
359
  return submittedWorksheet;
346
360
  }),
347
- // Get or create a student's worksheet response
348
- getOrCreateWorksheetResponse: protectedProcedure
361
+ // Grade a student's answer
362
+ gradeAnswer: protectedProcedure
349
363
  .input(z.object({
350
- worksheetId: z.string(),
351
- studentId: z.string(),
364
+ questionId: z.string(),
365
+ responseId: z.string().optional(), // StudentQuestionProgress ID (optional for upsert)
366
+ studentWorksheetResponseId: z.string(), // Required for linking to worksheet response
367
+ response: z.string().optional(), // The actual response text (needed if creating new)
368
+ isCorrect: z.boolean(),
369
+ feedback: z.string().optional(),
370
+ markschemeState: z.any().optional(),
371
+ points: z.number().optional(),
352
372
  }))
353
373
  .mutation(async ({ ctx, input }) => {
354
- const { worksheetId, studentId } = input;
355
- // Try to find existing response
356
- let worksheetResponse = await prisma.studentWorksheetResponse.findFirst({
357
- where: {
358
- worksheetId,
359
- studentId,
360
- submitted: false, // Only get unsubmitted responses
361
- },
362
- include: {
363
- responses: {
364
- include: {
365
- question: true,
366
- },
367
- },
368
- },
369
- });
370
- // Create new response if none exists
371
- if (!worksheetResponse) {
372
- worksheetResponse = await prisma.studentWorksheetResponse.create({
374
+ const { responseId, questionId, studentWorksheetResponseId, response, isCorrect, feedback, markschemeState, points } = input;
375
+ let gradedResponse;
376
+ if (responseId) {
377
+ // Update existing progress by ID
378
+ gradedResponse = await prisma.studentQuestionProgress.update({
379
+ where: { id: responseId },
373
380
  data: {
374
- worksheetId,
375
- studentId,
376
- },
377
- include: {
378
- responses: {
379
- include: {
380
- question: true,
381
- },
382
- },
381
+ isCorrect,
382
+ ...(feedback !== undefined && { feedback }),
383
+ ...(markschemeState !== undefined && { markschemeState }),
384
+ ...(points !== undefined && { points }),
383
385
  },
384
386
  });
385
387
  }
386
- return worksheetResponse;
387
- }),
388
- // Get all student responses for a worksheet (teacher view)
389
- getWorksheetResponse: protectedProcedure
390
- .input(z.object({
391
- worksheetId: z.string(),
392
- }))
393
- .query(async ({ ctx, input }) => {
394
- const { worksheetId } = input;
395
- const responses = await prisma.studentWorksheetResponse.findFirst({
396
- where: { worksheetId },
397
- include: {
398
- student: {
399
- select: {
400
- id: true,
401
- username: true,
402
- profile: {
403
- select: {
404
- displayName: true,
405
- profilePicture: true,
406
- },
407
- },
408
- },
388
+ else {
389
+ // Get the studentId from the worksheet response
390
+ const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
391
+ where: { id: studentWorksheetResponseId },
392
+ select: { studentId: true },
393
+ });
394
+ if (!worksheetResponse) {
395
+ throw new TRPCError({
396
+ code: 'NOT_FOUND',
397
+ message: 'Student worksheet response not found',
398
+ });
399
+ }
400
+ const { studentId } = worksheetResponse;
401
+ // Upsert - find or create the progress record
402
+ const existing = await prisma.studentQuestionProgress.findFirst({
403
+ where: {
404
+ studentId,
405
+ questionId,
406
+ studentWorksheetResponseId,
409
407
  },
410
- responses: {
411
- include: {
412
- question: true,
408
+ });
409
+ if (existing) {
410
+ // Update existing
411
+ gradedResponse = await prisma.studentQuestionProgress.update({
412
+ where: { id: existing.id },
413
+ data: {
414
+ isCorrect,
415
+ ...(response !== undefined && { response }),
416
+ ...(feedback !== undefined && { feedback }),
417
+ ...(markschemeState !== undefined && { markschemeState }),
418
+ ...(points !== undefined && { points }),
413
419
  },
414
- },
415
- },
416
- orderBy: { submittedAt: 'desc' },
417
- });
418
- return responses;
420
+ });
421
+ }
422
+ else {
423
+ // Create new
424
+ gradedResponse = await prisma.studentQuestionProgress.create({
425
+ data: {
426
+ studentId,
427
+ questionId,
428
+ studentWorksheetResponseId,
429
+ response: response || '',
430
+ isCorrect,
431
+ ...(feedback !== undefined && { feedback }),
432
+ ...(markschemeState !== undefined && { markschemeState }),
433
+ ...(points !== undefined && { points: points || 0 }),
434
+ },
435
+ });
436
+ }
437
+ }
438
+ return gradedResponse;
419
439
  }),
420
- // Grade a student's answer
421
- gradeAnswer: protectedProcedure
440
+ addComment: protectedProcedure
422
441
  .input(z.object({
423
- responseId: z.string(), // StudentQuestionProgress ID
424
- isCorrect: z.boolean(),
425
- feedback: z.string().optional(),
442
+ responseId: z.string(),
443
+ comment: z.string(),
426
444
  }))
427
445
  .mutation(async ({ ctx, input }) => {
428
- const { responseId, isCorrect, feedback } = input;
429
- const gradedResponse = await prisma.studentQuestionProgress.update({
430
- where: { id: responseId },
446
+ const { responseId, comment } = input;
447
+ const newComment = await prisma.comment.create({
431
448
  data: {
432
- isCorrect,
433
- ...(feedback !== undefined && { feedback }),
449
+ studentQuestionProgressId: responseId,
450
+ content: comment,
451
+ authorId: ctx.user.id,
434
452
  },
435
453
  });
436
- return gradedResponse;
454
+ return newComment;
437
455
  }),
438
456
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studious-lms/server",
3
- "version": "1.2.34",
3
+ "version": "1.2.36",
4
4
  "description": "Backend server for Studious application",
5
5
  "main": "dist/exportType.js",
6
6
  "types": "dist/exportType.d.ts",
@@ -301,14 +301,16 @@ model Comment {
301
301
  content String
302
302
  author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
303
303
  authorId String
304
- announcement Announcement @relation(fields: [announcementId], references: [id], onDelete: Cascade)
305
- announcementId String
304
+ announcement Announcement? @relation(fields: [announcementId], references: [id], onDelete: Cascade)
305
+ announcementId String?
306
306
  parentComment Comment? @relation("CommentReplies", fields: [parentCommentId], references: [id], onDelete: Cascade)
307
307
  parentCommentId String?
308
308
  replies Comment[] @relation("CommentReplies")
309
309
  reactions Reaction[]
310
310
  createdAt DateTime @default(now())
311
311
  modifiedAt DateTime? @updatedAt
312
+ studentQuestionProgress StudentQuestionProgress? @relation("StudentQuestionProgressComments", fields: [studentQuestionProgressId], references: [id], onDelete: Cascade)
313
+ studentQuestionProgressId String?
312
314
  }
313
315
 
314
316
  model Reaction {
@@ -517,7 +519,7 @@ model WorksheetQuestion {
517
519
  answer String
518
520
  options Json? @default("{}")
519
521
  markScheme Json? @default("{}")
520
- points Int? @default(0)
522
+ points Int @default(0)
521
523
  order Int? @default(0)
522
524
  createdAt DateTime @default(now())
523
525
  updatedAt DateTime @updatedAt
@@ -547,11 +549,16 @@ model StudentQuestionProgress {
547
549
  question WorksheetQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade)
548
550
  response String
549
551
  isCorrect Boolean @default(false)
552
+ markschemeState Json? @default("{}")
553
+ points Int @default(0)
554
+ comments Comment[] @relation("StudentQuestionProgressComments")
550
555
  feedback String?
551
556
  createdAt DateTime @default(now())
552
- updatedAt DateTime @updatedAt
557
+ updatedAt DateTime? @updatedAt
553
558
  studentWorksheetResponseId String?
554
559
  studentWorksheetResponse StudentWorksheetResponse? @relation(fields: [studentWorksheetResponseId], references: [id], onDelete: Cascade)
560
+
561
+ @@index([studentId, questionId])
555
562
  }
556
563
 
557
564
  model SchoolDevelopementProgram {
@@ -18,6 +18,7 @@ import { messageRouter } from "./message.js";
18
18
  import { labChatRouter } from "./labChat.js";
19
19
  import { marketingRouter } from "./marketing.js";
20
20
  import { worksheetRouter } from "./worksheet.js";
21
+ import { commentRouter } from "./comment.js";
21
22
 
22
23
  export const appRouter = createTRPCRouter({
23
24
  class: classRouter,
@@ -37,6 +38,7 @@ export const appRouter = createTRPCRouter({
37
38
  labChat: labChatRouter,
38
39
  marketing: marketingRouter,
39
40
  worksheet: worksheetRouter,
41
+ comment: commentRouter,
40
42
  });
41
43
 
42
44
  // Export type router type definition
@@ -667,7 +667,7 @@ export const announcementRouter = createTRPCRouter({
667
667
  // Only the author or a class teacher can delete comments
668
668
  const userId = ctx.user.id;
669
669
  const isAuthor = comment.authorId === userId;
670
- const isClassTeacher = comment.announcement.class.teachers.some(
670
+ const isClassTeacher = comment.announcement!.class.teachers.some(
671
671
  (teacher) => teacher.id === userId
672
672
  );
673
673
 
@@ -858,7 +858,7 @@ export const announcementRouter = createTRPCRouter({
858
858
  });
859
859
  }
860
860
 
861
- if (comment.announcement.classId !== input.classId) {
861
+ if (comment.announcement!.classId !== input.classId) {
862
862
  throw new TRPCError({
863
863
  code: "FORBIDDEN",
864
864
  message: "Comment does not belong to this class",
@@ -1078,7 +1078,7 @@ export const announcementRouter = createTRPCRouter({
1078
1078
  });
1079
1079
  }
1080
1080
 
1081
- if (comment.announcement.classId !== input.classId) {
1081
+ if (comment.announcement!.classId !== input.classId) {
1082
1082
  throw new TRPCError({
1083
1083
  code: "FORBIDDEN",
1084
1084
  message: "Comment does not belong to this class",