@studious-lms/server 1.4.7 → 1.5.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 (201) hide show
  1. package/.github/workflows/npm.yml +28 -0
  2. package/dist/lib/config/cors.d.ts.map +1 -1
  3. package/dist/lib/config/cors.js +2 -4
  4. package/dist/lib/config/cors.js.map +1 -1
  5. package/dist/lib/fileUpload.d.ts.map +1 -1
  6. package/dist/lib/fileUpload.js +4 -5
  7. package/dist/lib/fileUpload.js.map +1 -1
  8. package/dist/models/assignment.d.ts +32 -0
  9. package/dist/models/assignment.d.ts.map +1 -1
  10. package/dist/models/assignment.js +111 -2
  11. package/dist/models/assignment.js.map +1 -1
  12. package/dist/models/auth.d.ts +1 -0
  13. package/dist/models/auth.d.ts.map +1 -1
  14. package/dist/models/auth.js +3 -2
  15. package/dist/models/auth.js.map +1 -1
  16. package/dist/models/comment.d.ts +0 -5
  17. package/dist/models/comment.d.ts.map +1 -1
  18. package/dist/models/conversation.d.ts +2 -1
  19. package/dist/models/conversation.d.ts.map +1 -1
  20. package/dist/models/labChat.d.ts +2 -1
  21. package/dist/models/labChat.d.ts.map +1 -1
  22. package/dist/models/message.d.ts +6 -3
  23. package/dist/models/message.d.ts.map +1 -1
  24. package/dist/models/school.d.ts +0 -22
  25. package/dist/models/school.d.ts.map +1 -1
  26. package/dist/models/school.js +2 -14
  27. package/dist/models/school.js.map +1 -1
  28. package/dist/models/worksheet.d.ts +174 -95
  29. package/dist/models/worksheet.d.ts.map +1 -1
  30. package/dist/models/worksheet.js +38 -5
  31. package/dist/models/worksheet.js.map +1 -1
  32. package/dist/pipelines/aiLabChat.js +6 -6
  33. package/dist/pipelines/aiLabChat.js.map +1 -1
  34. package/dist/pipelines/gradeWorksheet.d.ts +5 -3
  35. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  36. package/dist/pipelines/gradeWorksheet.js +60 -24
  37. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  38. package/dist/pipelines/pdfToWorksheet.d.ts +3 -0
  39. package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
  40. package/dist/pipelines/pdfToWorksheet.js +65 -24
  41. package/dist/pipelines/pdfToWorksheet.js.map +1 -1
  42. package/dist/routers/_app.d.ts +232 -48
  43. package/dist/routers/_app.d.ts.map +1 -1
  44. package/dist/routers/announcement.d.ts +0 -4
  45. package/dist/routers/announcement.d.ts.map +1 -1
  46. package/dist/routers/assignment.d.ts +35 -8
  47. package/dist/routers/assignment.d.ts.map +1 -1
  48. package/dist/routers/assignment.js +10 -3
  49. package/dist/routers/assignment.js.map +1 -1
  50. package/dist/routers/auth.d.ts +1 -0
  51. package/dist/routers/auth.d.ts.map +1 -1
  52. package/dist/routers/class.d.ts +19 -0
  53. package/dist/routers/class.d.ts.map +1 -1
  54. package/dist/routers/comment.d.ts +0 -3
  55. package/dist/routers/comment.d.ts.map +1 -1
  56. package/dist/routers/conversation.d.ts +2 -1
  57. package/dist/routers/conversation.d.ts.map +1 -1
  58. package/dist/routers/labChat.d.ts +2 -1
  59. package/dist/routers/labChat.d.ts.map +1 -1
  60. package/dist/routers/school.d.ts +6 -0
  61. package/dist/routers/school.d.ts.map +1 -1
  62. package/dist/routers/school.js +7 -2
  63. package/dist/routers/school.js.map +1 -1
  64. package/dist/routers/worksheet.d.ts +51 -7
  65. package/dist/routers/worksheet.d.ts.map +1 -1
  66. package/dist/routers/worksheet.js +10 -3
  67. package/dist/routers/worksheet.js.map +1 -1
  68. package/dist/services/assignment.d.ts +30 -0
  69. package/dist/services/assignment.d.ts.map +1 -1
  70. package/dist/services/assignment.js +37 -3
  71. package/dist/services/assignment.js.map +1 -1
  72. package/dist/services/auth.d.ts +1 -0
  73. package/dist/services/auth.d.ts.map +1 -1
  74. package/dist/services/class.d.ts +19 -0
  75. package/dist/services/class.d.ts.map +1 -1
  76. package/dist/services/class.js +37 -19
  77. package/dist/services/class.js.map +1 -1
  78. package/dist/services/comment.d.ts +0 -3
  79. package/dist/services/comment.d.ts.map +1 -1
  80. package/dist/services/conversation.d.ts +2 -1
  81. package/dist/services/conversation.d.ts.map +1 -1
  82. package/dist/services/labChat.d.ts +2 -1
  83. package/dist/services/labChat.d.ts.map +1 -1
  84. package/dist/services/labChat.js +6 -6
  85. package/dist/services/labChat.js.map +1 -1
  86. package/dist/services/message.d.ts.map +1 -1
  87. package/dist/services/message.js +14 -2
  88. package/dist/services/message.js.map +1 -1
  89. package/dist/services/school.d.ts +6 -0
  90. package/dist/services/school.d.ts.map +1 -1
  91. package/dist/services/school.js +71 -6
  92. package/dist/services/school.js.map +1 -1
  93. package/dist/services/worksheet.d.ts +46 -7
  94. package/dist/services/worksheet.d.ts.map +1 -1
  95. package/dist/services/worksheet.js +52 -20
  96. package/dist/services/worksheet.js.map +1 -1
  97. package/dist/utils/generateUsername.d.ts +8 -0
  98. package/dist/utils/generateUsername.d.ts.map +1 -0
  99. package/dist/utils/generateUsername.js +103 -0
  100. package/dist/utils/generateUsername.js.map +1 -0
  101. package/dist/workers/connection.d.ts +3 -0
  102. package/dist/workers/connection.d.ts.map +1 -0
  103. package/dist/workers/connection.js +33 -0
  104. package/dist/workers/connection.js.map +1 -0
  105. package/dist/workers/index.d.ts +10 -0
  106. package/dist/workers/index.d.ts.map +1 -0
  107. package/dist/workers/index.js +53 -0
  108. package/dist/workers/index.js.map +1 -0
  109. package/dist/workers/processors/pdfToWorksheet.d.ts +9 -0
  110. package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -0
  111. package/dist/workers/processors/pdfToWorksheet.js +100 -0
  112. package/dist/workers/processors/pdfToWorksheet.js.map +1 -0
  113. package/dist/workers/queues.d.ts +69 -0
  114. package/dist/workers/queues.d.ts.map +1 -0
  115. package/dist/workers/queues.js +119 -0
  116. package/dist/workers/queues.js.map +1 -0
  117. package/package.json +4 -1
  118. package/prisma/migrations/20260504071500_school_memberships/migration.sql +1 -1
  119. package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +166 -0
  120. package/prisma/schema.prisma +465 -387
  121. package/src/lib/config/cors.ts +0 -2
  122. package/src/lib/fileUpload.ts +2 -4
  123. package/src/models/assignment.ts +132 -0
  124. package/src/models/auth.ts +1 -0
  125. package/src/models/school.ts +0 -14
  126. package/src/models/worksheet.ts +56 -2
  127. package/src/pipelines/aiLabChat.ts +4 -4
  128. package/src/pipelines/gradeWorksheet.ts +63 -25
  129. package/src/pipelines/pdfToWorksheet.ts +68 -22
  130. package/src/routers/assignment.ts +19 -0
  131. package/src/routers/school.ts +5 -0
  132. package/src/routers/worksheet.ts +18 -0
  133. package/src/services/assignment.ts +47 -1
  134. package/src/services/class.ts +41 -17
  135. package/src/services/labChat.ts +4 -4
  136. package/src/services/message.ts +13 -0
  137. package/src/services/school.ts +89 -6
  138. package/src/services/worksheet.ts +62 -18
  139. package/src/utils/generateUsername.ts +108 -0
  140. package/src/workers/connection.ts +37 -0
  141. package/src/workers/index.ts +61 -0
  142. package/src/workers/processors/pdfToWorksheet.ts +119 -0
  143. package/src/workers/queues.ts +159 -0
  144. package/tests/{globalSetup.ts → integration/globalSetup.ts} +4 -4
  145. package/tests/{helpers.ts → integration/helpers.ts} +2 -2
  146. package/tests/{pipelines → integration/pipelines}/pdfToWorksheet.test.ts +55 -43
  147. package/tests/{routers → integration/routers}/attendance.test.ts +1 -1
  148. package/tests/{routers → integration/routers}/auth.test.ts +3 -3
  149. package/tests/{routers → integration/routers}/class.test.ts +3 -17
  150. package/tests/{routers → integration/routers}/conversation.test.ts +1 -1
  151. package/tests/{routers → integration/routers}/event.test.ts +1 -1
  152. package/tests/{routers → integration/routers}/message.test.ts +1 -1
  153. package/tests/{routers → integration/routers}/newtonChat.test.ts +1 -1
  154. package/tests/{routers → integration/routers}/studentProgress.test.ts +1 -1
  155. package/tests/{user.test.ts → integration/routers/user.test.ts} +2 -2
  156. package/tests/integration/routers/worksheet.test.ts +448 -0
  157. package/tests/{setup.ts → integration/setup.ts} +4 -4
  158. package/tests/{lib → unit/lib}/cors.test.ts +1 -1
  159. package/tests/{middleware → unit/middleware}/security.test.ts +1 -1
  160. package/tests/{pipelines → unit/pipelines}/aiLabChat.test.ts +1 -1
  161. package/tests/{lib → unit/pipelines}/aiLabChatContract.test.ts +1 -1
  162. package/tests/{utils/aiLabChatPrompt.test.ts → unit/pipelines/labChatPrompt.test.ts} +2 -2
  163. package/tests/{utils → unit/services}/studentProgress.test.ts +5 -5
  164. package/tests/{utils → unit/utils}/aiUser.test.ts +1 -1
  165. package/tests/{utils → unit/utils}/generateInviteCode.test.ts +1 -1
  166. package/tests/{utils → unit/utils}/inference.test.ts +6 -6
  167. package/tests/{utils → unit/utils}/logger.test.ts +1 -1
  168. package/tests/{utils → unit/utils}/prismaErrorHandler.test.ts +1 -1
  169. package/tests/{utils → unit/utils}/prismaWrapper.test.ts +1 -1
  170. package/vitest.config.ts +11 -3
  171. package/vitest.unit.config.ts +10 -10
  172. package/dist/lib/notificationHandler.d.ts +0 -25
  173. package/dist/lib/notificationHandler.d.ts.map +0 -1
  174. package/dist/lib/notificationHandler.js +0 -32
  175. package/dist/lib/notificationHandler.js.map +0 -1
  176. package/dist/server/pipelines/aiLabChat.d.ts +0 -21
  177. package/dist/server/pipelines/aiLabChat.d.ts.map +0 -1
  178. package/dist/server/pipelines/aiLabChat.js +0 -456
  179. package/dist/server/pipelines/aiLabChat.js.map +0 -1
  180. package/dist/server/pipelines/aiNewtonChat.d.ts +0 -30
  181. package/dist/server/pipelines/aiNewtonChat.d.ts.map +0 -1
  182. package/dist/server/pipelines/aiNewtonChat.js +0 -285
  183. package/dist/server/pipelines/aiNewtonChat.js.map +0 -1
  184. package/dist/server/pipelines/gradeWorksheet.d.ts +0 -30
  185. package/dist/server/pipelines/gradeWorksheet.d.ts.map +0 -1
  186. package/dist/server/pipelines/gradeWorksheet.js +0 -248
  187. package/dist/server/pipelines/gradeWorksheet.js.map +0 -1
  188. package/tests/routers/worksheet.test.ts +0 -194
  189. package/tests/startup.test.ts +0 -5
  190. package/tests/worksheet.test.ts +0 -181
  191. /package/tests/{routers → integration/routers}/agenda.test.ts +0 -0
  192. /package/tests/{routers → integration/routers}/announcement.test.ts +0 -0
  193. /package/tests/{routers → integration/routers}/assignment.test.ts +0 -0
  194. /package/tests/{routers → integration/routers}/comment.test.ts +0 -0
  195. /package/tests/{routers → integration/routers}/folder.test.ts +0 -0
  196. /package/tests/{routers → integration/routers}/labChat.test.ts +0 -0
  197. /package/tests/{routers → integration/routers}/marketing.test.ts +0 -0
  198. /package/tests/{routers → integration/routers}/notification.test.ts +0 -0
  199. /package/tests/{routers → integration/routers}/search.test.ts +0 -0
  200. /package/tests/{routers → integration/routers}/section.test.ts +0 -0
  201. /package/tests/{server → unit/middleware}/rateLimit.test.ts +0 -0
