@studious-lms/server 1.4.2 → 1.4.4
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/.vscode/settings.json +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -29
- package/dist/index.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/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +18 -5
- package/dist/middleware/auth.js.map +1 -1
- package/dist/models/auth.d.ts +19 -14
- 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 +3 -1
- 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/notification.d.ts +11 -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/worksheet.d.ts +379 -7
- 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.map +1 -1
- package/dist/pipelines/aiLabChat.js +11 -3
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +3 -3
- 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.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +10 -3
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/pdfToWorksheet.d.ts +12 -0
- package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -0
- package/dist/pipelines/pdfToWorksheet.js +155 -0
- package/dist/pipelines/pdfToWorksheet.js.map +1 -0
- package/dist/routers/_app.d.ts +264 -16
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/class.d.ts +2 -1
- 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/labChat.d.ts +6 -0
- 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/message.d.ts +31 -0
- 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 +6 -0
- 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 +9 -2
- 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/worksheet.d.ts +78 -5
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +126 -17
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/services/auth.d.ts +1 -1
- 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 +1 -1
- 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/file.d.ts.map +1 -1
- package/dist/services/file.js +7 -2
- package/dist/services/file.js.map +1 -1
- package/dist/services/labChat.d.ts +6 -0
- 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/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 +12 -5
- 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/studentProgress.d.ts.map +1 -1
- package/dist/services/studentProgress.js +6 -3
- package/dist/services/studentProgress.js.map +1 -1
- package/dist/services/worksheet.d.ts +36 -15
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +186 -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 +10 -1
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +186 -50
- package/dist/utils/inference.js.map +1 -1
- package/package.json +1 -1
- 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 +27 -1
- package/scripts/test-pre-push.ts +6 -2
- package/src/index.ts +83 -30
- package/src/lib/fileUpload.ts +55 -5
- package/src/middleware/auth.ts +19 -4
- 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 +203 -0
- package/src/pipelines/aiLabChat.ts +9 -1
- package/src/pipelines/aiNewtonChat.ts +24 -14
- package/src/pipelines/gradeWorksheet.ts +8 -0
- package/src/pipelines/pdfToWorksheet.ts +177 -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/worksheet.ts +174 -19
- 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 +4 -1
- package/src/services/worksheet.ts +252 -51
- package/src/trpc.ts +12 -18
- package/src/utils/inference.ts +280 -56
- package/tests/globalSetup.ts +13 -4
- package/tests/pipelines/pdfToWorksheet.test.ts +176 -0
- 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/worksheet.test.ts +194 -0
- package/tests/setup.ts +22 -5
- package/tests/utils/inference.test.ts +255 -0
package/src/services/message.ts
CHANGED
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
* Message service – send, update, delete messages; mark as read; track mentions.
|
|
3
3
|
* Broadcasts real-time updates via Pusher.
|
|
4
4
|
*/
|
|
5
|
+
import type { Prisma } from "@prisma/client";
|
|
5
6
|
import { TRPCError } from "@trpc/server";
|
|
6
7
|
import { prisma } from "../lib/prisma.js";
|
|
7
8
|
import { chatChannel, pusher } from "../lib/pusher.js";
|
|
9
|
+
import {
|
|
10
|
+
confirmDirectUpload,
|
|
11
|
+
createDirectUploadFiles,
|
|
12
|
+
} from "../lib/fileUpload.js";
|
|
8
13
|
import { logger } from "../utils/logger.js";
|
|
9
14
|
import {
|
|
10
15
|
findConversationMembership,
|
|
@@ -16,6 +21,126 @@ import {
|
|
|
16
21
|
countUnreadMentions,
|
|
17
22
|
} from "../models/message.js";
|
|
18
23
|
|
|
24
|
+
export function ensureMessageHasContentOrAttachments(
|
|
25
|
+
content: string,
|
|
26
|
+
fileIds: string[] = []
|
|
27
|
+
) {
|
|
28
|
+
if (!content.trim() && fileIds.length === 0) {
|
|
29
|
+
throw new TRPCError({
|
|
30
|
+
code: "BAD_REQUEST",
|
|
31
|
+
message: "Message must include content or attachments",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function claimMessageAttachments(
|
|
37
|
+
tx: Prisma.TransactionClient,
|
|
38
|
+
userId: string,
|
|
39
|
+
conversationId: string,
|
|
40
|
+
messageId: string,
|
|
41
|
+
fileIds: string[] = []
|
|
42
|
+
) {
|
|
43
|
+
const uniqueFileIds = [...new Set(fileIds)];
|
|
44
|
+
|
|
45
|
+
if (uniqueFileIds.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { count } = await tx.file.updateMany({
|
|
50
|
+
where: {
|
|
51
|
+
id: { in: uniqueFileIds },
|
|
52
|
+
userId,
|
|
53
|
+
conversationId,
|
|
54
|
+
uploadStatus: "COMPLETED",
|
|
55
|
+
messageId: null,
|
|
56
|
+
},
|
|
57
|
+
data: {
|
|
58
|
+
messageId,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (count !== uniqueFileIds.length) {
|
|
63
|
+
throw new TRPCError({
|
|
64
|
+
code: "BAD_REQUEST",
|
|
65
|
+
message: "Some attachments are missing, unfinished, or unavailable",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function getMessageUploadUrls(
|
|
71
|
+
userId: string,
|
|
72
|
+
input: {
|
|
73
|
+
conversationId: string;
|
|
74
|
+
files: { name: string; type: string; size: number }[];
|
|
75
|
+
}
|
|
76
|
+
) {
|
|
77
|
+
const membership = await findConversationMembership(input.conversationId, userId);
|
|
78
|
+
if (!membership) {
|
|
79
|
+
throw new TRPCError({
|
|
80
|
+
code: "FORBIDDEN",
|
|
81
|
+
message: "Not a member of this conversation",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return createDirectUploadFiles(
|
|
86
|
+
input.files,
|
|
87
|
+
userId,
|
|
88
|
+
undefined,
|
|
89
|
+
undefined,
|
|
90
|
+
undefined,
|
|
91
|
+
undefined,
|
|
92
|
+
input.conversationId
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function confirmMessageUpload(
|
|
97
|
+
userId: string,
|
|
98
|
+
input: {
|
|
99
|
+
conversationId: string;
|
|
100
|
+
fileId: string;
|
|
101
|
+
uploadSuccess: boolean;
|
|
102
|
+
errorMessage?: string;
|
|
103
|
+
}
|
|
104
|
+
) {
|
|
105
|
+
const membership = await findConversationMembership(input.conversationId, userId);
|
|
106
|
+
if (!membership) {
|
|
107
|
+
throw new TRPCError({
|
|
108
|
+
code: "FORBIDDEN",
|
|
109
|
+
message: "Not a member of this conversation",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const file = await prisma.file.findFirst({
|
|
114
|
+
where: {
|
|
115
|
+
id: input.fileId,
|
|
116
|
+
userId,
|
|
117
|
+
conversationId: input.conversationId,
|
|
118
|
+
messageId: null,
|
|
119
|
+
},
|
|
120
|
+
select: { id: true },
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!file) {
|
|
124
|
+
throw new TRPCError({
|
|
125
|
+
code: "NOT_FOUND",
|
|
126
|
+
message: "File not found or you do not have permission",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await confirmDirectUpload(
|
|
131
|
+
input.fileId,
|
|
132
|
+
input.uploadSuccess,
|
|
133
|
+
input.errorMessage
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
message: input.uploadSuccess
|
|
139
|
+
? "Upload confirmed successfully"
|
|
140
|
+
: "Upload failed",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
19
144
|
/** List messages in a conversation with cursor-based pagination. */
|
|
20
145
|
export async function listMessages(
|
|
21
146
|
userId: string,
|
|
@@ -74,9 +199,18 @@ export async function sendMessage(
|
|
|
74
199
|
conversationId: string;
|
|
75
200
|
content: string;
|
|
76
201
|
mentionedUserIds?: string[];
|
|
202
|
+
fileIds?: string[];
|
|
77
203
|
}
|
|
78
204
|
) {
|
|
79
|
-
const {
|
|
205
|
+
const {
|
|
206
|
+
conversationId,
|
|
207
|
+
content,
|
|
208
|
+
mentionedUserIds = [],
|
|
209
|
+
fileIds = [],
|
|
210
|
+
} = input;
|
|
211
|
+
|
|
212
|
+
ensureMessageHasContentOrAttachments(content, fileIds);
|
|
213
|
+
const normalizedContent = content.trim();
|
|
80
214
|
|
|
81
215
|
const membership = await findConversationMembership(conversationId, userId);
|
|
82
216
|
if (!membership) {
|
|
@@ -102,10 +236,36 @@ export async function sendMessage(
|
|
|
102
236
|
const result = await prisma.$transaction(async (tx) => {
|
|
103
237
|
const message = await tx.message.create({
|
|
104
238
|
data: {
|
|
105
|
-
content,
|
|
239
|
+
content: normalizedContent,
|
|
106
240
|
senderId: userId,
|
|
107
241
|
conversationId,
|
|
108
242
|
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await claimMessageAttachments(
|
|
246
|
+
tx,
|
|
247
|
+
userId,
|
|
248
|
+
conversationId,
|
|
249
|
+
message.id,
|
|
250
|
+
fileIds
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
if (mentionedUserIds.length > 0) {
|
|
254
|
+
await tx.mention.createMany({
|
|
255
|
+
data: mentionedUserIds.map((mentionedUserId) => ({
|
|
256
|
+
messageId: message.id,
|
|
257
|
+
userId: mentionedUserId,
|
|
258
|
+
})),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await tx.conversation.update({
|
|
263
|
+
where: { id: conversationId },
|
|
264
|
+
data: { updatedAt: new Date() },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return tx.message.findUniqueOrThrow({
|
|
268
|
+
where: { id: message.id },
|
|
109
269
|
include: {
|
|
110
270
|
sender: {
|
|
111
271
|
select: {
|
|
@@ -124,22 +284,6 @@ export async function sendMessage(
|
|
|
124
284
|
},
|
|
125
285
|
},
|
|
126
286
|
});
|
|
127
|
-
|
|
128
|
-
if (mentionedUserIds.length > 0) {
|
|
129
|
-
await tx.mention.createMany({
|
|
130
|
-
data: mentionedUserIds.map((mentionedUserId) => ({
|
|
131
|
-
messageId: message.id,
|
|
132
|
-
userId: mentionedUserId,
|
|
133
|
-
})),
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
await tx.conversation.update({
|
|
138
|
-
where: { id: conversationId },
|
|
139
|
-
data: { updatedAt: new Date() },
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
return message;
|
|
143
287
|
});
|
|
144
288
|
|
|
145
289
|
try {
|
|
@@ -173,6 +317,7 @@ export async function sendMessage(
|
|
|
173
317
|
conversationId: result.conversationId,
|
|
174
318
|
createdAt: result.createdAt,
|
|
175
319
|
sender: result.sender,
|
|
320
|
+
attachments: result.attachments ?? [],
|
|
176
321
|
mentionedUserIds,
|
|
177
322
|
};
|
|
178
323
|
}
|
|
@@ -7,6 +7,10 @@ import { logger } from "../utils/logger.js";
|
|
|
7
7
|
import { chatChannel, pusher } from "../lib/pusher.js";
|
|
8
8
|
import { isAIUser } from "../utils/aiUser.js";
|
|
9
9
|
import { generateAndSendNewtonIntroduction, generateAndSendNewtonResponse } from "../pipelines/aiNewtonChat.js";
|
|
10
|
+
import {
|
|
11
|
+
claimMessageAttachments,
|
|
12
|
+
ensureMessageHasContentOrAttachments,
|
|
13
|
+
} from "./message.js";
|
|
10
14
|
import {
|
|
11
15
|
findClassWithStudent,
|
|
12
16
|
findSubmissionByAssignmentAndStudent,
|
|
@@ -118,7 +122,8 @@ export async function postToNewtonChat(
|
|
|
118
122
|
userId: string,
|
|
119
123
|
newtonChatId: string,
|
|
120
124
|
content: string,
|
|
121
|
-
mentionedUserIds: string[] = []
|
|
125
|
+
mentionedUserIds: string[] = [],
|
|
126
|
+
fileIds: string[] = []
|
|
122
127
|
) {
|
|
123
128
|
const newtonChat = await findNewtonChatByIdAndMember(newtonChatId, userId);
|
|
124
129
|
if (!newtonChat) {
|
|
@@ -141,29 +146,33 @@ export async function postToNewtonChat(
|
|
|
141
146
|
}
|
|
142
147
|
}
|
|
143
148
|
|
|
149
|
+
ensureMessageHasContentOrAttachments(content, fileIds);
|
|
150
|
+
const normalizedContent = content.trim();
|
|
151
|
+
|
|
152
|
+
if (!normalizedContent) {
|
|
153
|
+
throw new TRPCError({
|
|
154
|
+
code: "BAD_REQUEST",
|
|
155
|
+
message: "Newton chat messages must include text content",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
144
159
|
const result = await prisma.$transaction(async (tx) => {
|
|
145
160
|
const message = await tx.message.create({
|
|
146
161
|
data: {
|
|
147
|
-
content,
|
|
162
|
+
content: normalizedContent,
|
|
148
163
|
senderId: userId,
|
|
149
164
|
conversationId: newtonChat.conversationId,
|
|
150
165
|
},
|
|
151
|
-
include: {
|
|
152
|
-
sender: {
|
|
153
|
-
select: {
|
|
154
|
-
id: true,
|
|
155
|
-
username: true,
|
|
156
|
-
profile: {
|
|
157
|
-
select: {
|
|
158
|
-
displayName: true,
|
|
159
|
-
profilePicture: true,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
166
|
});
|
|
166
167
|
|
|
168
|
+
await claimMessageAttachments(
|
|
169
|
+
tx,
|
|
170
|
+
userId,
|
|
171
|
+
newtonChat.conversationId,
|
|
172
|
+
message.id,
|
|
173
|
+
fileIds
|
|
174
|
+
);
|
|
175
|
+
|
|
167
176
|
if (mentionedUserIds.length > 0) {
|
|
168
177
|
await tx.mention.createMany({
|
|
169
178
|
data: mentionedUserIds.map((mentionedUserId) => ({
|
|
@@ -178,7 +187,26 @@ export async function postToNewtonChat(
|
|
|
178
187
|
data: { updatedAt: new Date() },
|
|
179
188
|
});
|
|
180
189
|
|
|
181
|
-
return message
|
|
190
|
+
return tx.message.findUniqueOrThrow({
|
|
191
|
+
where: { id: message.id },
|
|
192
|
+
include: {
|
|
193
|
+
sender: {
|
|
194
|
+
select: {
|
|
195
|
+
id: true,
|
|
196
|
+
username: true,
|
|
197
|
+
profile: {
|
|
198
|
+
select: {
|
|
199
|
+
displayName: true,
|
|
200
|
+
profilePicture: true,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
attachments: {
|
|
206
|
+
select: { id: true, name: true, type: true },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
});
|
|
182
210
|
});
|
|
183
211
|
|
|
184
212
|
try {
|
|
@@ -192,7 +220,9 @@ export async function postToNewtonChat(
|
|
|
192
220
|
conversationId: result.conversationId,
|
|
193
221
|
createdAt: result.createdAt,
|
|
194
222
|
sender: result.sender,
|
|
223
|
+
attachments: result.attachments ?? [],
|
|
195
224
|
mentionedUserIds,
|
|
225
|
+
meta: {},
|
|
196
226
|
}
|
|
197
227
|
);
|
|
198
228
|
} catch (error) {
|
|
@@ -202,7 +232,7 @@ export async function postToNewtonChat(
|
|
|
202
232
|
if (!isAIUser(userId)) {
|
|
203
233
|
generateAndSendNewtonResponse(
|
|
204
234
|
newtonChatId,
|
|
205
|
-
|
|
235
|
+
normalizedContent,
|
|
206
236
|
newtonChat.conversationId,
|
|
207
237
|
newtonChat.submission
|
|
208
238
|
).catch((error) => {
|
|
@@ -217,6 +247,7 @@ export async function postToNewtonChat(
|
|
|
217
247
|
conversationId: result.conversationId,
|
|
218
248
|
createdAt: result.createdAt,
|
|
219
249
|
sender: result.sender,
|
|
250
|
+
attachments: result.attachments ?? [],
|
|
220
251
|
mentionedUserIds,
|
|
221
252
|
};
|
|
222
253
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Notification service – list, send, and mark notifications as read.
|
|
3
3
|
*/
|
|
4
|
+
import { TRPCError } from "@trpc/server";
|
|
4
5
|
import {
|
|
5
6
|
findNotificationsByReceiverId,
|
|
6
|
-
|
|
7
|
+
findNotificationByIdForReceiver,
|
|
7
8
|
createNotification,
|
|
8
9
|
createManyNotifications,
|
|
9
|
-
|
|
10
|
+
markNotificationAsReadForReceiver,
|
|
10
11
|
} from "../models/notification.js";
|
|
11
12
|
|
|
12
13
|
/** List notifications for a user. */
|
|
@@ -14,9 +15,18 @@ export async function listNotifications(userId: string) {
|
|
|
14
15
|
return findNotificationsByReceiverId(userId);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
/** Get a single notification by ID. */
|
|
18
|
-
export async function getNotification(id: string) {
|
|
19
|
-
|
|
18
|
+
/** Get a single notification by ID for its receiver. */
|
|
19
|
+
export async function getNotification(userId: string, id: string) {
|
|
20
|
+
const notification = await findNotificationByIdForReceiver(id, userId);
|
|
21
|
+
|
|
22
|
+
if (!notification) {
|
|
23
|
+
throw new TRPCError({
|
|
24
|
+
code: "NOT_FOUND",
|
|
25
|
+
message: "Notification not found",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return notification;
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
/** Send a notification to one user. */
|
|
@@ -44,7 +54,16 @@ export async function sendToMultiple(input: {
|
|
|
44
54
|
return result;
|
|
45
55
|
}
|
|
46
56
|
|
|
47
|
-
/** Mark a notification as read. */
|
|
48
|
-
export async function markAsRead(id: string) {
|
|
49
|
-
|
|
57
|
+
/** Mark a notification as read for its receiver. */
|
|
58
|
+
export async function markAsRead(userId: string, id: string) {
|
|
59
|
+
const notification = await markNotificationAsReadForReceiver(id, userId);
|
|
60
|
+
|
|
61
|
+
if (!notification) {
|
|
62
|
+
throw new TRPCError({
|
|
63
|
+
code: "NOT_FOUND",
|
|
64
|
+
message: "Notification not found",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return notification;
|
|
50
69
|
}
|
|
@@ -635,7 +635,10 @@ export async function chatAboutStudentProgress(
|
|
|
635
635
|
];
|
|
636
636
|
|
|
637
637
|
try {
|
|
638
|
-
const response = await inference<string>(messages
|
|
638
|
+
const response = await inference<string>(messages, undefined, {
|
|
639
|
+
reason: "student_progress_chat",
|
|
640
|
+
metadata: { viewerId, classId: input.classId, studentId: input.studentId },
|
|
641
|
+
});
|
|
639
642
|
if (typeof response !== "string" || response.trim().length === 0) {
|
|
640
643
|
throw new Error("Student progress chat returned an empty response");
|
|
641
644
|
}
|