@studious-lms/server 1.2.33 → 1.2.35
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.
- package/dist/routers/_app.d.ts +306 -188
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +2 -0
- package/dist/routers/announcement.d.ts +8 -4
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +9 -9
- package/dist/routers/comment.d.ts +124 -0
- package/dist/routers/comment.d.ts.map +1 -0
- package/dist/routers/comment.js +241 -0
- package/dist/routers/worksheet.d.ts +46 -113
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +90 -80
- package/package.json +1 -1
- package/prisma/schema.prisma +17 -9
- package/src/routers/_app.ts +2 -0
- package/src/routers/announcement.ts +12 -12
- package/src/routers/comment.ts +268 -0
- package/src/routers/worksheet.ts +92 -83
|
@@ -523,7 +523,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
523
523
|
|
|
524
524
|
// If replying to a comment, verify parent comment exists and belongs to the same announcement
|
|
525
525
|
if (input.parentCommentId) {
|
|
526
|
-
const parentComment = await prisma.
|
|
526
|
+
const parentComment = await prisma.comment.findFirst({
|
|
527
527
|
where: {
|
|
528
528
|
id: input.parentCommentId,
|
|
529
529
|
announcementId: input.announcementId,
|
|
@@ -538,7 +538,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
-
const comment = await prisma.
|
|
541
|
+
const comment = await prisma.comment.create({
|
|
542
542
|
data: {
|
|
543
543
|
content: input.content,
|
|
544
544
|
author: {
|
|
@@ -586,7 +586,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
586
586
|
});
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
const comment = await prisma.
|
|
589
|
+
const comment = await prisma.comment.findUnique({
|
|
590
590
|
where: { id: input.id },
|
|
591
591
|
});
|
|
592
592
|
|
|
@@ -605,7 +605,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
605
605
|
});
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
const updatedComment = await prisma.
|
|
608
|
+
const updatedComment = await prisma.comment.update({
|
|
609
609
|
where: { id: input.id },
|
|
610
610
|
data: {
|
|
611
611
|
content: input.content,
|
|
@@ -642,7 +642,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
642
642
|
});
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
-
const comment = await prisma.
|
|
645
|
+
const comment = await prisma.comment.findUnique({
|
|
646
646
|
where: { id: input.id },
|
|
647
647
|
include: {
|
|
648
648
|
announcement: {
|
|
@@ -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
|
|
670
|
+
const isClassTeacher = comment.announcement!.class.teachers.some(
|
|
671
671
|
(teacher) => teacher.id === userId
|
|
672
672
|
);
|
|
673
673
|
|
|
@@ -678,7 +678,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
678
678
|
});
|
|
679
679
|
}
|
|
680
680
|
|
|
681
|
-
await prisma.
|
|
681
|
+
await prisma.comment.delete({
|
|
682
682
|
where: { id: input.id },
|
|
683
683
|
});
|
|
684
684
|
|
|
@@ -707,7 +707,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
707
707
|
}
|
|
708
708
|
|
|
709
709
|
// Get all top-level comments (no parent)
|
|
710
|
-
const comments = await prisma.
|
|
710
|
+
const comments = await prisma.comment.findMany({
|
|
711
711
|
where: {
|
|
712
712
|
announcementId: input.announcementId,
|
|
713
713
|
parentCommentId: null,
|
|
@@ -840,7 +840,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
840
840
|
return { reaction };
|
|
841
841
|
} else if (input.commentId) {
|
|
842
842
|
// Verify comment exists and get its announcement to check class
|
|
843
|
-
|
|
843
|
+
const comment = await prisma.comment.findUnique({
|
|
844
844
|
where: { id: input.commentId },
|
|
845
845
|
include: {
|
|
846
846
|
announcement: {
|
|
@@ -858,7 +858,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
858
858
|
});
|
|
859
859
|
}
|
|
860
860
|
|
|
861
|
-
if (comment.announcement
|
|
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",
|
|
@@ -1060,7 +1060,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
1060
1060
|
};
|
|
1061
1061
|
} else if (input.commentId) {
|
|
1062
1062
|
// Verify comment exists
|
|
1063
|
-
const comment = await prisma.
|
|
1063
|
+
const comment = await prisma.comment.findUnique({
|
|
1064
1064
|
where: { id: input.commentId },
|
|
1065
1065
|
include: {
|
|
1066
1066
|
announcement: {
|
|
@@ -1078,7 +1078,7 @@ export const announcementRouter = createTRPCRouter({
|
|
|
1078
1078
|
});
|
|
1079
1079
|
}
|
|
1080
1080
|
|
|
1081
|
-
if (comment.announcement
|
|
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",
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { createTRPCRouter, protectedProcedure } from "../trpc.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { prisma } from "../lib/prisma.js";
|
|
4
|
+
import { TRPCError } from "@trpc/server";
|
|
5
|
+
|
|
6
|
+
export const commentRouter = createTRPCRouter({
|
|
7
|
+
get: protectedProcedure
|
|
8
|
+
.input(z.object({
|
|
9
|
+
id: z.string(),
|
|
10
|
+
}))
|
|
11
|
+
.query(async ({ ctx, input }) => {
|
|
12
|
+
const comment = await prisma.comment.findUnique({
|
|
13
|
+
where: { id: input.id },
|
|
14
|
+
select: {
|
|
15
|
+
replies: {
|
|
16
|
+
select: {
|
|
17
|
+
id: true,
|
|
18
|
+
content: true,
|
|
19
|
+
author: {
|
|
20
|
+
select: {
|
|
21
|
+
id: true,
|
|
22
|
+
username: true,
|
|
23
|
+
profile: {
|
|
24
|
+
select: {
|
|
25
|
+
displayName: true,
|
|
26
|
+
profilePicture: true,
|
|
27
|
+
profilePictureThumbnail: true,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
reactions: {
|
|
35
|
+
select: {
|
|
36
|
+
type: true,
|
|
37
|
+
user: {
|
|
38
|
+
select: {
|
|
39
|
+
id: true,
|
|
40
|
+
username: true,
|
|
41
|
+
profile: {
|
|
42
|
+
select: {
|
|
43
|
+
displayName: true,
|
|
44
|
+
profilePicture: true,
|
|
45
|
+
profilePictureThumbnail: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return comment;
|
|
55
|
+
}),
|
|
56
|
+
replyToComment: protectedProcedure
|
|
57
|
+
.input(z.object({
|
|
58
|
+
parentCommentId: z.string(),
|
|
59
|
+
content: z.string(),
|
|
60
|
+
}))
|
|
61
|
+
.mutation(async ({ ctx, input }) => {
|
|
62
|
+
const { parentCommentId, content } = input;
|
|
63
|
+
|
|
64
|
+
const newComment = await prisma.comment.create({
|
|
65
|
+
data: {
|
|
66
|
+
parentCommentId,
|
|
67
|
+
content,
|
|
68
|
+
authorId: ctx.user!.id,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return newComment;
|
|
73
|
+
}),
|
|
74
|
+
addReaction: protectedProcedure
|
|
75
|
+
.input(z.object({
|
|
76
|
+
id: z.string(),
|
|
77
|
+
type: z.enum(['THUMBSUP', 'CELEBRATE', 'CARE', 'HEART', 'IDEA', 'HAPPY']),
|
|
78
|
+
}))
|
|
79
|
+
.mutation(async ({ ctx, input }) => {
|
|
80
|
+
if (!ctx.user) {
|
|
81
|
+
throw new TRPCError({
|
|
82
|
+
code: "UNAUTHORIZED",
|
|
83
|
+
message: "User must be authenticated",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Exactly one of announcementId or commentId must be provided
|
|
88
|
+
const comment = await prisma.comment.findUnique({
|
|
89
|
+
where: { id: input.id },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const userId = ctx.user.id;
|
|
93
|
+
|
|
94
|
+
// Verify the announcement or comment exists and belongs to the class
|
|
95
|
+
if (comment) {
|
|
96
|
+
const announcement = await prisma.announcement.findFirst({
|
|
97
|
+
where: {
|
|
98
|
+
id: input.id,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Upsert reaction: update if exists, create if not
|
|
103
|
+
const reaction = await prisma.reaction.upsert({
|
|
104
|
+
where: {
|
|
105
|
+
userId_commentId: {
|
|
106
|
+
userId,
|
|
107
|
+
commentId: input.id,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
update: {
|
|
111
|
+
type: input.type,
|
|
112
|
+
},
|
|
113
|
+
create: {
|
|
114
|
+
type: input.type,
|
|
115
|
+
userId,
|
|
116
|
+
commentId: input.id,
|
|
117
|
+
},
|
|
118
|
+
include: {
|
|
119
|
+
user: {
|
|
120
|
+
select: {
|
|
121
|
+
id: true,
|
|
122
|
+
username: true,
|
|
123
|
+
profile: {
|
|
124
|
+
select: {
|
|
125
|
+
displayName: true,
|
|
126
|
+
profilePicture: true,
|
|
127
|
+
profilePictureThumbnail: true,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return { reaction };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw new TRPCError({
|
|
139
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
140
|
+
message: "Unexpected error",
|
|
141
|
+
});
|
|
142
|
+
}),
|
|
143
|
+
|
|
144
|
+
removeReaction: protectedProcedure
|
|
145
|
+
.input(z.object({
|
|
146
|
+
commentId: z.string().optional(),
|
|
147
|
+
}))
|
|
148
|
+
.mutation(async ({ ctx, input }) => {
|
|
149
|
+
if (!ctx.user) {
|
|
150
|
+
throw new TRPCError({
|
|
151
|
+
code: "UNAUTHORIZED",
|
|
152
|
+
message: "User must be authenticated",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Exactly one of announcementId or commentId must be provided
|
|
157
|
+
if (!input.commentId) {
|
|
158
|
+
throw new TRPCError({
|
|
159
|
+
code: "BAD_REQUEST",
|
|
160
|
+
message: "Either announcementId or commentId must be provided",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const userId = ctx.user.id;
|
|
165
|
+
|
|
166
|
+
const reaction = await prisma.reaction.findUnique({
|
|
167
|
+
where: {
|
|
168
|
+
userId_commentId: {
|
|
169
|
+
userId,
|
|
170
|
+
commentId: input.commentId,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!reaction) {
|
|
176
|
+
throw new TRPCError({
|
|
177
|
+
code: "NOT_FOUND",
|
|
178
|
+
message: "Reaction not found",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await prisma.reaction.delete({
|
|
183
|
+
where: { id: reaction.id },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return { success: true };
|
|
187
|
+
|
|
188
|
+
}),
|
|
189
|
+
|
|
190
|
+
getReactions: protectedProcedure
|
|
191
|
+
.input(z.object({
|
|
192
|
+
commentId: z.string().optional(),
|
|
193
|
+
}))
|
|
194
|
+
.query(async ({ ctx, input }) => {
|
|
195
|
+
if (!ctx.user) {
|
|
196
|
+
throw new TRPCError({
|
|
197
|
+
code: "UNAUTHORIZED",
|
|
198
|
+
message: "User must be authenticated",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Exactly one of announcementId or commentId must be provided
|
|
203
|
+
if (!input.commentId) {
|
|
204
|
+
throw new TRPCError({
|
|
205
|
+
code: "BAD_REQUEST",
|
|
206
|
+
message: "Either announcementId or commentId must be provided",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const userId = ctx.user.id;
|
|
211
|
+
|
|
212
|
+
// Verify comment exists
|
|
213
|
+
const comment = await prisma.comment.findUnique({
|
|
214
|
+
where: { id: input.commentId },
|
|
215
|
+
include: {
|
|
216
|
+
announcement: {
|
|
217
|
+
select: {
|
|
218
|
+
classId: true,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (!comment) {
|
|
225
|
+
throw new TRPCError({
|
|
226
|
+
code: "NOT_FOUND",
|
|
227
|
+
message: "Comment not found",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Get reaction counts by type
|
|
232
|
+
const reactionCounts = await prisma.reaction.groupBy({
|
|
233
|
+
by: ['type'],
|
|
234
|
+
where: { commentId: input.commentId },
|
|
235
|
+
_count: { type: true },
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Get current user's reaction
|
|
239
|
+
const userReaction = await prisma.reaction.findUnique({
|
|
240
|
+
where: {
|
|
241
|
+
userId_commentId: {
|
|
242
|
+
userId,
|
|
243
|
+
commentId: input.commentId,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Format counts
|
|
249
|
+
const counts = {
|
|
250
|
+
THUMBSUP: 0,
|
|
251
|
+
CELEBRATE: 0,
|
|
252
|
+
CARE: 0,
|
|
253
|
+
HEART: 0,
|
|
254
|
+
IDEA: 0,
|
|
255
|
+
HAPPY: 0,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
reactionCounts.forEach((item) => {
|
|
259
|
+
counts[item.type as keyof typeof counts] = item._count.type;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
counts,
|
|
264
|
+
userReaction: userReaction?.type || null,
|
|
265
|
+
total: reactionCounts.reduce((sum, item) => sum + item._count.type, 0),
|
|
266
|
+
};
|
|
267
|
+
}),
|
|
268
|
+
});
|
package/src/routers/worksheet.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TRPCError } from "@trpc/server";
|
|
2
|
-
import { createTRPCRouter, protectedProcedure } from "
|
|
2
|
+
import { createTRPCRouter, protectedProcedure } from "../trpc.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { prisma } from "
|
|
4
|
+
import { prisma } from "../lib/prisma.js";
|
|
5
5
|
import { WorksheetQuestionType } from "@prisma/client";
|
|
6
6
|
|
|
7
7
|
export const worksheetRouter = createTRPCRouter({
|
|
@@ -18,6 +18,7 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
18
18
|
include: {
|
|
19
19
|
questions: {
|
|
20
20
|
orderBy: { createdAt: 'asc' },
|
|
21
|
+
// select: { id: true, type: true, question: true, answer: true, points: true },
|
|
21
22
|
},
|
|
22
23
|
class: true,
|
|
23
24
|
},
|
|
@@ -112,12 +113,13 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
112
113
|
worksheetId: z.string(),
|
|
113
114
|
question: z.string(),
|
|
114
115
|
answer: z.string(),
|
|
116
|
+
points: z.number().optional(),
|
|
115
117
|
options: z.any().optional(), // JSON field
|
|
116
118
|
markScheme: z.any().optional(), // JSON field
|
|
117
119
|
type: z.enum(['MULTIPLE_CHOICE', 'TRUE_FALSE', 'SHORT_ANSWER', 'LONG_ANSWER', 'MATH_EXPRESSION', 'ESSAY']),
|
|
118
120
|
}))
|
|
119
121
|
.mutation(async ({ ctx, input }) => {
|
|
120
|
-
const { worksheetId, question, answer, options, markScheme, type } = input;
|
|
122
|
+
const { worksheetId, question, points, answer, options, markScheme, type } = input;
|
|
121
123
|
|
|
122
124
|
const worksheet = await prisma.worksheet.findUnique({
|
|
123
125
|
where: { id: worksheetId },
|
|
@@ -131,6 +133,7 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
131
133
|
data: {
|
|
132
134
|
worksheetId,
|
|
133
135
|
type,
|
|
136
|
+
points,
|
|
134
137
|
question,
|
|
135
138
|
answer,
|
|
136
139
|
options,
|
|
@@ -201,12 +204,13 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
201
204
|
questionId: z.string(),
|
|
202
205
|
question: z.string().optional(),
|
|
203
206
|
answer: z.string().optional(),
|
|
207
|
+
points: z.number().optional(),
|
|
204
208
|
options: z.any().optional(), // JSON field
|
|
205
209
|
markScheme: z.any().optional(), // JSON field
|
|
206
210
|
type: z.enum(['MULTIPLE_CHOICE', 'TRUE_FALSE', 'SHORT_ANSWER', 'LONG_ANSWER', 'MATH_EXPRESSION', 'ESSAY']).optional(),
|
|
207
211
|
}))
|
|
208
212
|
.mutation(async ({ ctx, input }) => {
|
|
209
|
-
const { worksheetId, questionId, question, answer, options, markScheme, type } = input;
|
|
213
|
+
const { worksheetId, questionId, points, question, answer, options, markScheme, type } = input;
|
|
210
214
|
|
|
211
215
|
const worksheet = await prisma.worksheet.findUnique({
|
|
212
216
|
where: { id: worksheetId },
|
|
@@ -224,6 +228,7 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
224
228
|
...(markScheme !== undefined && { markScheme }),
|
|
225
229
|
...(type !== undefined && { type }),
|
|
226
230
|
...(options !== undefined && { options }),
|
|
231
|
+
...(points !== undefined && { points }),
|
|
227
232
|
},
|
|
228
233
|
});
|
|
229
234
|
|
|
@@ -269,7 +274,7 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
// Find or create worksheet response for this submission
|
|
272
|
-
const
|
|
277
|
+
const worksheetResponse = await prisma.$transaction(async (tx) => {
|
|
273
278
|
// First check if a response exists
|
|
274
279
|
const existing = await tx.studentWorksheetResponse.findFirst({
|
|
275
280
|
where: {
|
|
@@ -300,7 +305,9 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
300
305
|
return created;
|
|
301
306
|
});
|
|
302
307
|
|
|
303
|
-
|
|
308
|
+
|
|
309
|
+
console.log(worksheetResponse);
|
|
310
|
+
return worksheetResponse;
|
|
304
311
|
}),
|
|
305
312
|
answerQuestion: protectedProcedure
|
|
306
313
|
.input(z.object({
|
|
@@ -401,104 +408,106 @@ export const worksheetRouter = createTRPCRouter({
|
|
|
401
408
|
return submittedWorksheet;
|
|
402
409
|
}),
|
|
403
410
|
|
|
404
|
-
//
|
|
405
|
-
|
|
411
|
+
// Grade a student's answer
|
|
412
|
+
gradeAnswer: protectedProcedure
|
|
406
413
|
.input(z.object({
|
|
407
|
-
|
|
408
|
-
|
|
414
|
+
questionId: z.string(),
|
|
415
|
+
responseId: z.string().optional(), // StudentQuestionProgress ID (optional for upsert)
|
|
416
|
+
studentWorksheetResponseId: z.string(), // Required for linking to worksheet response
|
|
417
|
+
response: z.string().optional(), // The actual response text (needed if creating new)
|
|
418
|
+
isCorrect: z.boolean(),
|
|
419
|
+
feedback: z.string().optional(),
|
|
420
|
+
markschemeState: z.any().optional(),
|
|
421
|
+
points: z.number().optional(),
|
|
409
422
|
}))
|
|
410
423
|
.mutation(async ({ ctx, input }) => {
|
|
411
|
-
const {
|
|
412
|
-
|
|
413
|
-
// Try to find existing response
|
|
414
|
-
let worksheetResponse = await prisma.studentWorksheetResponse.findFirst({
|
|
415
|
-
where: {
|
|
416
|
-
worksheetId,
|
|
417
|
-
studentId,
|
|
418
|
-
submitted: false, // Only get unsubmitted responses
|
|
419
|
-
},
|
|
420
|
-
include: {
|
|
421
|
-
responses: {
|
|
422
|
-
include: {
|
|
423
|
-
question: true,
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
});
|
|
424
|
+
const { responseId, questionId, studentWorksheetResponseId, response, isCorrect, feedback, markschemeState, points } = input;
|
|
428
425
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
426
|
+
let gradedResponse;
|
|
427
|
+
|
|
428
|
+
if (responseId) {
|
|
429
|
+
// Update existing progress by ID
|
|
430
|
+
gradedResponse = await prisma.studentQuestionProgress.update({
|
|
431
|
+
where: { id: responseId },
|
|
432
432
|
data: {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
responses: {
|
|
438
|
-
include: {
|
|
439
|
-
question: true,
|
|
440
|
-
},
|
|
441
|
-
},
|
|
433
|
+
isCorrect,
|
|
434
|
+
...(feedback !== undefined && { feedback }),
|
|
435
|
+
...(markschemeState !== undefined && { markschemeState }),
|
|
436
|
+
...(points !== undefined && { points }),
|
|
442
437
|
},
|
|
443
438
|
});
|
|
444
|
-
}
|
|
439
|
+
} else {
|
|
440
|
+
// Get the studentId from the worksheet response
|
|
441
|
+
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
442
|
+
where: { id: studentWorksheetResponseId },
|
|
443
|
+
select: { studentId: true },
|
|
444
|
+
});
|
|
445
445
|
|
|
446
|
-
|
|
447
|
-
|
|
446
|
+
if (!worksheetResponse) {
|
|
447
|
+
throw new TRPCError({
|
|
448
|
+
code: 'NOT_FOUND',
|
|
449
|
+
message: 'Student worksheet response not found',
|
|
450
|
+
});
|
|
451
|
+
}
|
|
448
452
|
|
|
449
|
-
|
|
450
|
-
getWorksheetResponse: protectedProcedure
|
|
451
|
-
.input(z.object({
|
|
452
|
-
worksheetId: z.string(),
|
|
453
|
-
}))
|
|
454
|
-
.query(async ({ ctx, input }) => {
|
|
455
|
-
const { worksheetId } = input;
|
|
453
|
+
const { studentId } = worksheetResponse;
|
|
456
454
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
username: true,
|
|
464
|
-
profile: {
|
|
465
|
-
select: {
|
|
466
|
-
displayName: true,
|
|
467
|
-
profilePicture: true,
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
},
|
|
455
|
+
// Upsert - find or create the progress record
|
|
456
|
+
const existing = await prisma.studentQuestionProgress.findFirst({
|
|
457
|
+
where: {
|
|
458
|
+
studentId,
|
|
459
|
+
questionId,
|
|
460
|
+
studentWorksheetResponseId,
|
|
471
461
|
},
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
if (existing) {
|
|
465
|
+
// Update existing
|
|
466
|
+
gradedResponse = await prisma.studentQuestionProgress.update({
|
|
467
|
+
where: { id: existing.id },
|
|
468
|
+
data: {
|
|
469
|
+
isCorrect,
|
|
470
|
+
...(response !== undefined && { response }),
|
|
471
|
+
...(feedback !== undefined && { feedback }),
|
|
472
|
+
...(markschemeState !== undefined && { markschemeState }),
|
|
473
|
+
...(points !== undefined && { points }),
|
|
475
474
|
},
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
475
|
+
});
|
|
476
|
+
} else {
|
|
477
|
+
// Create new
|
|
478
|
+
gradedResponse = await prisma.studentQuestionProgress.create({
|
|
479
|
+
data: {
|
|
480
|
+
studentId,
|
|
481
|
+
questionId,
|
|
482
|
+
studentWorksheetResponseId,
|
|
483
|
+
response: response || '',
|
|
484
|
+
isCorrect,
|
|
485
|
+
...(feedback !== undefined && { feedback }),
|
|
486
|
+
...(markschemeState !== undefined && { markschemeState }),
|
|
487
|
+
...(points !== undefined && { points: points || 0 }),
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
480
492
|
|
|
481
|
-
return
|
|
493
|
+
return gradedResponse;
|
|
482
494
|
}),
|
|
483
|
-
|
|
484
|
-
// Grade a student's answer
|
|
485
|
-
gradeAnswer: protectedProcedure
|
|
495
|
+
addComment: protectedProcedure
|
|
486
496
|
.input(z.object({
|
|
487
|
-
responseId: z.string(),
|
|
488
|
-
|
|
489
|
-
feedback: z.string().optional(),
|
|
497
|
+
responseId: z.string(),
|
|
498
|
+
comment: z.string(),
|
|
490
499
|
}))
|
|
491
500
|
.mutation(async ({ ctx, input }) => {
|
|
492
|
-
const { responseId,
|
|
501
|
+
const { responseId, comment } = input;
|
|
493
502
|
|
|
494
|
-
const
|
|
495
|
-
where: { id: responseId },
|
|
503
|
+
const newComment = await prisma.comment.create({
|
|
496
504
|
data: {
|
|
497
|
-
|
|
498
|
-
|
|
505
|
+
studentQuestionProgressId: responseId,
|
|
506
|
+
content: comment,
|
|
507
|
+
authorId: ctx.user!.id,
|
|
499
508
|
},
|
|
500
509
|
});
|
|
501
510
|
|
|
502
|
-
return
|
|
511
|
+
return newComment;
|
|
503
512
|
}),
|
|
504
513
|
});
|