@@ -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 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":"6e561e6d-4f33-54c5-ac1f-fb72f3c08a68"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"school.js","sources":["services/school.ts"],"sourceRoot":"/","sourcesContent":["/**\n * School service - school admin workflow, memberships, and invites.\n */\nimport { SchoolMembershipRole } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { env } from \"../lib/config/env.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { sendMail } from \"../utils/email.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n acceptSchoolInvite,\n countActiveAdmins,\n createSchoolInvite,\n createSchoolWithAdmin,\n findActiveSchoolAdminMembership,\n findPendingInviteByToken,\n findSchoolMembership,\n findUserByIdForSchool,\n getSchoolById,\n getSchoolOverview,\n listSchoolClasses,\n listSchoolInvites,\n listSchoolMembers,\n listUserSchoolMemberships,\n removeSchoolMembershipPreservingContent,\n revokeSchoolInvite,\n updateSchoolMembershipRole,\n upsertSchoolMembership,\n} from \"../models/school.js\";\n\nasync function requireSchoolAdmin(schoolId: string, userId: string) {\n const membership = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can perform this action\",\n });\n }\n return membership;\n}\n\nasync function requireSchoolMember(schoolId: string, userId: string) {\n const membership = await findSchoolMembership(schoolId, userId);\n if (!membership || membership.status !== \"ACTIVE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a member of this school\",\n });\n }\n return membership;\n}\n\nexport async function getCurrentSchool(userId: string, schoolId?: string) {\n const memberships = await listUserSchoolMemberships(userId);\n const activeMembership = schoolId\n ? memberships.find((membership) => membership.schoolId === schoolId)\n : memberships[0];\n\n return {\n activeSchool: activeMembership?.school ?? null,\n activeRole: activeMembership?.role ?? null,\n memberships,\n };\n}\n\nexport async function createSchoolRecord(\n userId: string,\n input: { name: string; subdomain?: string; logoId?: string }\n) {\n const school = await createSchoolWithAdmin({\n userId,\n name: input.name,\n subdomain: input.subdomain,\n logoId: input.logoId,\n });\n\n return { school };\n}\n\nexport async function getAdminOverview(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n const school = await getSchoolOverview(schoolId);\n if (!school) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"School not found\" });\n }\n\n const roleCounts = school.memberships.reduce(\n (counts, membership) => {\n counts[membership.role] += 1;\n return counts;\n },\n { ADMIN: 0, TEACHER: 0, STUDENT: 0 } satisfies Record<SchoolMembershipRole, number>\n );\n\n return {\n school: {\n id: school.id,\n name: school.name,\n subdomain: school.subdomain,\n counts: {\n classes: school._count.classes,\n members: school._count.memberships,\n pendingInvites: school._count.invites,\n ...roleCounts,\n },\n recentClasses: school.classes,\n recentInvites: school.invites,\n },\n };\n}\n\nexport async function listClasses(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { classes: await listSchoolClasses(schoolId) };\n}\n\nexport async function createSchoolClass(\n userId: string,\n input: {\n schoolId: string;\n name: string;\n section: string;\n subject: string;\n color?: string;\n teachers?: string[];\n students?: string[];\n }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const createdClass = await prisma.class.create({\n data: {\n name: input.name,\n section: input.section,\n subject: input.subject,\n color: input.color,\n schoolId: input.schoolId,\n teachers: input.teachers?.length\n ? { connect: input.teachers.map((id) => ({ id })) }\n : undefined,\n students: input.students?.length\n ? { connect: input.students.map((id) => ({ id })) }\n : undefined,\n },\n include: { teachers: true, students: true },\n });\n\n return { class: createdClass };\n}\n\nexport async function listMembers(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { members: await listSchoolMembers(schoolId) };\n}\n\nexport async function addMember(\n userId: string,\n input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const membership = await upsertSchoolMembership({\n schoolId: input.schoolId,\n userId: input.memberUserId,\n role: input.role,\n });\n return { membership };\n}\n\nexport async function updateMemberRole(\n userId: string,\n input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const current = await findSchoolMembership(input.schoolId, input.memberUserId);\n if (!current) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Member not found\" });\n }\n\n if (current.role === \"ADMIN\" && input.role !== \"ADMIN\") {\n const adminCount = await countActiveAdmins(input.schoolId);\n if (adminCount <= 1) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"A school must have at least one active admin\",\n });\n }\n }\n\n const membership = await updateSchoolMembershipRole(\n input.schoolId,\n input.memberUserId,\n input.role\n );\n return { membership };\n}\n\nexport async function removeMember(\n userId: string,\n input: { schoolId: string; memberUserId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const current = await findSchoolMembership(input.schoolId, input.memberUserId);\n if (!current) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Member not found\" });\n }\n\n if (current.role === \"ADMIN\" && current.status === \"ACTIVE\") {\n const adminCount = await countActiveAdmins(input.schoolId);\n if (adminCount <= 1) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"A school must have at least one active admin\",\n });\n }\n }\n\n await removeSchoolMembershipPreservingContent(input.schoolId, input.memberUserId);\n return { success: true };\n}\n\nexport async function listInvites(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { invites: await listSchoolInvites(schoolId) };\n}\n\nexport async function createInvite(\n userId: string,\n input: { schoolId: string; email: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const token = uuidv4();\n const invite = await createSchoolInvite({\n schoolId: input.schoolId,\n invitedById: userId,\n email: input.email,\n role: input.role,\n token,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),\n });\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: input.email,\n subject: `Join ${invite.school.name} on Studious`,\n text: `Click the link to join ${invite.school.name}: ${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`,\n });\n } catch (err) {\n logger.error(\"Failed to send school invite email\", {\n schoolId: input.schoolId,\n email: input.email,\n err,\n });\n }\n\n return { invite };\n}\n\nexport async function revokeInvite(\n userId: string,\n input: { schoolId: string; inviteId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n return { invite: await revokeSchoolInvite(input.inviteId, input.schoolId) };\n}\n\nexport async function getInvite(token: string) {\n const invite = await findPendingInviteByToken(token);\n if (!invite || invite.status !== \"PENDING\") {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invite not found\" });\n }\n if (invite.expiresAt < new Date()) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: \"Invite expired\" });\n }\n return {\n invite: {\n id: invite.id,\n email: invite.email,\n role: invite.role,\n school: invite.school,\n expiresAt: invite.expiresAt,\n },\n };\n}\n\nexport async function acceptInvite(userId: string, token: string) {\n const invite = await findPendingInviteByToken(token);\n if (!invite || invite.status !== \"PENDING\") {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invite not found\" });\n }\n if (invite.expiresAt < new Date()) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: \"Invite expired\" });\n }\n\n const user = await findUserByIdForSchool(userId);\n if (!user) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"User not found\" });\n }\n if (user.email.toLowerCase() !== invite.email.toLowerCase()) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"This invite was sent to a different account\",\n });\n }\n\n const membership = await acceptSchoolInvite(invite.id, userId);\n return { membership };\n}\n"],"names":[],"mappings":"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","debug_id":"eb106c0e-5f2a-504b-9b64-f273c0b7478e"}
1
+ {"version":3,"file":"school.js","sources":["services/school.ts"],"sourceRoot":"/","sourcesContent":["/**\n * School service - school admin workflow, memberships, and invites.\n */\nimport { SchoolMembershipRole, SessionType } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { hash } from \"bcryptjs\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { env } from \"../lib/config/env.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { sendMail } from \"../utils/email.js\";\nimport { generateUniqueUsername } from \"../utils/generateUsername.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n createSession,\n createUser,\n findUserByEmail,\n findUserById,\n findUserByUsername,\n} from \"../models/auth.js\";\nimport { upsertUserProfile } from \"../models/user.js\";\nimport {\n acceptSchoolInvite,\n countActiveAdmins,\n createSchoolInvite,\n createSchoolWithAdmin,\n findActiveSchoolAdminMembership,\n findPendingInviteByToken,\n findSchoolMembership,\n getSchoolById,\n getSchoolOverview,\n listSchoolClasses,\n listSchoolInvites,\n listSchoolMembers,\n listUserSchoolMemberships,\n removeSchoolMembershipPreservingContent,\n revokeSchoolInvite,\n updateSchoolMembershipRole,\n upsertSchoolMembership,\n} from \"../models/school.js\";\n\nasync function requireSchoolAdmin(schoolId: string, userId: string) {\n const membership = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can perform this action\",\n });\n }\n return membership;\n}\n\nasync function requireSchoolMember(schoolId: string, userId: string) {\n const membership = await findSchoolMembership(schoolId, userId);\n if (!membership || membership.status !== \"ACTIVE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a member of this school\",\n });\n }\n return membership;\n}\n\nexport async function getCurrentSchool(userId: string, schoolId?: string) {\n const memberships = await listUserSchoolMemberships(userId);\n const activeMembership = schoolId\n ? memberships.find((membership) => membership.schoolId === schoolId)\n : memberships[0];\n\n return {\n activeSchool: activeMembership?.school ?? null,\n activeRole: activeMembership?.role ?? null,\n memberships,\n };\n}\n\nexport async function createSchoolRecord(\n userId: string,\n input: { name: string; subdomain?: string; logoId?: string }\n) {\n const school = await createSchoolWithAdmin({\n userId,\n name: input.name,\n subdomain: input.subdomain,\n logoId: input.logoId,\n });\n\n return { school };\n}\n\nexport async function getAdminOverview(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n const school = await getSchoolOverview(schoolId);\n if (!school) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"School not found\" });\n }\n\n const roleCounts = school.memberships.reduce(\n (counts, membership) => {\n counts[membership.role] += 1;\n return counts;\n },\n { ADMIN: 0, TEACHER: 0, STUDENT: 0 } satisfies Record<SchoolMembershipRole, number>\n );\n\n return {\n school: {\n id: school.id,\n name: school.name,\n subdomain: school.subdomain,\n counts: {\n classes: school._count.classes,\n members: school._count.memberships,\n pendingInvites: school._count.invites,\n ...roleCounts,\n },\n recentClasses: school.classes,\n recentInvites: school.invites,\n },\n };\n}\n\nexport async function listClasses(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { classes: await listSchoolClasses(schoolId) };\n}\n\nexport async function createSchoolClass(\n userId: string,\n input: {\n schoolId: string;\n name: string;\n section: string;\n subject: string;\n color?: string;\n teachers?: string[];\n students?: string[];\n }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const createdClass = await prisma.class.create({\n data: {\n name: input.name,\n section: input.section,\n subject: input.subject,\n color: input.color,\n schoolId: input.schoolId,\n teachers: input.teachers?.length\n ? { connect: input.teachers.map((id) => ({ id })) }\n : undefined,\n students: input.students?.length\n ? { connect: input.students.map((id) => ({ id })) }\n : undefined,\n },\n include: { teachers: true, students: true },\n });\n\n return { class: createdClass };\n}\n\nexport async function listMembers(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { members: await listSchoolMembers(schoolId) };\n}\n\nexport async function addMember(\n userId: string,\n input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const membership = await upsertSchoolMembership({\n schoolId: input.schoolId,\n userId: input.memberUserId,\n role: input.role,\n });\n return { membership };\n}\n\nexport async function updateMemberRole(\n userId: string,\n input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const current = await findSchoolMembership(input.schoolId, input.memberUserId);\n if (!current) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Member not found\" });\n }\n\n if (current.role === \"ADMIN\" && input.role !== \"ADMIN\") {\n const adminCount = await countActiveAdmins(input.schoolId);\n if (adminCount <= 1) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"A school must have at least one active admin\",\n });\n }\n }\n\n const membership = await updateSchoolMembershipRole(\n input.schoolId,\n input.memberUserId,\n input.role\n );\n return { membership };\n}\n\nexport async function removeMember(\n userId: string,\n input: { schoolId: string; memberUserId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const current = await findSchoolMembership(input.schoolId, input.memberUserId);\n if (!current) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Member not found\" });\n }\n\n if (current.role === \"ADMIN\" && current.status === \"ACTIVE\") {\n const adminCount = await countActiveAdmins(input.schoolId);\n if (adminCount <= 1) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"A school must have at least one active admin\",\n });\n }\n }\n\n await removeSchoolMembershipPreservingContent(input.schoolId, input.memberUserId);\n return { success: true };\n}\n\nexport async function listInvites(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { invites: await listSchoolInvites(schoolId) };\n}\n\nexport async function createInvite(\n userId: string,\n input: {\n schoolId: string;\n email: string;\n role: SchoolMembershipRole;\n username?: string;\n displayName?: string;\n }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n\n const existingUser = await findUserByEmail(input.email);\n\n let createdUser: { id: string; username: string } | null = null;\n let passwordResetToken: string | null = null;\n\n if (!existingUser) {\n let username: string;\n if (input.username) {\n const taken = await findUserByUsername(input.username);\n if (taken) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Username already taken\",\n });\n }\n username = input.username;\n } else {\n username = await generateUniqueUsername(async (candidate) => {\n const existing = await findUserByUsername(candidate);\n return Boolean(existing);\n });\n }\n\n const placeholderPassword = await hash(uuidv4(), 10);\n const user = await createUser({\n username,\n email: input.email,\n password: placeholderPassword,\n verified: true,\n });\n createdUser = { id: user.id, username: user.username };\n\n if (input.displayName) {\n await upsertUserProfile(user.id, { displayName: input.displayName });\n }\n\n const resetSession = await createSession({\n id: uuidv4(),\n type: SessionType.PASSWORD_RESET,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),\n });\n passwordResetToken = resetSession.id;\n }\n\n const token = uuidv4();\n const invite = await createSchoolInvite({\n schoolId: input.schoolId,\n invitedById: userId,\n email: input.email,\n role: input.role,\n token,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),\n });\n\n if (createdUser) {\n await acceptSchoolInvite(invite.id, createdUser.id);\n }\n\n const inviteUrl = `${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`;\n const passwordResetUrl = passwordResetToken\n ? `${env.NEXT_PUBLIC_APP_URL}/reset-password/${passwordResetToken}`\n : null;\n\n const emailLines = [\n `You've been invited to ${invite.school.name} on Studious.`,\n \"\",\n ];\n if (createdUser && passwordResetUrl) {\n emailLines.push(\n `We created an account for you with the username \"${createdUser.username}\" and added you to ${invite.school.name}.`,\n `Set your password to get started: ${passwordResetUrl}`\n );\n } else {\n emailLines.push(`Click the link to confirm: ${inviteUrl}`);\n }\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: input.email,\n subject: `Join ${invite.school.name} on Studious`,\n text: emailLines.join(\"\\n\"),\n });\n } catch (err) {\n logger.error(\"Failed to send school invite email\", {\n schoolId: input.schoolId,\n email: input.email,\n err,\n });\n }\n\n return { invite, createdUser };\n}\n\nexport async function revokeInvite(\n userId: string,\n input: { schoolId: string; inviteId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n return { invite: await revokeSchoolInvite(input.inviteId, input.schoolId) };\n}\n\nexport async function getInvite(token: string) {\n const invite = await findPendingInviteByToken(token);\n if (!invite || invite.status !== \"PENDING\") {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invite not found\" });\n }\n if (invite.expiresAt < new Date()) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: \"Invite expired\" });\n }\n return {\n invite: {\n id: invite.id,\n email: invite.email,\n role: invite.role,\n school: invite.school,\n expiresAt: invite.expiresAt,\n },\n };\n}\n\nexport async function acceptInvite(userId: string, token: string) {\n const invite = await findPendingInviteByToken(token);\n if (!invite || invite.status !== \"PENDING\") {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invite not found\" });\n }\n if (invite.expiresAt < new Date()) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: \"Invite expired\" });\n }\n\n const user = await findUserById(userId);\n if (!user) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"User not found\" });\n }\n if (user.email.toLowerCase() !== invite.email.toLowerCase()) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"This invite was sent to a different account\",\n });\n }\n\n const membership = await acceptSchoolInvite(invite.id, userId);\n return { membership };\n}\n"],"names":[],"mappings":"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","debug_id":"eab19eca-0947-5015-a1af-d1dcf6c40a4d"}
@@ -24,11 +24,14 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
24
24
  answer: string;
