@studious-lms/server 1.4.9 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/dist/index.js +15 -3
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/config/cors.d.ts.map +1 -1
  4. package/dist/lib/config/cors.js +2 -3
  5. package/dist/lib/config/cors.js.map +1 -1
  6. package/dist/lib/fileUpload.d.ts.map +1 -1
  7. package/dist/lib/fileUpload.js +4 -5
  8. package/dist/lib/fileUpload.js.map +1 -1
  9. package/dist/lib/pusher.d.ts +7 -1
  10. package/dist/lib/pusher.d.ts.map +1 -1
  11. package/dist/lib/pusher.js +9 -3
  12. package/dist/lib/pusher.js.map +1 -1
  13. package/dist/models/attendance.d.ts +16 -7
  14. package/dist/models/attendance.d.ts.map +1 -1
  15. package/dist/models/attendance.js +17 -2
  16. package/dist/models/attendance.js.map +1 -1
  17. package/dist/models/auth.d.ts +1 -0
  18. package/dist/models/auth.d.ts.map +1 -1
  19. package/dist/models/auth.js +3 -2
  20. package/dist/models/auth.js.map +1 -1
  21. package/dist/models/comment.d.ts +0 -5
  22. package/dist/models/comment.d.ts.map +1 -1
  23. package/dist/models/conversation.d.ts +17 -16
  24. package/dist/models/conversation.d.ts.map +1 -1
  25. package/dist/models/labChat.d.ts +2 -1
  26. package/dist/models/labChat.d.ts.map +1 -1
  27. package/dist/models/message.d.ts +6 -3
  28. package/dist/models/message.d.ts.map +1 -1
  29. package/dist/models/school.d.ts +0 -22
  30. package/dist/models/school.d.ts.map +1 -1
  31. package/dist/models/school.js +2 -14
  32. package/dist/models/school.js.map +1 -1
  33. package/dist/models/worksheet.d.ts +76 -19
  34. package/dist/models/worksheet.d.ts.map +1 -1
  35. package/dist/models/worksheet.js +2 -2
  36. package/dist/models/worksheet.js.map +1 -1
  37. package/dist/pipelines/aiLabChat.d.ts +20 -9
  38. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  39. package/dist/pipelines/aiLabChat.js +63 -75
  40. package/dist/pipelines/aiLabChat.js.map +1 -1
  41. package/dist/pipelines/aiNewtonChat.d.ts +13 -4
  42. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  43. package/dist/pipelines/aiNewtonChat.js +39 -27
  44. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  45. package/dist/pipelines/gradeWorksheet.d.ts +5 -3
  46. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  47. package/dist/pipelines/gradeWorksheet.js +60 -24
  48. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  49. package/dist/pipelines/pdfToWorksheet.d.ts +34 -2
  50. package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
  51. package/dist/pipelines/pdfToWorksheet.js +205 -32
  52. package/dist/pipelines/pdfToWorksheet.js.map +1 -1
  53. package/dist/routers/_app.d.ts +106 -48
  54. package/dist/routers/_app.d.ts.map +1 -1
  55. package/dist/routers/announcement.d.ts +0 -4
  56. package/dist/routers/announcement.d.ts.map +1 -1
  57. package/dist/routers/assignment.d.ts +0 -8
  58. package/dist/routers/assignment.d.ts.map +1 -1
  59. package/dist/routers/assignment.js +64 -4
  60. package/dist/routers/assignment.js.map +1 -1
  61. package/dist/routers/auth.d.ts +1 -0
  62. package/dist/routers/auth.d.ts.map +1 -1
  63. package/dist/routers/comment.d.ts +0 -3
  64. package/dist/routers/comment.d.ts.map +1 -1
  65. package/dist/routers/conversation.d.ts +2 -1
  66. package/dist/routers/conversation.d.ts.map +1 -1
  67. package/dist/routers/labChat.d.ts +3 -1
  68. package/dist/routers/labChat.d.ts.map +1 -1
  69. package/dist/routers/message.d.ts +3 -0
  70. package/dist/routers/message.d.ts.map +1 -1
  71. package/dist/routers/newtonChat.d.ts +1 -0
  72. package/dist/routers/newtonChat.d.ts.map +1 -1
  73. package/dist/routers/school.d.ts +6 -0
  74. package/dist/routers/school.d.ts.map +1 -1
  75. package/dist/routers/school.js +7 -2
  76. package/dist/routers/school.js.map +1 -1
  77. package/dist/routers/worksheet.d.ts +37 -7
  78. package/dist/routers/worksheet.d.ts.map +1 -1
  79. package/dist/routers/worksheet.js +16 -6
  80. package/dist/routers/worksheet.js.map +1 -1
  81. package/dist/services/announcement.js +4 -4
  82. package/dist/services/announcement.js.map +1 -1
  83. package/dist/services/assignment.d.ts.map +1 -1
  84. package/dist/services/assignment.js +16 -6
  85. package/dist/services/assignment.js.map +1 -1
  86. package/dist/services/attendance.d.ts.map +1 -1
  87. package/dist/services/attendance.js +68 -4
  88. package/dist/services/attendance.js.map +1 -1
  89. package/dist/services/auth.d.ts +1 -0
  90. package/dist/services/auth.d.ts.map +1 -1
  91. package/dist/services/comment.d.ts +0 -3
  92. package/dist/services/comment.d.ts.map +1 -1
  93. package/dist/services/conversation.d.ts +2 -1
  94. package/dist/services/conversation.d.ts.map +1 -1
  95. package/dist/services/labChat.d.ts +10 -2
  96. package/dist/services/labChat.d.ts.map +1 -1
  97. package/dist/services/labChat.js +103 -44
  98. package/dist/services/labChat.js.map +1 -1
  99. package/dist/services/message.d.ts +3 -0
  100. package/dist/services/message.d.ts.map +1 -1
  101. package/dist/services/message.js +20 -2
  102. package/dist/services/message.js.map +1 -1
  103. package/dist/services/newtonChat.d.ts +1 -0
  104. package/dist/services/newtonChat.d.ts.map +1 -1
  105. package/dist/services/newtonChat.js +40 -17
  106. package/dist/services/newtonChat.js.map +1 -1
  107. package/dist/services/school.d.ts +6 -0
  108. package/dist/services/school.d.ts.map +1 -1
  109. package/dist/services/school.js +71 -6
  110. package/dist/services/school.js.map +1 -1
  111. package/dist/services/worksheet.d.ts +48 -11
  112. package/dist/services/worksheet.d.ts.map +1 -1
  113. package/dist/services/worksheet.js +73 -29
  114. package/dist/services/worksheet.js.map +1 -1
  115. package/dist/utils/generateUsername.d.ts +8 -0
  116. package/dist/utils/generateUsername.d.ts.map +1 -0
  117. package/dist/utils/generateUsername.js +103 -0
  118. package/dist/utils/generateUsername.js.map +1 -0
  119. package/dist/utils/inference.d.ts +50 -1
  120. package/dist/utils/inference.d.ts.map +1 -1
  121. package/dist/utils/inference.js +167 -2
  122. package/dist/utils/inference.js.map +1 -1
  123. package/dist/workers/connection.d.ts +3 -0
  124. package/dist/workers/connection.d.ts.map +1 -0
  125. package/dist/workers/connection.js +33 -0
  126. package/dist/workers/connection.js.map +1 -0
  127. package/dist/workers/index.d.ts +10 -0
  128. package/dist/workers/index.d.ts.map +1 -0
  129. package/dist/workers/index.js +63 -0
  130. package/dist/workers/index.js.map +1 -0
  131. package/dist/workers/processors/aiLabChat.d.ts +7 -0
  132. package/dist/workers/processors/aiLabChat.d.ts.map +1 -0
  133. package/dist/workers/processors/aiLabChat.js +52 -0
  134. package/dist/workers/processors/aiLabChat.js.map +1 -0
  135. package/dist/workers/processors/aiNewtonChat.d.ts +7 -0
  136. package/dist/workers/processors/aiNewtonChat.d.ts.map +1 -0
  137. package/dist/workers/processors/aiNewtonChat.js +57 -0
  138. package/dist/workers/processors/aiNewtonChat.js.map +1 -0
  139. package/dist/workers/processors/createRunJobWorker.d.ts +46 -0
  140. package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -0
  141. package/dist/workers/processors/createRunJobWorker.js +118 -0
  142. package/dist/workers/processors/createRunJobWorker.js.map +1 -0
  143. package/dist/workers/processors/gradeWorksheet.d.ts +6 -0
  144. package/dist/workers/processors/gradeWorksheet.d.ts.map +1 -0
  145. package/dist/workers/processors/gradeWorksheet.js +19 -0
  146. package/dist/workers/processors/gradeWorksheet.js.map +1 -0
  147. package/dist/workers/processors/pdfToWorksheet.d.ts +8 -0
  148. package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -0
  149. package/dist/workers/processors/pdfToWorksheet.js +20 -0
  150. package/dist/workers/processors/pdfToWorksheet.js.map +1 -0
  151. package/dist/workers/processors/studentProgressChat.d.ts +8 -0
  152. package/dist/workers/processors/studentProgressChat.d.ts.map +1 -0
  153. package/dist/workers/processors/studentProgressChat.js +27 -0
  154. package/dist/workers/processors/studentProgressChat.js.map +1 -0
  155. package/dist/workers/queues.d.ts +85 -0
  156. package/dist/workers/queues.d.ts.map +1 -0
  157. package/dist/workers/queues.js +119 -0
  158. package/dist/workers/queues.js.map +1 -0
  159. package/package.json +6 -3
  160. package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +166 -0
  161. package/prisma/schema.prisma +465 -387
  162. package/src/index.ts +12 -1
  163. package/src/lib/config/cors.ts +0 -1
  164. package/src/lib/fileUpload.ts +2 -4
  165. package/src/lib/pusher.ts +7 -1
  166. package/src/models/attendance.ts +15 -0
  167. package/src/models/auth.ts +1 -0
  168. package/src/models/school.ts +0 -14
  169. package/src/models/worksheet.ts +2 -2
  170. package/src/pipelines/aiLabChat.ts +71 -79
  171. package/src/pipelines/aiNewtonChat.ts +47 -31
  172. package/src/pipelines/gradeWorksheet.ts +63 -25
  173. package/src/pipelines/pdfToWorksheet.ts +272 -34
  174. package/src/routers/assignment.ts +74 -3
  175. package/src/routers/school.ts +5 -0
  176. package/src/routers/worksheet.ts +25 -4
  177. package/src/services/announcement.ts +2 -2
  178. package/src/services/assignment.ts +18 -4
  179. package/src/services/attendance.ts +87 -2
  180. package/src/services/labChat.ts +108 -48
  181. package/src/services/message.ts +19 -0
  182. package/src/services/newtonChat.ts +46 -25
  183. package/src/services/school.ts +89 -6
  184. package/src/services/worksheet.ts +87 -28
  185. package/src/utils/generateUsername.ts +108 -0
  186. package/src/utils/inference.ts +209 -1
  187. package/src/workers/connection.ts +37 -0
  188. package/src/workers/index.ts +71 -0
  189. package/src/workers/processors/aiLabChat.ts +65 -0
  190. package/src/workers/processors/aiNewtonChat.ts +75 -0
  191. package/src/workers/processors/createRunJobWorker.ts +164 -0
  192. package/src/workers/processors/gradeWorksheet.ts +16 -0
  193. package/src/workers/processors/pdfToWorksheet.ts +17 -0
  194. package/src/workers/processors/studentProgressChat.ts +28 -0
  195. package/src/workers/queues.ts +174 -0
  196. package/tests/{globalSetup.ts → integration/globalSetup.ts} +4 -4
  197. package/tests/{helpers.ts → integration/helpers.ts} +2 -2
  198. package/tests/{pipelines → integration/pipelines}/pdfToWorksheet.test.ts +55 -43
  199. package/tests/{routers → integration/routers}/attendance.test.ts +61 -2
  200. package/tests/{routers → integration/routers}/auth.test.ts +3 -3
  201. package/tests/{routers → integration/routers}/class.test.ts +3 -17
  202. package/tests/{routers → integration/routers}/conversation.test.ts +1 -1
  203. package/tests/{routers → integration/routers}/event.test.ts +1 -1
  204. package/tests/{routers → integration/routers}/message.test.ts +1 -1
  205. package/tests/{routers → integration/routers}/newtonChat.test.ts +1 -1
  206. package/tests/{routers → integration/routers}/studentProgress.test.ts +1 -1
  207. package/tests/{user.test.ts → integration/routers/user.test.ts} +2 -2
  208. package/tests/integration/routers/worksheet.test.ts +448 -0
  209. package/tests/{setup.ts → integration/setup.ts} +4 -4
  210. package/tests/{lib → unit/lib}/cors.test.ts +1 -1
  211. package/tests/{middleware → unit/middleware}/security.test.ts +1 -1
  212. package/tests/{pipelines → unit/pipelines}/aiLabChat.test.ts +1 -1
  213. package/tests/{lib → unit/pipelines}/aiLabChatContract.test.ts +1 -1
  214. package/tests/{utils/aiLabChatPrompt.test.ts → unit/pipelines/labChatPrompt.test.ts} +2 -2
  215. package/tests/{utils → unit/services}/studentProgress.test.ts +5 -5
  216. package/tests/{utils → unit/utils}/aiUser.test.ts +1 -1
  217. package/tests/{utils → unit/utils}/generateInviteCode.test.ts +1 -1
  218. package/tests/{utils → unit/utils}/inference.test.ts +6 -6
  219. package/tests/{utils → unit/utils}/logger.test.ts +1 -1
  220. package/tests/{utils → unit/utils}/prismaErrorHandler.test.ts +1 -1
  221. package/tests/{utils → unit/utils}/prismaWrapper.test.ts +1 -1
  222. package/vitest.config.ts +11 -3
  223. package/vitest.unit.config.ts +10 -10
  224. package/tests/routers/worksheet.test.ts +0 -194
  225. package/tests/startup.test.ts +0 -5
  226. package/tests/worksheet.test.ts +0 -181
  227. /package/tests/{routers → integration/routers}/agenda.test.ts +0 -0
  228. /package/tests/{routers → integration/routers}/announcement.test.ts +0 -0
  229. /package/tests/{routers → integration/routers}/assignment.test.ts +0 -0
  230. /package/tests/{routers → integration/routers}/comment.test.ts +0 -0
  231. /package/tests/{routers → integration/routers}/folder.test.ts +0 -0
  232. /package/tests/{routers → integration/routers}/labChat.test.ts +0 -0
  233. /package/tests/{routers → integration/routers}/marketing.test.ts +0 -0
  234. /package/tests/{routers → integration/routers}/notification.test.ts +0 -0
  235. /package/tests/{routers → integration/routers}/search.test.ts +0 -0
  236. /package/tests/{routers → integration/routers}/section.test.ts +0 -0
  237. /package/tests/{server → unit/middleware}/rateLimit.test.ts +0 -0
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="181d07f5-4bad-5e9c-a71e-3c354adb855b")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="013aae33-465f-5a6c-8d45-87d01c0d532f")}catch(e){}}();
3
3
  import { TRPCError } from "@trpc/server";
