@studious-lms/server 1.4.1 → 1.4.3
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/.coderabbit.yaml +4 -1
- package/.env.example +6 -0
- package/.env.test.example +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -76
- package/dist/index.js.map +1 -1
- package/dist/lib/config/cors.d.ts +16 -0
- package/dist/lib/config/cors.d.ts.map +1 -0
- package/dist/lib/config/cors.js +75 -0
- package/dist/lib/config/cors.js.map +1 -0
- package/dist/lib/config/env.d.ts +14 -0
- package/dist/lib/config/env.d.ts.map +1 -1
- package/dist/lib/config/env.js +9 -2
- package/dist/lib/config/env.js.map +1 -1
- package/dist/lib/fileUpload.d.ts +10 -2
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +38 -6
- package/dist/lib/fileUpload.js.map +1 -1
- package/dist/lib/prisma.d.ts +14 -2
- package/dist/lib/prisma.d.ts.map +1 -1
- package/dist/lib/prisma.js +27 -8
- package/dist/lib/prisma.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +18 -5
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +3 -3
- package/dist/middleware/security.js.map +1 -1
- package/dist/models/agenda.d.ts +16 -16
- package/dist/models/announcement.d.ts +59 -23
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/assignment.d.ts +363 -276
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/attendance.d.ts +63 -21
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/auth.d.ts +115 -26
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +53 -30
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +115 -65
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +16 -4
- package/dist/models/class.js.map +1 -1
- package/dist/models/comment.d.ts +52 -16
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/conversation.d.ts +46 -16
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/event.d.ts +107 -53
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/file.d.ts +213 -165
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +161 -77
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +73 -31
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/marketing.d.ts +25 -7
- package/dist/models/marketing.d.ts.map +1 -1
- package/dist/models/message.d.ts +31 -13
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/newtonChat.d.ts +34 -10
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/notification.d.ts +29 -10
- package/dist/models/notification.d.ts.map +1 -1
- package/dist/models/notification.js +13 -9
- package/dist/models/notification.js.map +1 -1
- package/dist/models/section.d.ts +71 -23
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/user.d.ts +27 -9
- package/dist/models/user.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +606 -106
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +168 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts +22 -2
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +133 -95
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +25 -25
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +25 -14
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +8 -8
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +12 -5
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/labChatPrompt.d.ts +27 -0
- package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
- package/dist/pipelines/labChatPrompt.js +143 -69
- package/dist/pipelines/labChatPrompt.js.map +1 -1
- package/dist/routers/_app.d.ts +1579 -1257
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/agenda.d.ts +16 -16
- package/dist/routers/announcement.d.ts +19 -19
- package/dist/routers/assignment.d.ts +307 -291
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +3 -2
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/attendance.d.ts +7 -7
- package/dist/routers/auth.d.ts +1 -1
- package/dist/routers/class.d.ts +79 -72
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +3 -3
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/comment.d.ts +6 -6
- package/dist/routers/conversation.d.ts +11 -11
- package/dist/routers/event.d.ts +35 -35
- package/dist/routers/file.d.ts +12 -12
- package/dist/routers/folder.d.ts +54 -54
- package/dist/routers/labChat.d.ts +18 -12
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +5 -3
- package/dist/routers/labChat.js.map +1 -1
- package/dist/routers/marketing.d.ts +2 -2
- package/dist/routers/message.d.ts +33 -2
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +27 -4
- package/dist/routers/message.js.map +1 -1
- package/dist/routers/newtonChat.d.ts +7 -1
- package/dist/routers/newtonChat.d.ts.map +1 -1
- package/dist/routers/newtonChat.js +5 -4
- package/dist/routers/newtonChat.js.map +1 -1
- package/dist/routers/notifications.d.ts +13 -6
- package/dist/routers/notifications.d.ts.map +1 -1
- package/dist/routers/notifications.js +4 -4
- package/dist/routers/notifications.js.map +1 -1
- package/dist/routers/section.d.ts +7 -7
- package/dist/routers/studentProgress.d.ts +86 -0
- package/dist/routers/studentProgress.d.ts.map +1 -1
- package/dist/routers/studentProgress.js +14 -4
- package/dist/routers/studentProgress.js.map +1 -1
- package/dist/routers/user.d.ts +1 -1
- package/dist/routers/worksheet.d.ts +64 -62
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +18 -16
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/seedDatabase.d.ts +1 -1
- package/dist/services/agenda.d.ts +16 -16
- package/dist/services/announcement.d.ts +8 -8
- package/dist/services/assignment.d.ts +299 -283
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +24 -5
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.d.ts +7 -7
- package/dist/services/auth.d.ts +2 -2
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +19 -12
- package/dist/services/auth.js.map +1 -1
- package/dist/services/class.d.ts +74 -68
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +6 -3
- package/dist/services/class.js.map +1 -1
- package/dist/services/comment.d.ts +6 -6
- package/dist/services/conversation.d.ts +11 -11
- package/dist/services/event.d.ts +31 -31
- package/dist/services/file.d.ts +12 -12
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/file.js +7 -2
- package/dist/services/file.js.map +1 -1
- package/dist/services/folder.d.ts +52 -52
- package/dist/services/labChat.d.ts +18 -12
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +30 -18
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/marketing.d.ts +2 -2
- package/dist/services/message.d.ts +30 -0
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +97 -21
- package/dist/services/message.js.map +1 -1
- package/dist/services/newtonChat.d.ts +6 -1
- package/dist/services/newtonChat.d.ts.map +1 -1
- package/dist/services/newtonChat.js +36 -18
- package/dist/services/newtonChat.js.map +1 -1
- package/dist/services/notification.d.ts +16 -9
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/notification.js +24 -9
- package/dist/services/notification.js.map +1 -1
- package/dist/services/section.d.ts +6 -6
- package/dist/services/studentProgress.d.ts +75 -0
- package/dist/services/studentProgress.d.ts.map +1 -1
- package/dist/services/studentProgress.js +300 -107
- package/dist/services/studentProgress.js.map +1 -1
- package/dist/services/worksheet.d.ts +64 -64
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +138 -54
- package/dist/services/worksheet.js.map +1 -1
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +11 -17
- package/dist/trpc.js.map +1 -1
- package/dist/utils/inference.d.ts +9 -12
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +135 -95
- package/dist/utils/inference.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +14 -0
- package/prisma/migrations/20260415103500_add_session_type/migration.sql +33 -0
- package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +19 -0
- package/prisma/schema.prisma +41 -1
- package/scripts/test-pre-push.ts +6 -2
- package/src/index.ts +119 -79
- package/src/lib/config/cors.ts +96 -0
- package/src/lib/config/env.ts +12 -1
- package/src/lib/fileUpload.ts +55 -5
- package/src/lib/prisma.ts +25 -6
- package/src/middleware/auth.ts +19 -4
- package/src/middleware/security.ts +1 -1
- package/src/models/auth.ts +59 -29
- package/src/models/class.ts +15 -4
- package/src/models/notification.ts +13 -7
- package/src/models/worksheet.ts +202 -0
- package/src/pipelines/aiLabChat.ts +183 -104
- package/src/pipelines/aiNewtonChat.ts +24 -14
- package/src/pipelines/gradeWorksheet.ts +10 -2
- package/src/pipelines/labChatPrompt.ts +196 -68
- package/src/routers/assignment.ts +1 -0
- package/src/routers/class.ts +2 -2
- package/src/routers/labChat.ts +3 -1
- package/src/routers/message.ts +38 -1
- package/src/routers/newtonChat.ts +4 -2
- package/src/routers/notifications.ts +2 -2
- package/src/routers/studentProgress.ts +25 -1
- package/src/routers/worksheet.ts +31 -19
- package/src/services/assignment.ts +30 -2
- package/src/services/auth.ts +26 -12
- package/src/services/class.ts +4 -1
- package/src/services/file.ts +6 -0
- package/src/services/labChat.ts +42 -18
- package/src/services/message.ts +163 -18
- package/src/services/newtonChat.ts +49 -18
- package/src/services/notification.ts +27 -8
- package/src/services/studentProgress.ts +425 -121
- package/src/services/worksheet.ts +191 -51
- package/src/trpc.ts +12 -18
- package/src/utils/inference.ts +205 -112
- package/tests/globalSetup.ts +13 -4
- package/tests/lib/cors.test.ts +103 -0
- package/tests/pipelines/aiLabChat.test.ts +64 -84
- package/tests/routers/auth.test.ts +120 -17
- package/tests/routers/class.test.ts +7 -1
- package/tests/routers/message.test.ts +236 -9
- package/tests/routers/newtonChat.test.ts +81 -0
- package/tests/routers/notification.test.ts +12 -0
- package/tests/routers/studentProgress.test.ts +2 -31
- package/tests/routers/worksheet.test.ts +194 -0
- package/tests/setup.ts +22 -5
- package/tests/utils/aiLabChatPrompt.test.ts +114 -6
- package/tests/utils/inference.test.ts +218 -0
- package/tests/utils/studentProgress.test.ts +361 -0
- package/vitest.unit.config.ts +1 -0
|
@@ -7,8 +7,9 @@ import { GenerationStatus, type WorksheetQuestionType } from "@prisma/client";
|
|
|
7
7
|
import { prisma } from "../lib/prisma.js";
|
|
8
8
|
import { cancelGradePipeline, regradeWorksheetPipeline } from "../pipelines/gradeWorksheet.js";
|
|
9
9
|
import {
|
|
10
|
-
findWorksheetById,
|
|
11
10
|
findWorksheetByIdMinimal,
|
|
11
|
+
findWorksheetByIdForClassMember,
|
|
12
|
+
findWorksheetByIdForTeacher,
|
|
12
13
|
findWorksheetsByClassId,
|
|
13
14
|
createWorksheet,
|
|
14
15
|
updateWorksheet,
|
|
@@ -17,30 +18,63 @@ import {
|
|
|
17
18
|
createWorksheetQuestion,
|
|
18
19
|
updateWorksheetQuestion,
|
|
19
20
|
deleteWorksheetQuestion,
|
|
20
|
-
|
|
21
|
+
findSubmissionByIdForWorksheetAccess,
|
|
21
22
|
findOrCreateWorksheetResponse,
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
findWorksheetResponseByIdForTeacher,
|
|
24
|
+
findWorksheetResponseWithResponsesForStudent,
|
|
25
|
+
findWorksheetQuestionByIdForTeacher,
|
|
26
|
+
findWorksheetQuestionByIdInWorksheet,
|
|
24
27
|
updateStudentQuestionProgress,
|
|
25
28
|
createStudentQuestionProgress,
|
|
26
29
|
findStudentQuestionProgress,
|
|
27
30
|
updateStudentQuestionProgressForGrading,
|
|
28
31
|
createStudentQuestionProgressForGrading,
|
|
29
32
|
createComment,
|
|
33
|
+
findStudentQuestionProgressByIdForTeacher,
|
|
34
|
+
findStudentQuestionProgressByIdForTeacherAndResponse,
|
|
30
35
|
} from "../models/worksheet.js";
|
|
31
36
|
|
|
32
37
|
type MCQOptions = { id: string; text: string; isCorrect: boolean }[];
|
|
33
38
|
|
|
39
|
+
const sanitizeQuestionOptionsForStudent = (options: unknown) => {
|
|
40
|
+
if (!Array.isArray(options)) return options;
|
|
41
|
+
|
|
42
|
+
return options.map((option) => {
|
|
43
|
+
if (!option || typeof option !== "object") return option;
|
|
44
|
+
|
|
45
|
+
const { isCorrect: _isCorrect, ...rest } = option as Record<string, unknown>;
|
|
46
|
+
return rest;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const sanitizeWorksheetForStudent = <T extends {
|
|
51
|
+
questions: Array<Record<string, unknown>>;
|
|
52
|
+
}>(worksheet: T) => ({
|
|
53
|
+
...worksheet,
|
|
54
|
+
questions: worksheet.questions.map((question) => ({
|
|
55
|
+
...question,
|
|
56
|
+
answer: "",
|
|
57
|
+
markScheme: null,
|
|
58
|
+
options: sanitizeQuestionOptionsForStudent(question.options),
|
|
59
|
+
})),
|
|
60
|
+
});
|
|
61
|
+
|
|
34
62
|
/** Get worksheet with questions and mark schemes. */
|
|
35
|
-
export async function getWorksheet(worksheetId: string) {
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
63
|
+
export async function getWorksheet(worksheetId: string, userId: string) {
|
|
64
|
+
const teacherWorksheet = await findWorksheetByIdForTeacher(worksheetId, userId);
|
|
65
|
+
if (teacherWorksheet) {
|
|
66
|
+
return teacherWorksheet;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const studentWorksheet = await findWorksheetByIdForClassMember(worksheetId, userId);
|
|
70
|
+
if (!studentWorksheet) {
|
|
38
71
|
throw new TRPCError({
|
|
39
|
-
code: "
|
|
40
|
-
message: "
|
|
72
|
+
code: "FORBIDDEN",
|
|
73
|
+
message: "You do not have access to this worksheet",
|
|
41
74
|
});
|
|
42
75
|
}
|
|
43
|
-
|
|
76
|
+
|
|
77
|
+
return sanitizeWorksheetForStudent(studentWorksheet);
|
|
44
78
|
}
|
|
45
79
|
|
|
46
80
|
/** Check if worksheet exists. */
|
|
@@ -59,12 +93,27 @@ export async function listWorksheets(classId: string) {
|
|
|
59
93
|
|
|
60
94
|
export async function updateWorksheetRecord(
|
|
61
95
|
worksheetId: string,
|
|
96
|
+
userId: string,
|
|
62
97
|
data: { name?: string }
|
|
63
98
|
) {
|
|
99
|
+
const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);
|
|
100
|
+
if (!worksheet) {
|
|
101
|
+
throw new TRPCError({
|
|
102
|
+
code: "FORBIDDEN",
|
|
103
|
+
message: "Only class teachers can update worksheets",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
64
106
|
return updateWorksheet(worksheetId, data);
|
|
65
107
|
}
|
|
66
108
|
|
|
67
|
-
export async function deleteWorksheetRecord(worksheetId: string) {
|
|
109
|
+
export async function deleteWorksheetRecord(worksheetId: string, userId: string) {
|
|
110
|
+
const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);
|
|
111
|
+
if (!worksheet) {
|
|
112
|
+
throw new TRPCError({
|
|
113
|
+
code: "FORBIDDEN",
|
|
114
|
+
message: "Only class teachers can delete worksheets",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
68
117
|
return deleteWorksheet(worksheetId);
|
|
69
118
|
}
|
|
70
119
|
|
|
@@ -74,6 +123,7 @@ export async function createWorksheetRecord(classId: string, name: string) {
|
|
|
74
123
|
|
|
75
124
|
export async function addQuestionToWorksheet(
|
|
76
125
|
worksheetId: string,
|
|
126
|
+
userId: string,
|
|
77
127
|
data: {
|
|
78
128
|
question: string;
|
|
79
129
|
answer: string;
|
|
@@ -83,11 +133,11 @@ export async function addQuestionToWorksheet(
|
|
|
83
133
|
type: WorksheetQuestionType;
|
|
84
134
|
}
|
|
85
135
|
) {
|
|
86
|
-
const worksheet = await
|
|
136
|
+
const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);
|
|
87
137
|
if (!worksheet) {
|
|
88
138
|
throw new TRPCError({
|
|
89
|
-
code: "
|
|
90
|
-
message: "
|
|
139
|
+
code: "FORBIDDEN",
|
|
140
|
+
message: "Only class teachers can modify worksheet questions",
|
|
91
141
|
});
|
|
92
142
|
}
|
|
93
143
|
return createWorksheetQuestion({
|
|
@@ -98,15 +148,16 @@ export async function addQuestionToWorksheet(
|
|
|
98
148
|
|
|
99
149
|
export async function reorderWorksheetQuestions(
|
|
100
150
|
worksheetId: string,
|
|
151
|
+
userId: string,
|
|
101
152
|
movedId: string,
|
|
102
153
|
position: "before" | "after",
|
|
103
154
|
targetId: string
|
|
104
155
|
) {
|
|
105
|
-
const worksheet = await
|
|
156
|
+
const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);
|
|
106
157
|
if (!worksheet) {
|
|
107
158
|
throw new TRPCError({
|
|
108
|
-
code: "
|
|
109
|
-
message: "
|
|
159
|
+
code: "FORBIDDEN",
|
|
160
|
+
message: "Only class teachers can reorder worksheet questions",
|
|
110
161
|
});
|
|
111
162
|
}
|
|
112
163
|
|
|
@@ -158,6 +209,7 @@ export async function reorderWorksheetQuestions(
|
|
|
158
209
|
export async function updateWorksheetQuestionRecord(
|
|
159
210
|
worksheetId: string,
|
|
160
211
|
questionId: string,
|
|
212
|
+
userId: string,
|
|
161
213
|
data: {
|
|
162
214
|
question?: string;
|
|
163
215
|
answer?: string;
|
|
@@ -167,11 +219,11 @@ export async function updateWorksheetQuestionRecord(
|
|
|
167
219
|
type?: WorksheetQuestionType;
|
|
168
220
|
}
|
|
169
221
|
) {
|
|
170
|
-
const
|
|
171
|
-
if (!
|
|
222
|
+
const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);
|
|
223
|
+
if (!question || question.worksheetId !== worksheetId) {
|
|
172
224
|
throw new TRPCError({
|
|
173
|
-
code: "
|
|
174
|
-
message: "
|
|
225
|
+
code: "FORBIDDEN",
|
|
226
|
+
message: "Only class teachers can update worksheet questions",
|
|
175
227
|
});
|
|
176
228
|
}
|
|
177
229
|
return updateWorksheetQuestion(questionId, data);
|
|
@@ -179,13 +231,14 @@ export async function updateWorksheetQuestionRecord(
|
|
|
179
231
|
|
|
180
232
|
export async function deleteWorksheetQuestionRecord(
|
|
181
233
|
worksheetId: string,
|
|
182
|
-
questionId: string
|
|
234
|
+
questionId: string,
|
|
235
|
+
userId: string
|
|
183
236
|
) {
|
|
184
|
-
const
|
|
185
|
-
if (!
|
|
237
|
+
const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);
|
|
238
|
+
if (!question || question.worksheetId !== worksheetId) {
|
|
186
239
|
throw new TRPCError({
|
|
187
|
-
code: "
|
|
188
|
-
message: "
|
|
240
|
+
code: "FORBIDDEN",
|
|
241
|
+
message: "Only class teachers can delete worksheet questions",
|
|
189
242
|
});
|
|
190
243
|
}
|
|
191
244
|
return deleteWorksheetQuestion(questionId);
|
|
@@ -193,13 +246,27 @@ export async function deleteWorksheetQuestionRecord(
|
|
|
193
246
|
|
|
194
247
|
export async function getWorksheetSubmission(
|
|
195
248
|
worksheetId: string,
|
|
196
|
-
submissionId: string
|
|
249
|
+
submissionId: string,
|
|
250
|
+
userId: string
|
|
197
251
|
) {
|
|
198
|
-
const
|
|
252
|
+
const worksheet = await findWorksheetByIdForClassMember(worksheetId, userId);
|
|
253
|
+
if (!worksheet) {
|
|
254
|
+
throw new TRPCError({
|
|
255
|
+
code: "FORBIDDEN",
|
|
256
|
+
message: "You do not have access to this worksheet",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const submission = await findSubmissionByIdForWorksheetAccess(
|
|
261
|
+
submissionId,
|
|
262
|
+
worksheetId,
|
|
263
|
+
worksheet.classId,
|
|
264
|
+
userId
|
|
265
|
+
);
|
|
199
266
|
if (!submission) {
|
|
200
267
|
throw new TRPCError({
|
|
201
|
-
code: "
|
|
202
|
-
message: "
|
|
268
|
+
code: "FORBIDDEN",
|
|
269
|
+
message: "You do not have access to this worksheet submission",
|
|
203
270
|
});
|
|
204
271
|
}
|
|
205
272
|
return findOrCreateWorksheetResponse(
|
|
@@ -215,25 +282,36 @@ export async function answerWorksheetQuestion(
|
|
|
215
282
|
response: string,
|
|
216
283
|
userId: string
|
|
217
284
|
) {
|
|
218
|
-
const worksheetResponse = await
|
|
285
|
+
const worksheetResponse = await findWorksheetResponseWithResponsesForStudent(
|
|
219
286
|
worksheetResponseId,
|
|
287
|
+
userId,
|
|
220
288
|
questionId
|
|
221
289
|
);
|
|
222
290
|
if (!worksheetResponse) {
|
|
291
|
+
throw new TRPCError({
|
|
292
|
+
code: "FORBIDDEN",
|
|
293
|
+
message: "You can only answer your own worksheet responses",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const question = await findWorksheetQuestionByIdInWorksheet(
|
|
298
|
+
questionId,
|
|
299
|
+
worksheetResponse.worksheetId
|
|
300
|
+
);
|
|
301
|
+
if (!question) {
|
|
223
302
|
throw new TRPCError({
|
|
224
303
|
code: "NOT_FOUND",
|
|
225
|
-
message: "Worksheet
|
|
304
|
+
message: "Worksheet question not found",
|
|
226
305
|
});
|
|
227
306
|
}
|
|
228
307
|
|
|
229
|
-
const question = await findWorksheetQuestionById(questionId);
|
|
230
308
|
const isMarkableByAlgo =
|
|
231
|
-
question
|
|
232
|
-
const marksAwardedIfCorrect = question
|
|
309
|
+
question.type === "MULTIPLE_CHOICE" || question.type === "TRUE_FALSE";
|
|
310
|
+
const marksAwardedIfCorrect = question.points || 0;
|
|
233
311
|
const correctAnswer = isMarkableByAlgo
|
|
234
|
-
? question
|
|
235
|
-
? (question
|
|
236
|
-
: question
|
|
312
|
+
? question.type === "MULTIPLE_CHOICE"
|
|
313
|
+
? (question.options as MCQOptions)?.find((o) => o.isCorrect)?.id
|
|
314
|
+
: question.answer?.toString()
|
|
237
315
|
: null;
|
|
238
316
|
|
|
239
317
|
const existingResponse = worksheetResponse.responses[0];
|
|
@@ -273,21 +351,46 @@ export async function answerWorksheetQuestion(
|
|
|
273
351
|
|
|
274
352
|
export async function cancelGrading(
|
|
275
353
|
worksheetResponseId: string,
|
|
276
|
-
progressId: string
|
|
354
|
+
progressId: string,
|
|
355
|
+
userId: string
|
|
277
356
|
) {
|
|
357
|
+
const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(
|
|
358
|
+
progressId,
|
|
359
|
+
worksheetResponseId,
|
|
360
|
+
userId
|
|
361
|
+
);
|
|
362
|
+
if (!progress) {
|
|
363
|
+
throw new TRPCError({
|
|
364
|
+
code: "FORBIDDEN",
|
|
365
|
+
message: "Only class teachers can cancel worksheet grading",
|
|
366
|
+
});
|
|
367
|
+
}
|
|
278
368
|
return cancelGradePipeline(worksheetResponseId, progressId);
|
|
279
369
|
}
|
|
280
370
|
|
|
281
371
|
export async function regradeQuestion(
|
|
282
372
|
worksheetResponseId: string,
|
|
283
|
-
progressId: string
|
|
373
|
+
progressId: string,
|
|
374
|
+
userId: string
|
|
284
375
|
) {
|
|
376
|
+
const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(
|
|
377
|
+
progressId,
|
|
378
|
+
worksheetResponseId,
|
|
379
|
+
userId
|
|
380
|
+
);
|
|
381
|
+
if (!progress) {
|
|
382
|
+
throw new TRPCError({
|
|
383
|
+
code: "FORBIDDEN",
|
|
384
|
+
message: "Only class teachers can regrade worksheet answers",
|
|
385
|
+
});
|
|
386
|
+
}
|
|
285
387
|
return regradeWorksheetPipeline(worksheetResponseId, progressId);
|
|
286
388
|
}
|
|
287
389
|
|
|
288
390
|
export async function gradeAnswer(
|
|
289
391
|
questionId: string,
|
|
290
392
|
studentWorksheetResponseId: string,
|
|
393
|
+
userId: string,
|
|
291
394
|
data: {
|
|
292
395
|
responseId?: string;
|
|
293
396
|
response?: string;
|
|
@@ -297,7 +400,44 @@ export async function gradeAnswer(
|
|
|
297
400
|
points?: number;
|
|
298
401
|
}
|
|
299
402
|
) {
|
|
403
|
+
const worksheetResponse = await findWorksheetResponseByIdForTeacher(
|
|
404
|
+
studentWorksheetResponseId,
|
|
405
|
+
userId
|
|
406
|
+
);
|
|
407
|
+
if (!worksheetResponse) {
|
|
408
|
+
throw new TRPCError({
|
|
409
|
+
code: "FORBIDDEN",
|
|
410
|
+
message: "Only class teachers can grade worksheet answers",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const question = await findWorksheetQuestionByIdInWorksheet(
|
|
415
|
+
questionId,
|
|
416
|
+
worksheetResponse.worksheetId
|
|
417
|
+
);
|
|
418
|
+
if (!question) {
|
|
419
|
+
throw new TRPCError({
|
|
420
|
+
code: "NOT_FOUND",
|
|
421
|
+
message: "Worksheet question not found",
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
300
425
|
if (data.responseId) {
|
|
426
|
+
const existingProgress = await findStudentQuestionProgressByIdForTeacher(
|
|
427
|
+
data.responseId,
|
|
428
|
+
userId
|
|
429
|
+
);
|
|
430
|
+
if (
|
|
431
|
+
!existingProgress ||
|
|
432
|
+
existingProgress.studentWorksheetResponseId !== studentWorksheetResponseId ||
|
|
433
|
+
existingProgress.questionId !== questionId
|
|
434
|
+
) {
|
|
435
|
+
throw new TRPCError({
|
|
436
|
+
code: "FORBIDDEN",
|
|
437
|
+
message: "Only class teachers can grade worksheet answers",
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
301
441
|
return updateStudentQuestionProgressForGrading(data.responseId, {
|
|
302
442
|
isCorrect: data.isCorrect,
|
|
303
443
|
feedback: data.feedback,
|
|
@@ -306,17 +446,6 @@ export async function gradeAnswer(
|
|
|
306
446
|
});
|
|
307
447
|
}
|
|
308
448
|
|
|
309
|
-
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
310
|
-
where: { id: studentWorksheetResponseId },
|
|
311
|
-
select: { studentId: true },
|
|
312
|
-
});
|
|
313
|
-
if (!worksheetResponse) {
|
|
314
|
-
throw new TRPCError({
|
|
315
|
-
code: "NOT_FOUND",
|
|
316
|
-
message: "Student worksheet response not found",
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
449
|
const existing = await findStudentQuestionProgress(
|
|
321
450
|
worksheetResponse.studentId,
|
|
322
451
|
questionId,
|
|
@@ -350,6 +479,17 @@ export async function addCommentToResponse(
|
|
|
350
479
|
comment: string,
|
|
351
480
|
authorId: string
|
|
352
481
|
) {
|
|
482
|
+
const progress = await findStudentQuestionProgressByIdForTeacher(
|
|
483
|
+
responseId,
|
|
484
|
+
authorId
|
|
485
|
+
);
|
|
486
|
+
if (!progress) {
|
|
487
|
+
throw new TRPCError({
|
|
488
|
+
code: "FORBIDDEN",
|
|
489
|
+
message: "Only class teachers can comment on worksheet responses",
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
353
493
|
return createComment({
|
|
354
494
|
studentQuestionProgressId: responseId,
|
|
355
495
|
content: comment,
|
package/src/trpc.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { initTRPC, TRPCError } from '@trpc/server';
|
|
2
2
|
import { ZodError } from 'zod';
|
|
3
3
|
import { logger } from './utils/logger.js';
|
|
4
|
-
import { prisma } from './lib/prisma.js';
|
|
5
4
|
import { createLoggingMiddleware } from './middleware/logging.js';
|
|
6
5
|
import { createAuthMiddleware } from './middleware/auth.js';
|
|
7
6
|
import { Request, Response } from 'express';
|
|
8
7
|
import { z } from 'zod';
|
|
9
8
|
import { handlePrismaError, PrismaErrorInfo } from './utils/prismaErrorHandler.js';
|
|
10
|
-
import {
|
|
9
|
+
import { findUserBySessionToken } from './models/auth.js';
|
|
11
10
|
|
|
12
11
|
interface CreateContextOptions {
|
|
13
12
|
req: Request;
|
|
@@ -26,22 +25,17 @@ export type Context = {
|
|
|
26
25
|
|
|
27
26
|
export const createTRPCContext = async (opts: CreateContextOptions): Promise<Context> => {
|
|
28
27
|
const { req, res } = opts;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
select: {
|
|
41
|
-
id: true,
|
|
42
|
-
}
|
|
43
|
-
}) : null;
|
|
44
|
-
|
|
28
|
+
|
|
29
|
+
const userHeader = req.headers["x-user"];
|
|
30
|
+
const tokenFromUserHeader = typeof userHeader === "string" ? userHeader : userHeader?.[0];
|
|
31
|
+
const authorizationHeader = req.headers.authorization;
|
|
32
|
+
const tokenFromAuthorization =
|
|
33
|
+
typeof authorizationHeader === "string" && authorizationHeader.startsWith("Bearer ")
|
|
34
|
+
? authorizationHeader.slice(7)
|
|
35
|
+
: null;
|
|
36
|
+
const token = tokenFromUserHeader ?? tokenFromAuthorization;
|
|
37
|
+
const user = token ? await findUserBySessionToken(token) : null;
|
|
38
|
+
|
|
45
39
|
return {
|
|
46
40
|
req,
|
|
47
41
|
res,
|