25
25
  }[];
26
26
  } & {
27
+ status: import(".prisma/client").$Enums.JobStatus | null;
27
28
  name: string;
28
29
  id: string;
29
30
  createdAt: Date;
30
31
  updatedAt: Date;
31
32
  classId: string;
33
+ runJobId: string | null;
34
+ sourceUrl: string | null;
32
35
  }>;
33
36
  /** Check if worksheet exists. */
34
37
  export declare function worksheetExists(id: string): Promise<boolean>;
@@ -37,55 +40,87 @@ export declare function listWorksheets(classId: string): Promise<{
37
40
  questions: {
38
41
  id: string;
39
42
  }[];
43
+ status: import(".prisma/client").$Enums.JobStatus | null;
40
44
  name: string;
41
45
  id: string;
42
46
  createdAt: Date;
43
47
  updatedAt: Date;
44
48
  classId: string;
49
+ runJobId: string | null;
50
+ sourceUrl: string | null;
45
51
  }[]>;
46
52
  export declare function updateWorksheetRecord(worksheetId: string, userId: string, data: {
47
53
  name?: string;
48
54
  }): Promise<{
55
+ status: import(".prisma/client").$Enums.JobStatus | null;
49
56
  name: string;
50
57
  id: string;
51
58
  createdAt: Date;
52
59
  updatedAt: Date;
53
60
  classId: string;
61
+ runJobId: string | null;
62
+ sourceUrl: string | null;
54
63
  }>;