4
4
  import { prisma } from "../lib/prisma.js";
5
5
  import { chatChannel, pusher } from "../lib/pusher.js";
@@ -56,6 +56,18 @@ export async function confirmMessageUpload(userId, input) {
56
56
  message: "Not a member of this conversation",
57
57
  });
58
58
  }
59
+ if (input.uploadSuccess) {
60
+ await prisma.$transaction(async (tx) => {
61
+ await tx.file.update({
62
+ where: { id: input.fileId },
63
+ data: { uploadStatus: "FAILED", uploadError: input.errorMessage },
64
+ });
65
+ });
66
+ throw new TRPCError({
67
+ code: "INTERNAL_SERVER_ERROR",
68
+ message: "Failed to confirm upload",
69
+ });
70
+ }
59
71
  const file = await prisma.file.findFirst({
60
72
  where: {
61
73
  id: input.fileId,
@@ -106,6 +118,7 @@ export async function listMessages(userId, input) {
106
118
  conversationId: message.conversationId,
107
119
  createdAt: message.createdAt,
108
120
  sender: message.sender,
121
+ status: message.status,
109
122
  attachments: message.attachments.map((a) => ({
110
123
  id: a.id,
111
124
  name: a.name,
@@ -194,6 +207,7 @@ export async function sendMessage(userId, input) {
194
207
  content: result.content,
195
208
  senderId: result.senderId,
196
209
  conversationId: result.conversationId,
210
+ status: result.status,
197
211
  createdAt: result.createdAt,
198
212
  sender: result.sender,
199
213
  attachments: result.attachments ?? [],
@@ -213,6 +227,7 @@ export async function sendMessage(userId, input) {
213
227
  content: result.content,
214
228
  senderId: result.senderId,
215
229
  conversationId: result.conversationId,
230
+ status: result.status,
216
231
  createdAt: result.createdAt,
217
232
  sender: result.sender,
218
233
  attachments: result.attachments ?? [],
@@ -291,6 +306,7 @@ export async function updateMessage(userId, input) {
291
306
  content: updatedMessage.content,
292
307
  senderId: updatedMessage.senderId,
293
308
  conversationId: updatedMessage.conversationId,
309
+ status: updatedMessage.status,
294
310
  createdAt: updatedMessage.createdAt,
295
311
  sender: updatedMessage.sender,
296
312
  attachments: updatedMessage.attachments ?? [],
@@ -310,6 +326,7 @@ export async function updateMessage(userId, input) {
310
326
  content: updatedMessage.content,
311
327
  senderId: updatedMessage.senderId,
312
328
  conversationId: updatedMessage.conversationId,
329
+ status: updatedMessage.status,
313
330
  createdAt: updatedMessage.createdAt,
314
331
  sender: updatedMessage.sender,
315
332
  mentionedUserIds,
@@ -445,6 +462,7 @@ export async function markSuggestionCreated(userId, input) {
445
462
  content: updated.content,
446
463
  senderId: updated.senderId,
447
464
  conversationId: updated.conversationId,
465
+ status: updated.status,
448
466
  createdAt: updated.createdAt,
449
467
  sender: updated.sender,
450
468
  attachments: updated.attachments ?? [],
@@ -544,4 +562,4 @@ export async function getUnreadCount(userId, conversationId) {
544
562
  return { unreadCount, unreadMentionCount };
545
563
  }
546
564
  //# sourceMappingURL=message.js.map
547
- //# debugId=181d07f5-4bad-5e9c-a71e-3c354adb855b
565
+ //# debugId=013aae33-465f-5a6c-8d45-87d01c0d532f
@@ -1 +1 @@
1
- {"version":3,"file":"message.js","sources":["services/message.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Message service – send, update, delete messages; mark as read; track mentions.\n * Broadcasts real-time updates via Pusher.\n */\nimport type { Prisma } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { chatChannel, pusher } from \"../lib/pusher.js\";\nimport {\n confirmDirectUpload,\n createDirectUploadFiles,\n} from \"../lib/fileUpload.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n findConversationMembership,\n findMessages,\n findMentionedMemberships,\n findMessageById,\n findMessageByIdMinimal,\n countUnreadMessages,\n countUnreadMentions,\n} from \"../models/message.js\";\n\nexport function ensureMessageHasContentOrAttachments(\n content: string,\n fileIds: string[] = []\n) {\n if (!content.trim() && fileIds.length === 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Message must include content or attachments\",\n });\n }\n}\n\nexport async function claimMessageAttachments(\n tx: Prisma.TransactionClient,\n userId: string,\n conversationId: string,\n messageId: string,\n fileIds: string[] = []\n) {\n const uniqueFileIds = [...new Set(fileIds)];\n\n if (uniqueFileIds.length === 0) {\n return;\n }\n\n const { count } = await tx.file.updateMany({\n where: {\n id: { in: uniqueFileIds },\n userId,\n conversationId,\n uploadStatus: \"COMPLETED\",\n messageId: null,\n },\n data: {\n messageId,\n },\n });\n\n if (count !== uniqueFileIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some attachments are missing, unfinished, or unavailable\",\n });\n }\n}\n\nexport async function getMessageUploadUrls(\n userId: string,\n input: {\n conversationId: string;\n files: { name: string; type: string; size: number }[];\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n return createDirectUploadFiles(\n input.files,\n userId,\n undefined,\n undefined,\n undefined,\n undefined,\n input.conversationId\n );\n}\n\nexport async function confirmMessageUpload(\n userId: string,\n input: {\n conversationId: string;\n fileId: string;\n uploadSuccess: boolean;\n errorMessage?: string;\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const file = await prisma.file.findFirst({\n where: {\n id: input.fileId,\n userId,\n conversationId: input.conversationId,\n messageId: null,\n },\n select: { id: true },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found or you do not have permission\",\n });\n }\n\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n\n return {\n success: true,\n message: input.uploadSuccess\n ? \"Upload confirmed successfully\"\n : \"Upload failed\",\n };\n}\n\n/** List messages in a conversation with cursor-based pagination. */\nexport async function listMessages(\n userId: string,\n input: {\n conversationId: string;\n cursor?: string;\n limit: number;\n }\n) {\n const { conversationId, cursor, limit } = input;\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const messages = await findMessages(conversationId, {\n cursor: cursor ? new Date(cursor) : undefined,\n limit,\n });\n\n let nextCursor: string | undefined;\n if (messages.length > limit) {\n const nextItem = messages.pop();\n nextCursor = nextItem!.createdAt.toISOString();\n }\n\n return {\n messages: messages.reverse().map((message) => ({\n id: message.id,\n content: message.content,\n senderId: message.senderId,\n conversationId: message.conversationId,\n createdAt: message.createdAt,\n sender: message.sender,\n attachments: message.attachments.map((a) => ({\n id: a.id,\n name: a.name,\n type: a.type,\n })),\n meta: message.meta as Record<string, unknown>,\n mentions: message.mentions.map((m) => ({ user: m.user })),\n mentionsMe: message.mentions.some((m) => m.userId === userId),\n })),\n nextCursor,\n };\n}\n\n/** Send a message. Validates membership, creates message, broadcasts via Pusher. */\nexport async function sendMessage(\n userId: string,\n input: {\n conversationId: string;\n content: string;\n mentionedUserIds?: string[];\n fileIds?: string[];\n }\n) {\n const {\n conversationId,\n content,\n mentionedUserIds = [],\n fileIds = [],\n } = input;\n\n ensureMessageHasContentOrAttachments(content, fileIds);\n const normalizedContent = content.trim();\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const result = await prisma.$transaction(async (tx) => {\n const message = await tx.message.create({\n data: {\n content: normalizedContent,\n senderId: userId,\n conversationId,\n },\n });\n\n await claimMessageAttachments(\n tx,\n userId,\n conversationId,\n message.id,\n fileIds\n );\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId: message.id,\n userId: mentionedUserId,\n })),\n });\n }\n\n await tx.conversationMember.updateMany({\n where: {\n conversationId,\n hiddenAt: { not: null },\n },\n data: { hiddenAt: null },\n });\n\n await tx.conversation.update({\n where: { id: conversationId },\n data: { updatedAt: new Date() },\n });\n\n return tx.message.findUniqueOrThrow({\n where: { id: message.id },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: { id: true, name: true, type: true },\n },\n },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"new-message\",\n {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n meta: (result.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message via Pusher\", {\n error,\n conversationId,\n messageId: result.id,\n });\n }\n\n return {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n };\n}\n\nexport async function updateMessage(\n userId: string,\n input: {\n messageId: string;\n content: string;\n mentionedUserIds?: string[];\n }\n) {\n const { messageId, content, mentionedUserIds = [] } = input;\n\n const existingMessage = await findMessageById(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n existingMessage.conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const updatedMessage = await prisma.$transaction(async (tx) => {\n const message = await tx.message.update({\n where: { id: messageId },\n data: { content },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: { id: true, name: true, type: true },\n },\n },\n });\n\n await tx.mention.deleteMany({\n where: { messageId },\n });\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId,\n userId: mentionedUserId,\n })),\n });\n }\n\n return message;\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-updated\",\n {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n attachments: updatedMessage.attachments ?? [],\n meta: (updatedMessage.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message update via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n mentionedUserIds,\n };\n}\n\nexport async function deleteMessage(userId: string, messageId: string) {\n const existingMessage = await findMessageByIdMinimal(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.$transaction(async (tx) => {\n await tx.mention.deleteMany({\n where: { messageId },\n });\n await tx.message.delete({\n where: { id: messageId },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-deleted\",\n {\n messageId,\n conversationId: existingMessage.conversationId,\n senderId: existingMessage.senderId,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message deletion via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return { success: true, messageId };\n}\n\nconst CREATED_INDICES_KEY: Record<\"assignment\" | \"worksheet\" | \"section\", string> = {\n assignment: \"assignments\",\n worksheet: \"worksheets\",\n section: \"sections\",\n};\n\nconst MARK_SUGGESTION_MAX_RETRIES = 5;\n\n/** Mark an AI-suggested item as created. Stores in message meta for fast reads. */\nexport async function markSuggestionCreated(\n userId: string,\n input: {\n messageId: string;\n type: \"assignment\" | \"worksheet\" | \"section\";\n index: number;\n }\n) {\n const { messageId, type, index } = input;\n\n const existingMessage = await findMessageByIdMinimal(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const conversationId = existingMessage.conversationId;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < MARK_SUGGESTION_MAX_RETRIES; attempt++) {\n const msg = await prisma.message.findUnique({\n where: { id: messageId },\n select: { meta: true },\n });\n if (!msg) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n const currentMeta = (msg.meta as Record<string, unknown>) ?? {};\n const createdIndices = (currentMeta.createdIndices as Record<string, number[]>) ?? {};\n const typeIndices = createdIndices[CREATED_INDICES_KEY[type]] ?? [];\n if (typeIndices.includes(index)) return { success: true };\n\n const newTypeIndices = [...typeIndices, index].sort((a, b) => a - b);\n const newMeta = {\n ...currentMeta,\n createdIndices: {\n ...createdIndices,\n [CREATED_INDICES_KEY[type]]: newTypeIndices,\n },\n };\n\n const newMetaJson = JSON.stringify(newMeta);\n const currentMetaJson = msg.meta === null ? null : JSON.stringify(msg.meta);\n\n const updatedCount =\n currentMetaJson === null\n ? await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta IS NULL\n `\n : await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta = ${currentMetaJson}::jsonb\n `;\n\n if (Number(updatedCount) > 0) {\n const updated = await prisma.message.findUnique({\n where: { id: messageId },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: { displayName: true, profilePicture: true },\n },\n },\n },\n attachments: { select: { id: true, name: true, type: true } },\n mentions: { select: { userId: true } },\n },\n });\n\n if (updated) {\n const mentionedUserIds = updated.mentions.map((m) => m.userId);\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"message-updated\",\n {\n id: updated.id,\n content: updated.content,\n senderId: updated.senderId,\n conversationId: updated.conversationId,\n createdAt: updated.createdAt,\n sender: updated.sender,\n attachments: updated.attachments ?? [],\n meta: newMeta,\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast suggestion status via Pusher\", {\n error,\n messageId,\n });\n }\n }\n return { success: true };\n }\n\n lastError = new Error(\"Concurrent update conflict\");\n }\n\n logger.error(\"markSuggestionCreated failed after retries\", {\n messageId,\n type,\n index,\n error: lastError,\n });\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Failed to update suggestion status after retries due to concurrent updates\",\n });\n}\n\nexport async function markAsRead(userId: string, conversationId: string) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"conversation-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast conversation-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function markMentionsAsRead(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedMentionAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"mentions-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast mentions-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function getUnreadCount(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const unreadCount = await countUnreadMessages(\n conversationId,\n userId,\n membership.lastViewedAt ?? undefined\n );\n\n const mentionCutoffTime =\n membership.lastViewedMentionAt && membership.lastViewedAt\n ? membership.lastViewedMentionAt > membership.lastViewedAt\n ? membership.lastViewedMentionAt\n : membership.lastViewedAt\n : membership.lastViewedMentionAt ?? membership.lastViewedAt;\n\n const unreadMentionCount = await countUnreadMentions(\n conversationId,\n userId,\n mentionCutoffTime ?? undefined\n );\n\n return { unreadCount, unreadMentionCount };\n}\n"],"names":[],"mappings":"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","debug_id":"181d07f5-4bad-5e9c-a71e-3c354adb855b"}
1
+ {"version":3,"file":"message.js","sources":["services/message.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Message service – send, update, delete messages; mark as read; track mentions.\n * Broadcasts real-time updates via Pusher.\n */\nimport type { Prisma } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { chatChannel, pusher } from \"../lib/pusher.js\";\nimport {\n confirmDirectUpload,\n createDirectUploadFiles,\n} from \"../lib/fileUpload.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n findConversationMembership,\n findMessages,\n findMentionedMemberships,\n findMessageById,\n findMessageByIdMinimal,\n countUnreadMessages,\n countUnreadMentions,\n} from \"../models/message.js\";\n\nexport function ensureMessageHasContentOrAttachments(\n content: string,\n fileIds: string[] = []\n) {\n if (!content.trim() && fileIds.length === 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Message must include content or attachments\",\n });\n }\n}\n\nexport async function claimMessageAttachments(\n tx: Prisma.TransactionClient,\n userId: string,\n conversationId: string,\n messageId: string,\n fileIds: string[] = []\n) {\n const uniqueFileIds = [...new Set(fileIds)];\n\n if (uniqueFileIds.length === 0) {\n return;\n }\n\n const { count } = await tx.file.updateMany({\n where: {\n id: { in: uniqueFileIds },\n userId,\n conversationId,\n uploadStatus: \"COMPLETED\",\n messageId: null,\n },\n data: {\n messageId,\n },\n });\n\n if (count !== uniqueFileIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some attachments are missing, unfinished, or unavailable\",\n });\n }\n}\n\nexport async function getMessageUploadUrls(\n userId: string,\n input: {\n conversationId: string;\n files: { name: string; type: string; size: number }[];\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n return createDirectUploadFiles(\n input.files,\n userId,\n undefined,\n undefined,\n undefined,\n undefined,\n input.conversationId\n );\n}\n\nexport async function confirmMessageUpload(\n userId: string,\n input: {\n conversationId: string;\n fileId: string;\n uploadSuccess: boolean;\n errorMessage?: string;\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (input.uploadSuccess) {\n await prisma.$transaction(async (tx) => {\n await tx.file.update({\n where: { id: input.fileId },\n data: { uploadStatus: \"FAILED\", uploadError: input.errorMessage },\n });\n });\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to confirm upload\",\n });\n }\n\n const file = await prisma.file.findFirst({\n where: {\n id: input.fileId,\n userId,\n conversationId: input.conversationId,\n messageId: null,\n },\n select: { id: true },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found or you do not have permission\",\n });\n }\n\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n\n return {\n success: true,\n message: input.uploadSuccess\n ? \"Upload confirmed successfully\"\n : \"Upload failed\",\n };\n}\n\n/** List messages in a conversation with cursor-based pagination. */\nexport async function listMessages(\n userId: string,\n input: {\n conversationId: string;\n cursor?: string;\n limit: number;\n }\n) {\n const { conversationId, cursor, limit } = input;\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const messages = await findMessages(conversationId, {\n cursor: cursor ? new Date(cursor) : undefined,\n limit,\n });\n\n let nextCursor: string | undefined;\n if (messages.length > limit) {\n const nextItem = messages.pop();\n nextCursor = nextItem!.createdAt.toISOString();\n }\n\n return {\n messages: messages.reverse().map((message) => ({\n id: message.id,\n content: message.content,\n senderId: message.senderId,\n conversationId: message.conversationId,\n createdAt: message.createdAt,\n sender: message.sender,\n status: message.status,\n attachments: message.attachments.map((a) => ({\n id: a.id,\n name: a.name,\n type: a.type,\n })),\n meta: message.meta as Record<string, unknown>,\n mentions: message.mentions.map((m) => ({ user: m.user })),\n mentionsMe: message.mentions.some((m) => m.userId === userId),\n })),\n nextCursor,\n };\n}\n\n/** Send a message. Validates membership, creates message, broadcasts via Pusher. */\nexport async function sendMessage(\n userId: string,\n input: {\n conversationId: string;\n content: string;\n mentionedUserIds?: string[];\n fileIds?: string[];\n }\n) {\n const {\n conversationId,\n content,\n mentionedUserIds = [],\n fileIds = [],\n } = input;\n\n ensureMessageHasContentOrAttachments(content, fileIds);\n const normalizedContent = content.trim();\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const result = await prisma.$transaction(async (tx) => {\n const message = await tx.message.create({\n data: {\n content: normalizedContent,\n senderId: userId,\n conversationId,\n },\n });\n\n await claimMessageAttachments(\n tx,\n userId,\n conversationId,\n message.id,\n fileIds\n );\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId: message.id,\n userId: mentionedUserId,\n })),\n });\n }\n\n await tx.conversationMember.updateMany({\n where: {\n conversationId,\n hiddenAt: { not: null },\n },\n data: { hiddenAt: null },\n });\n\n await tx.conversation.update({\n where: { id: conversationId },\n data: { updatedAt: new Date() },\n });\n\n return tx.message.findUniqueOrThrow({\n where: { id: message.id },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: { id: true, name: true, type: true },\n },\n },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"new-message\",\n {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n meta: (result.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message via Pusher\", {\n error,\n conversationId,\n messageId: result.id,\n });\n }\n\n return {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n };\n}\n\nexport async function updateMessage(\n userId: string,\n input: {\n messageId: string;\n content: string;\n mentionedUserIds?: string[];\n }\n) {\n const { messageId, content, mentionedUserIds = [] } = input;\n\n const existingMessage = await findMessageById(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n existingMessage.conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const updatedMessage = await prisma.$transaction(async (tx) => {\n const message = await tx.message.update({\n where: { id: messageId },\n data: { content },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: { id: true, name: true, type: true },\n },\n },\n });\n\n await tx.mention.deleteMany({\n where: { messageId },\n });\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId,\n userId: mentionedUserId,\n })),\n });\n }\n\n return message;\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-updated\",\n {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n status: updatedMessage.status,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n attachments: updatedMessage.attachments ?? [],\n meta: (updatedMessage.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message update via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n status: updatedMessage.status,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n mentionedUserIds,\n };\n}\n\nexport async function deleteMessage(userId: string, messageId: string) {\n const existingMessage = await findMessageByIdMinimal(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.$transaction(async (tx) => {\n await tx.mention.deleteMany({\n where: { messageId },\n });\n await tx.message.delete({\n where: { id: messageId },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-deleted\",\n {\n messageId,\n conversationId: existingMessage.conversationId,\n senderId: existingMessage.senderId,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message deletion via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return { success: true, messageId };\n}\n\nconst CREATED_INDICES_KEY: Record<\"assignment\" | \"worksheet\" | \"section\", string> = {\n assignment: \"assignments\",\n worksheet: \"worksheets\",\n section: \"sections\",\n};\n\nconst MARK_SUGGESTION_MAX_RETRIES = 5;\n\n/** Mark an AI-suggested item as created. Stores in message meta for fast reads. */\nexport async function markSuggestionCreated(\n userId: string,\n input: {\n messageId: string;\n type: \"assignment\" | \"worksheet\" | \"section\";\n index: number;\n }\n) {\n const { messageId, type, index } = input;\n\n const existingMessage = await findMessageByIdMinimal(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const conversationId = existingMessage.conversationId;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < MARK_SUGGESTION_MAX_RETRIES; attempt++) {\n const msg = await prisma.message.findUnique({\n where: { id: messageId },\n select: { meta: true },\n });\n if (!msg) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n const currentMeta = (msg.meta as Record<string, unknown>) ?? {};\n const createdIndices = (currentMeta.createdIndices as Record<string, number[]>) ?? {};\n const typeIndices = createdIndices[CREATED_INDICES_KEY[type]] ?? [];\n if (typeIndices.includes(index)) return { success: true };\n\n const newTypeIndices = [...typeIndices, index].sort((a, b) => a - b);\n const newMeta = {\n ...currentMeta,\n createdIndices: {\n ...createdIndices,\n [CREATED_INDICES_KEY[type]]: newTypeIndices,\n },\n };\n\n const newMetaJson = JSON.stringify(newMeta);\n const currentMetaJson = msg.meta === null ? null : JSON.stringify(msg.meta);\n\n const updatedCount =\n currentMetaJson === null\n ? await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta IS NULL\n `\n : await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta = ${currentMetaJson}::jsonb\n `;\n\n if (Number(updatedCount) > 0) {\n const updated = await prisma.message.findUnique({\n where: { id: messageId },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: { displayName: true, profilePicture: true },\n },\n },\n },\n attachments: { select: { id: true, name: true, type: true } },\n mentions: { select: { userId: true } },\n },\n });\n\n if (updated) {\n const mentionedUserIds = updated.mentions.map((m) => m.userId);\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"message-updated\",\n {\n id: updated.id,\n content: updated.content,\n senderId: updated.senderId,\n conversationId: updated.conversationId,\n status: updated.status,\n createdAt: updated.createdAt,\n sender: updated.sender,\n attachments: updated.attachments ?? [],\n meta: newMeta,\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast suggestion status via Pusher\", {\n error,\n messageId,\n });\n }\n }\n return { success: true };\n }\n\n lastError = new Error(\"Concurrent update conflict\");\n }\n\n logger.error(\"markSuggestionCreated failed after retries\", {\n messageId,\n type,\n index,\n error: lastError,\n });\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Failed to update suggestion status after retries due to concurrent updates\",\n });\n}\n\nexport async function markAsRead(userId: string, conversationId: string) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"conversation-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast conversation-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function markMentionsAsRead(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedMentionAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"mentions-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast mentions-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function getUnreadCount(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const unreadCount = await countUnreadMessages(\n conversationId,\n userId,\n membership.lastViewedAt ?? undefined\n );\n\n const mentionCutoffTime =\n membership.lastViewedMentionAt && membership.lastViewedAt\n ? membership.lastViewedMentionAt > membership.lastViewedAt\n ? membership.lastViewedMentionAt\n : membership.lastViewedAt\n : membership.lastViewedMentionAt ?? membership.lastViewedAt;\n\n const unreadMentionCount = await countUnreadMentions(\n conversationId,\n userId,\n mentionCutoffTime ?? undefined\n );\n\n return { unreadCount, unreadMentionCount };\n}\n"],"names":[],"mappings":"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","debug_id":"013aae33-465f-5a6c-8d45-87d01c0d532f"}
@@ -8,6 +8,7 @@ export declare function postToNewtonChat(userId: string, newtonChatId: string, c
8
8
  content: string;
9
9
  senderId: string;
10
10
  conversationId: string;
11
+ status: import(".prisma/client").$Enums.JobStatus | null;
11
12
  createdAt: Date;
12
13
  sender: {
13
14
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"newtonChat.d.ts","sourceRoot":"/","sources":["services/newtonChat.ts"],"names":[],"mappings":"AAqBA,8EAA8E;AAC9E,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM;;;GA6FhB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,gBAAgB,GAAE,MAAM,EAAO,EAC/B,OAAO,GAAE,MAAM,EAAO;;;;;;;;;;;;;;;;;;;;GA+HvB"}
1
+ {"version":3,"file":"newtonChat.d.ts","sourceRoot":"/","sources":["services/newtonChat.ts"],"names":[],"mappings":"AAuBA,8EAA8E;AAC9E,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM;;;GAqGhB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,gBAAgB,GAAE,MAAM,EAAO,EAC/B,OAAO,GAAE,MAAM,EAAO;;;;;;;;;;;;;;;;;;;;;GA0IvB"}
@@ -3,12 +3,13 @@
3
3
  * tutor session, post messages, trigger AI responses. Broadcasts via Pusher.
4
4
  */
5
5
 
6
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4008c70c-b11c-5552-91e3-8c82551e82a2")}catch(e){}}();
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="17139b82-2307-50e5-9512-3d4f94276d26")}catch(e){}}();
7
7
  import { TRPCError } from "@trpc/server";
8
8
  import { logger } from "../utils/logger.js";
9
9
  import { chatChannel, pusher } from "../lib/pusher.js";
10
10
  import { isAIUser } from "../utils/aiUser.js";
11
- import { generateAndSendNewtonIntroduction, generateAndSendNewtonResponse } from "../pipelines/aiNewtonChat.js";
11
+ import { createPendingAIMessage } from "../utils/inference.js";
12
+ import { enqueueRunJob, QUEUE_NAMES } from "../workers/queues.js";
12
13
  import { claimMessageAttachments, ensureMessageHasContentOrAttachments, } from "./message.js";
13
14
  import { findClassWithStudent, findSubmissionByAssignmentAndStudent, findNewtonChatByIdAndMember, findConversationMembersByUserIds, } from "../models/newtonChat.js";
14
15
  import { prisma } from "../lib/prisma.js";
@@ -28,7 +29,7 @@ export async function getTutorConversation(userId, assignmentId, classId) {
28
29
  message: "Submission not found. Please create a submission first.",
29
30
  });
30
31
  }
31
- const result = await prisma.$transaction(async (tx) => {
32
+ const { chat, isNew } = await prisma.$transaction(async (tx) => {
32
33
  const existingNewtonChat = await tx.newtonChat.findFirst({
33
34
  where: { submissionId: submission.id },
34
35
  include: {
@@ -43,7 +44,7 @@ export async function getTutorConversation(userId, assignmentId, classId) {
43
44
  orderBy: { createdAt: "desc" },
44
45
  });
45
46
  if (existingNewtonChat && existingNewtonChat.conversation.members.length > 0) {
46
- return existingNewtonChat;
47
+ return { chat: existingNewtonChat, isNew: false };
47
48
  }
48
49
  if (existingNewtonChat) {
49
50
  await tx.conversationMember.create({
@@ -53,7 +54,7 @@ export async function getTutorConversation(userId, assignmentId, classId) {
53
54
  role: "MEMBER",
54
55
  },
55
56
  });
56
- return existingNewtonChat;
57
+ return { chat: existingNewtonChat, isNew: false };
57
58
  }
58
59
  const conversation = await tx.conversation.create({
59
60
  data: {
@@ -76,17 +77,26 @@ export async function getTutorConversation(userId, assignmentId, classId) {
76
77
  title: "Session with Newton Tutor",
77
78
  },
78
79
  });
79
- generateAndSendNewtonIntroduction(newtonChat.id, newtonChat.conversationId, submission.id).catch((error) => {
80
- logger.error("Failed to generate AI introduction:", {
80
+ return { chat: newtonChat, isNew: true };
81
+ });
82
+ if (isNew) {
83
+ // Create the AI introduction placeholder and enqueue OUTSIDE the
84
+ // transaction — a tx rollback would otherwise orphan a job pointing at
85
+ // a non-existent NewtonChat / Message row.
86
+ try {
87
+ const aiPlaceholder = await createPendingAIMessage(chat.conversationId);
88
+ await enqueueRunJob(QUEUE_NAMES.AI_NEWTON_CHAT, { conversationId: chat.conversationId, aiMessageId: aiPlaceholder.id }, { userId });
89
+ }
90
+ catch (error) {
91
+ logger.error("Failed to enqueue Newton introduction", {
81
92
  error,
82
- newtonChatId: newtonChat.id,
93
+ newtonChatId: chat.id,
83
94
  });
84
- });
85
- return newtonChat;
86
- });
95
+ }
96
+ }
87
97
  return {
88
- conversationId: result.conversationId,
89
- newtonChatId: result.id,
98
+ conversationId: chat.conversationId,
99
+ newtonChatId: chat.id,
90
100
  };
91
101
  }
92
102
  export async function postToNewtonChat(userId, newtonChatId, content, mentionedUserIds = [], fileIds = []) {
@@ -162,6 +172,7 @@ export async function postToNewtonChat(userId, newtonChatId, content, mentionedU
162
172
  content: result.content,
163
173
  senderId: result.senderId,
164
174
  conversationId: result.conversationId,
175
+ status: result.status,
165
176
  createdAt: result.createdAt,
166
177
  sender: result.sender,
167
178
  attachments: result.attachments ?? [],
@@ -173,15 +184,27 @@ export async function postToNewtonChat(userId, newtonChatId, content, mentionedU
173
184
  console.error("Failed to broadcast newton chat message:", error);
174
185
  }
175
186
  if (!isAIUser(userId)) {
176
- generateAndSendNewtonResponse(newtonChatId, normalizedContent, newtonChat.conversationId, newtonChat.submission).catch((error) => {
177
- logger.error("Failed to generate AI response:", { error });
178
- });
187
+ // Create the AI placeholder post-tx (so a rollback can't broadcast a
188
+ // ghost bubble); the pipeline fills it in.
189
+ try {
190
+ const aiPlaceholder = await createPendingAIMessage(newtonChat.conversationId);
191
+ await enqueueRunJob(QUEUE_NAMES.AI_NEWTON_CHAT, { conversationId: newtonChat.conversationId, aiMessageId: aiPlaceholder.id }, { userId });
192
+ }
193
+ catch (error) {
194
+ logger.error("Failed to enqueue Newton response", {
195
+ error,
196
+ newtonChatId,
197
+ studentMessageId: result.id,
198
+ conversationId: newtonChat.conversationId,
199
+ });
200
+ }
179
201
  }
180
202
  return {
181
203
  id: result.id,
182
204
  content: result.content,
183
205
  senderId: result.senderId,
184
206
  conversationId: result.conversationId,
207
+ status: result.status,
185
208
  createdAt: result.createdAt,
186
209
  sender: result.sender,
187
210
  attachments: result.attachments ?? [],
@@ -189,4 +212,4 @@ export async function postToNewtonChat(userId, newtonChatId, content, mentionedU
189
212
  };
190
213
  }
191
214
  //# sourceMappingURL=newtonChat.js.map
192
- //# debugId=4008c70c-b11c-5552-91e3-8c82551e82a2
215
+ //# debugId=17139b82-2307-50e5-9512-3d4f94276d26
@@ -1 +1 @@
1
- {"version":3,"file":"newtonChat.js","sources":["services/newtonChat.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Newton chat service – AI tutor conversations for assignments. Get or create\n * tutor session, post messages, trigger AI responses. Broadcasts via Pusher.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { logger } from \"../utils/logger.js\";\nimport { chatChannel, pusher } from \"../lib/pusher.js\";\nimport { isAIUser } from \"../utils/aiUser.js\";\nimport { generateAndSendNewtonIntroduction, generateAndSendNewtonResponse } from \"../pipelines/aiNewtonChat.js\";\nimport {\n claimMessageAttachments,\n ensureMessageHasContentOrAttachments,\n} from \"./message.js\";\nimport {\n findClassWithStudent,\n findSubmissionByAssignmentAndStudent,\n findNewtonChatByIdAndMember,\n findConversationMembersByUserIds,\n} from \"../models/newtonChat.js\";\nimport { prisma } from \"../lib/prisma.js\";\n\n/** Get or create Newton tutor conversation for a submission. Student-only. */\nexport async function getTutorConversation(\n userId: string,\n assignmentId: string,\n classId: string\n) {\n const classMembership = await findClassWithStudent(classId, userId);\n if (!classMembership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a student in this class\",\n });\n }\n\n const submission = await findSubmissionByAssignmentAndStudent(\n assignmentId,\n userId\n );\n if (!submission) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Submission not found. Please create a submission first.\",\n });\n }\n\n const result = await prisma.$transaction(async (tx) => {\n const existingNewtonChat = await tx.newtonChat.findFirst({\n where: { submissionId: submission.id },\n include: {\n conversation: {\n include: {\n members: {\n where: { userId },\n },\n },\n },\n },\n orderBy: { createdAt: \"desc\" },\n });\n\n if (existingNewtonChat && existingNewtonChat.conversation.members.length > 0) {\n return existingNewtonChat;\n }\n\n if (existingNewtonChat) {\n await tx.conversationMember.create({\n data: {\n userId,\n conversationId: existingNewtonChat.conversationId,\n role: \"MEMBER\",\n },\n });\n return existingNewtonChat;\n }\n\n const conversation = await tx.conversation.create({\n data: {\n type: \"DM\",\n name: \"Session with Newton Tutor\",\n displayInChat: false,\n },\n });\n\n await tx.conversationMember.create({\n data: {\n userId,\n conversationId: conversation.id,\n role: \"MEMBER\",\n },\n });\n\n const newtonChat = await tx.newtonChat.create({\n data: {\n submissionId: submission.id,\n conversationId: conversation.id,\n title: \"Session with Newton Tutor\",\n },\n });\n\n generateAndSendNewtonIntroduction(\n newtonChat.id,\n newtonChat.conversationId,\n submission.id\n ).catch((error) => {\n logger.error(\"Failed to generate AI introduction:\", {\n error,\n newtonChatId: newtonChat.id,\n });\n });\n\n return newtonChat;\n });\n\n return {\n conversationId: result.conversationId,\n newtonChatId: result.id,\n };\n}\n\nexport async function postToNewtonChat(\n userId: string,\n newtonChatId: string,\n content: string,\n mentionedUserIds: string[] = [],\n fileIds: string[] = []\n) {\n const newtonChat = await findNewtonChatByIdAndMember(newtonChatId, userId);\n if (!newtonChat) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Newton chat not found or access denied\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findConversationMembersByUserIds(\n newtonChat.conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n ensureMessageHasContentOrAttachments(content, fileIds);\n const normalizedContent = content.trim();\n\n if (!normalizedContent) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Newton chat messages must include text content\",\n });\n }\n\n const result = await prisma.$transaction(async (tx) => {\n const message = await tx.message.create({\n data: {\n content: normalizedContent,\n senderId: userId,\n conversationId: newtonChat.conversationId,\n },\n });\n\n await claimMessageAttachments(\n tx,\n userId,\n newtonChat.conversationId,\n message.id,\n fileIds\n );\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId: message.id,\n userId: mentionedUserId,\n })),\n });\n }\n\n await tx.newtonChat.update({\n where: { id: newtonChatId },\n data: { updatedAt: new Date() },\n });\n\n return tx.message.findUniqueOrThrow({\n where: { id: message.id },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: { id: true, name: true, type: true },\n },\n },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(newtonChat.conversationId),\n \"new-message\",\n {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n meta: {},\n }\n );\n } catch (error) {\n console.error(\"Failed to broadcast newton chat message:\", error);\n }\n\n if (!isAIUser(userId)) {\n generateAndSendNewtonResponse(\n newtonChatId,\n normalizedContent,\n newtonChat.conversationId,\n newtonChat.submission\n ).catch((error) => {\n logger.error(\"Failed to generate AI response:\", { error });\n });\n }\n\n return {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n };\n}\n"],"names":[],"mappings":"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","debug_id":"4008c70c-b11c-5552-91e3-8c82551e82a2"}
1
+ {"version":3,"file":"newtonChat.js","sources":["services/newtonChat.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Newton chat service – AI tutor conversations for assignments. Get or create\n * tutor session, post messages, trigger AI responses. Broadcasts via Pusher.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { logger } from \"../utils/logger.js\";\nimport { chatChannel, pusher } from \"../lib/pusher.js\";\nimport { isAIUser } from \"../utils/aiUser.js\";\nimport { createPendingAIMessage } from \"../utils/inference.js\";\nimport { enqueueRunJob, QUEUE_NAMES } from \"../workers/queues.js\";\nimport {\n claimMessageAttachments,\n ensureMessageHasContentOrAttachments,\n} from \"./message.js\";\nimport {\n findClassWithStudent,\n findSubmissionByAssignmentAndStudent,\n findNewtonChatByIdAndMember,\n findConversationMembersByUserIds,\n} from \"../models/newtonChat.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { RunJobType } from \"@prisma/client\";\n\n/** Get or create Newton tutor conversation for a submission. Student-only. */\nexport async function getTutorConversation(\n userId: string,\n assignmentId: string,\n classId: string\n) {\n const classMembership = await findClassWithStudent(classId, userId);\n if (!classMembership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a student in this class\",\n });\n }\n\n const submission = await findSubmissionByAssignmentAndStudent(\n assignmentId,\n userId\n );\n if (!submission) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Submission not found. Please create a submission first.\",\n });\n }\n\n const { chat, isNew } = await prisma.$transaction(async (tx) => {\n const existingNewtonChat = await tx.newtonChat.findFirst({\n where: { submissionId: submission.id },\n include: {\n conversation: {\n include: {\n members: {\n where: { userId },\n },\n },\n },\n },\n orderBy: { createdAt: \"desc\" },\n });\n\n if (existingNewtonChat && existingNewtonChat.conversation.members.length > 0) {\n return { chat: existingNewtonChat, isNew: false };\n }\n\n if (existingNewtonChat) {\n await tx.conversationMember.create({\n data: {\n userId,\n conversationId: existingNewtonChat.conversationId,\n role: \"MEMBER\",\n },\n });\n return { chat: existingNewtonChat, isNew: false };\n }\n\n const conversation = await tx.conversation.create({\n data: {\n type: \"DM\",\n name: \"Session with Newton Tutor\",\n displayInChat: false,\n },\n });\n\n await tx.conversationMember.create({\n data: {\n userId,\n conversationId: conversation.id,\n role: \"MEMBER\",\n },\n });\n\n const newtonChat = await tx.newtonChat.create({\n data: {\n submissionId: submission.id,\n conversationId: conversation.id,\n title: \"Session with Newton Tutor\",\n },\n });\n\n return { chat: newtonChat, isNew: true };\n });\n\n if (isNew) {\n // Create the AI introduction placeholder and enqueue OUTSIDE the\n // transaction — a tx rollback would otherwise orphan a job pointing at\n // a non-existent NewtonChat / Message row.\n try {\n const aiPlaceholder = await createPendingAIMessage(chat.conversationId);\n await enqueueRunJob(\n QUEUE_NAMES.AI_NEWTON_CHAT as RunJobType,\n { conversationId: chat.conversationId, aiMessageId: aiPlaceholder.id },\n { userId },\n );\n } catch (error) {\n logger.error(\"Failed to enqueue Newton introduction\", {\n error,\n newtonChatId: chat.id,\n });\n }\n }\n\n return {\n conversationId: chat.conversationId,\n newtonChatId: chat.id,\n };\n}\n\nexport async function postToNewtonChat(\n userId: string,\n newtonChatId: string,\n content: string,\n mentionedUserIds: string[] = [],\n fileIds: string[] = []\n) {\n const newtonChat = await findNewtonChatByIdAndMember(newtonChatId, userId);\n if (!newtonChat) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Newton chat not found or access denied\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findConversationMembersByUserIds(\n newtonChat.conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n ensureMessageHasContentOrAttachments(content, fileIds);\n const normalizedContent = content.trim();\n\n if (!normalizedContent) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Newton chat messages must include text content\",\n });\n }\n\n const result = await prisma.$transaction(async (tx) => {\n const message = await tx.message.create({\n data: {\n content: normalizedContent,\n senderId: userId,\n conversationId: newtonChat.conversationId,\n },\n });\n\n await claimMessageAttachments(\n tx,\n userId,\n newtonChat.conversationId,\n message.id,\n fileIds\n );\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId: message.id,\n userId: mentionedUserId,\n })),\n });\n }\n\n await tx.newtonChat.update({\n where: { id: newtonChatId },\n data: { updatedAt: new Date() },\n });\n\n return tx.message.findUniqueOrThrow({\n where: { id: message.id },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: { id: true, name: true, type: true },\n },\n },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(newtonChat.conversationId),\n \"new-message\",\n {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n meta: {},\n }\n );\n } catch (error) {\n console.error(\"Failed to broadcast newton chat message:\", error);\n }\n\n if (!isAIUser(userId)) {\n // Create the AI placeholder post-tx (so a rollback can't broadcast a\n // ghost bubble); the pipeline fills it in.\n try {\n const aiPlaceholder = await createPendingAIMessage(newtonChat.conversationId);\n await enqueueRunJob(\n QUEUE_NAMES.AI_NEWTON_CHAT as RunJobType,\n { conversationId: newtonChat.conversationId, aiMessageId: aiPlaceholder.id },\n { userId },\n );\n } catch (error) {\n logger.error(\"Failed to enqueue Newton response\", {\n error,\n newtonChatId,\n studentMessageId: result.id,\n conversationId: newtonChat.conversationId,\n });\n }\n }\n\n return {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n };\n}\n"],"names":[],"mappings":"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","debug_id":"17139b82-2307-50e5-9512-3d4f94276d26"}
@@ -233,6 +233,8 @@ export declare function createInvite(userId: string, input: {
233
233
  schoolId: string;
234
234
  email: string;
235
235
  role: SchoolMembershipRole;
236
+ username?: string;
237
+ displayName?: string;
236
238
  }): Promise<{
237
239
  invite: {
238
240
  school: {
@@ -253,6 +255,10 @@ export declare function createInvite(userId: string, input: {
253
255
  revokedAt: Date | null;
254
256
  invitedById: string;
255
257
  };
258
+ createdUser: {
259
+ id: string;
260
+ username: string;
261
+ } | null;
256
262
  }>;
257
263
  export declare function revokeInvite(userId: string, input: {
258
264
  schoolId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"school.d.ts","sourceRoot":"/","sources":["services/school.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAkDtD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAWvE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;GAU7D;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BtE;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGjE;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqBF;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;GAGjE;AAED,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE;;;;;;;;;;GAS9E;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE;;;;;;;;;;GAwB9E;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE;;GAoBlD;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;GAGjE;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;GA6BvE;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;GAI9C;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;GAiB5C;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;;;;;;;;;;GAsB/D"}
1
+ {"version":3,"file":"school.d.ts","sourceRoot":"/","sources":["services/school.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,oBAAoB,EAAe,MAAM,gBAAgB,CAAC;AA2DnE,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAWvE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;GAU7D;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BtE;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGjE;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqBF;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;GAGjE;AAED,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE;;;;;;;;;;GAS9E;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE;;;;;;;;;;GAwB9E;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE;;GAoBlD;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;GAGjE;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,oBAAoB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;;;;;;;;;;;;;;;;;;;;;YAMsB,MAAM;kBAAY,MAAM;;GA2FhD;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;GAI9C;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;GAiB5C;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;;;;;;;;;;GAsB/D"}
@@ -1,12 +1,20 @@
1
+ /**
2
+ * School service - school admin workflow, memberships, and invites.
3
+ */
1
4
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="eb106c0e-5f2a-504b-9b64-f273c0b7478e")}catch(e){}}();
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="eab19eca-0947-5015-a1af-d1dcf6c40a4d")}catch(e){}}();
6
+ import { SessionType } from "@prisma/client";
3
7
  import { TRPCError } from "@trpc/server";
8
+ import { hash } from "bcryptjs";
4
9
  import { v4 as uuidv4 } from "uuid";
5
10
  import { env } from "../lib/config/env.js";
6
11
  import { prisma } from "../lib/prisma.js";
7
12
  import { sendMail } from "../utils/email.js";
13
+ import { generateUniqueUsername } from "../utils/generateUsername.js";
8
14
  import { logger } from "../utils/logger.js";
9
- import { acceptSchoolInvite, countActiveAdmins, createSchoolInvite, createSchoolWithAdmin, findActiveSchoolAdminMembership, findPendingInviteByToken, findSchoolMembership, findUserByIdForSchool, getSchoolOverview, listSchoolClasses, listSchoolInvites, listSchoolMembers, listUserSchoolMemberships, removeSchoolMembershipPreservingContent, revokeSchoolInvite, updateSchoolMembershipRole, upsertSchoolMembership, } from "../models/school.js";
15
+ import { createSession, createUser, findUserByEmail, findUserById, findUserByUsername, } from "../models/auth.js";
16
+ import { upsertUserProfile } from "../models/user.js";
17
+ import { acceptSchoolInvite, countActiveAdmins, createSchoolInvite, createSchoolWithAdmin, findActiveSchoolAdminMembership, findPendingInviteByToken, findSchoolMembership, getSchoolOverview, listSchoolClasses, listSchoolInvites, listSchoolMembers, listUserSchoolMemberships, removeSchoolMembershipPreservingContent, revokeSchoolInvite, updateSchoolMembershipRole, upsertSchoolMembership, } from "../models/school.js";
10
18
  async function requireSchoolAdmin(schoolId, userId) {
11
19
  const membership = await findActiveSchoolAdminMembership(schoolId, userId);
12
20
  if (!membership) {
@@ -152,6 +160,46 @@ export async function listInvites(userId, schoolId) {
152
160
  }
153
161
  export async function createInvite(userId, input) {
154
162
  await requireSchoolAdmin(input.schoolId, userId);
163
+ const existingUser = await findUserByEmail(input.email);
164
+ let createdUser = null;
165
+ let passwordResetToken = null;
166
+ if (!existingUser) {
167
+ let username;
168
+ if (input.username) {
169
+ const taken = await findUserByUsername(input.username);
170
+ if (taken) {
171
+ throw new TRPCError({
172
+ code: "CONFLICT",
173
+ message: "Username already taken",
174
+ });
175
+ }
176
+ username = input.username;
177
+ }
178
+ else {
179
+ username = await generateUniqueUsername(async (candidate) => {
180
+ const existing = await findUserByUsername(candidate);
181
+ return Boolean(existing);
182
+ });
183
+ }
184
+ const placeholderPassword = await hash(uuidv4(), 10);
185
+ const user = await createUser({
186
+ username,
187
+ email: input.email,
188
+ password: placeholderPassword,
189
+ verified: true,
190
+ });
191
+ createdUser = { id: user.id, username: user.username };
192
+ if (input.displayName) {
193
+ await upsertUserProfile(user.id, { displayName: input.displayName });
194
+ }
195
+ const resetSession = await createSession({
196
+ id: uuidv4(),
197
+ type: SessionType.PASSWORD_RESET,
198
+ userId: user.id,
199
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
200
+ });
201
+ passwordResetToken = resetSession.id;
202
+ }
155
203
  const token = uuidv4();
156
204
  const invite = await createSchoolInvite({
157
205
  schoolId: input.schoolId,
@@ -161,12 +209,29 @@ export async function createInvite(userId, input) {
161
209
  token,
162
210
  expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
163
211
  });
212
+ if (createdUser) {
213
+ await acceptSchoolInvite(invite.id, createdUser.id);
214
+ }
215
+ const inviteUrl = `${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`;
216
+ const passwordResetUrl = passwordResetToken
217
+ ? `${env.NEXT_PUBLIC_APP_URL}/reset-password/${passwordResetToken}`
218
+ : null;
219
+ const emailLines = [
220
+ `You've been invited to ${invite.school.name} on Studious.`,
221
+ "",
222
+ ];
223
+ if (createdUser && passwordResetUrl) {
224
+ emailLines.push(`We created an account for you with the username "${createdUser.username}" and added you to ${invite.school.name}.`, `Set your password to get started: ${passwordResetUrl}`);
225
+ }
226
+ else {
227
+ emailLines.push(`Click the link to confirm: ${inviteUrl}`);
228
+ }
164
229
  try {
165
230
  await sendMail({
166
231
  from: "hello@studious.sh",
167
232
  to: input.email,
168
233
  subject: `Join ${invite.school.name} on Studious`,
169
- text: `Click the link to join ${invite.school.name}: ${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`,
234
+ text: emailLines.join("\n"),
170
235
  });
171
236
  }
172
237
  catch (err) {
@@ -176,7 +241,7 @@ export async function createInvite(userId, input) {
176
241
  err,
177
242
  });
178
243
  }
179
- return { invite };
244
+ return { invite, createdUser };
180
245
  }
181
246
  export async function revokeInvite(userId, input) {
182
247
  await requireSchoolAdmin(input.schoolId, userId);
@@ -208,7 +273,7 @@ export async function acceptInvite(userId, token) {
208
273
  if (invite.expiresAt < new Date()) {
209
274
  throw new TRPCError({ code: "UNAUTHORIZED", message: "Invite expired" });
210
275
  }
211
- const user = await findUserByIdForSchool(userId);
276
+ const user = await findUserById(userId);
212
277
  if (!user) {
213
278
  throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
214
279
  }
@@ -222,4 +287,4 @@ export async function acceptInvite(userId, token) {
222
287
  return { membership };
223
288
  }
224
289
  //# sourceMappingURL=school.js.map
225
- //# debugId=eb106c0e-5f2a-504b-9b64-f273c0b7478e
290
+ //# debugId=eab19eca-0947-5015-a1af-d1dcf6c40a4d