55
64
  export declare function deleteWorksheetRecord(worksheetId: string, userId: string): Promise<{
65
+ status: import(".prisma/client").$Enums.JobStatus | null;
56
66
  name: string;
57
67
  id: string;
58
68
  createdAt: Date;
59
69
  updatedAt: Date;
60
70
  classId: string;
71
+ runJobId: string | null;
72
+ sourceUrl: string | null;
61
73
  }>;
62
74
  export declare function createWorksheetRecord(classId: string, name: string): Promise<{
75
+ status: import(".prisma/client").$Enums.JobStatus | null;
63
76
  name: string;
64
77
  id: string;
65
78
  createdAt: Date;
66
79
  updatedAt: Date;
67
80
  classId: string;
81
+ runJobId: string | null;
82
+ sourceUrl: string | null;
83
+ }>;
84
+ /**
85
+ * Duplicate a worksheet into another class. Caller must teach both the source worksheet's class
86
+ * and the target class.
87
+ */
88
+ export declare function duplicateWorksheetRecord(worksheetId: string, classId: string, name: string, userId: string): Promise<{
89
+ status: import(".prisma/client").$Enums.JobStatus | null;
90
+ name: string;
91
+ id: string;
92
+ createdAt: Date;
93
+ updatedAt: Date;
94
+ classId: string;
95
+ runJobId: string | null;
96
+ sourceUrl: string | null;
68
97
  }>;
69
98
  /**
70
99
  * Create a worksheet by converting a PDF (URL) into structured questions.
71
100
  * Uses the `pdfToWorksheet` pipeline to do inference + persistence.
72
101
  */
73
102
  export declare function createWorksheetFromPdf(classId: string, url: string): Promise<{
103
+ status: import(".prisma/client").$Enums.JobStatus | null;
74
104
  name: string;
75
105
  id: string;
76
106
  createdAt: Date;
77
107
  updatedAt: Date;
78
108
  classId: string;
109
+ runJobId: string | null;
110
+ sourceUrl: string | null;
79
111
  }>;
80
112
  /**
81
113
  * Create a worksheet from a PDF that was uploaded through the direct-upload flow.
82
114
  */
83
115
  export declare function createWorksheetFromUploadedPdf(classId: string, fileId: string, userId: string): Promise<{
116
+ status: import(".prisma/client").$Enums.JobStatus | null;
84
117
  name: string;
85
118
  id: string;
86
119
  createdAt: Date;
87
120
  updatedAt: Date;
88
121
  classId: string;
122
+ runJobId: string | null;
123
+ sourceUrl: string | null;
89
124
  }>;
90
125
  export declare function addQuestionToWorksheet(worksheetId: string, userId: string, data: {
91
126
  question: string;
@@ -149,7 +184,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
149
184
  id: string;
150
185
  }[];
151
186
  } & {
152
- status: import(".prisma/client").$Enums.GenerationStatus | null;
187
+ status: import(".prisma/client").$Enums.JobStatus | null;
153
188
  id: string;
154
189
  createdAt: Date;
155
190
  updatedAt: Date | null;
@@ -161,6 +196,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
161
196
  points: number;
162
197
  feedback: string | null;
163
198
  studentWorksheetResponseId: string | null;
199
+ runJobId: string | null;
164
200
  })[];
165
201
  } & {
166
202
  id: string;
@@ -174,7 +210,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
174
210
  }>;
175
211
  export declare function answerWorksheetQuestion(worksheetResponseId: string, questionId: string, response: string, userId: string): Promise<{
176
212
  responses: {
177
- status: import(".prisma/client").$Enums.GenerationStatus | null;
213
+ status: import(".prisma/client").$Enums.JobStatus | null;
178
214
  id: string;
179
215
  createdAt: Date;
180
216
  updatedAt: Date | null;
@@ -186,6 +222,7 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
186
222
  points: number;
187
223
  feedback: string | null;
188
224
  studentWorksheetResponseId: string | null;
225
+ runJobId: string | null;
189
226
  }[];
190
227
  } & {
191
228
  id: string;
@@ -198,7 +235,7 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
198
235
  worksheetId: string;
199
236
  }>;
200
237
  export declare function cancelGrading(worksheetResponseId: string, progressId: string, userId: string): Promise<{
201
- status: import(".prisma/client").$Enums.GenerationStatus | null;
238
+ status: import(".prisma/client").$Enums.JobStatus | null;
202
239
  id: string;
203
240
  createdAt: Date;
204
241
  updatedAt: Date | null;
@@ -210,9 +247,10 @@ export declare function cancelGrading(worksheetResponseId: string, progressId: s
210
247
  points: number;
211
248
  feedback: string | null;
212
249
  studentWorksheetResponseId: string | null;
250
+ runJobId: string | null;
213
251
  }>;
214
252
  export declare function regradeQuestion(worksheetResponseId: string, progressId: string, userId: string): Promise<{
215
- status: import(".prisma/client").$Enums.GenerationStatus | null;
253
+ status: import(".prisma/client").$Enums.JobStatus | null;
216
254
  id: string;
217
255
  createdAt: Date;
218
256
  updatedAt: Date | null;
@@ -224,7 +262,8 @@ export declare function regradeQuestion(worksheetResponseId: string, progressId:
224
262
  points: number;
225
263
  feedback: string | null;
226
264
  studentWorksheetResponseId: string | null;
227
- }>;
265
+ runJobId: string | null;
266
+ } | null>;
228
267
  export declare function gradeAnswer(questionId: string, studentWorksheetResponseId: string, userId: string, data: {
229
268
  responseId?: string;
230
269
  response?: string;
@@ -233,7 +272,7 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
233
272
  markschemeState?: object;
234
273
  points?: number;
235
274
  }): Promise<{
236
- status: import(".prisma/client").$Enums.GenerationStatus | null;
275
+ status: import(".prisma/client").$Enums.JobStatus | null;
237
276
  id: string;
238
277
  createdAt: Date;
239
278
  updatedAt: Date | null;
@@ -245,9 +284,9 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
245
284
  points: number;
246
285
  feedback: string | null;
247
286
  studentWorksheetResponseId: string | null;
287
+ runJobId: string | null;
248
288
  }>;
249
289
  export declare function addCommentToResponse(responseId: string, comment: string, authorId: string): Promise<{
250
- status: import(".prisma/client").$Enums.GenerationStatus | null;
251
290
  id: string;
252
291
  createdAt: Date;
253
292
  assignmentId: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}