@studious-lms/server 1.23.0 → 1.24.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 (298) hide show
  1. package/.env.example +0 -1
  2. package/data/curriculum/nepal-cdc-g4-g5/README.md +90 -0
  3. package/data/curriculum/nepal-cdc-g4-g5/manifest.json +97 -0
  4. package/data/curriculum/nepal-cdc-g4-g5/proficiency-scale.json +76 -0
  5. package/data/curriculum/nepal-cdc-g4-g5/subjects/english.json +6936 -0
  6. package/data/curriculum/nepal-cdc-g4-g5/subjects/health-physical-creative-arts.json +3401 -0
  7. package/data/curriculum/nepal-cdc-g4-g5/subjects/mathematics.json +1511 -0
  8. package/data/curriculum/nepal-cdc-g4-g5/subjects/nepali.json +7928 -0
  9. package/data/curriculum/nepal-cdc-g4-g5/subjects/science-technology.json +2420 -0
  10. package/data/curriculum/nepal-cdc-g4-g5/subjects/social-studies.json +2177 -0
  11. package/dist/lib/assignmentTypeDefaults.d.ts +41 -0
  12. package/dist/lib/assignmentTypeDefaults.d.ts.map +1 -0
  13. package/dist/lib/assignmentTypeDefaults.js +167 -0
  14. package/dist/lib/assignmentTypeDefaults.js.map +1 -0
  15. package/dist/lib/config/env.d.ts +0 -8
  16. package/dist/lib/config/env.d.ts.map +1 -1
  17. package/dist/lib/config/env.js +2 -5
  18. package/dist/lib/config/env.js.map +1 -1
  19. package/dist/lib/feePdf.d.ts +19 -0
  20. package/dist/lib/feePdf.d.ts.map +1 -0
  21. package/dist/lib/feePdf.js +167 -0
  22. package/dist/lib/feePdf.js.map +1 -0
  23. package/dist/lib/feePdfFormat.d.ts +4 -0
  24. package/dist/lib/feePdfFormat.d.ts.map +1 -0
  25. package/dist/lib/feePdfFormat.js +16 -0
  26. package/dist/lib/feePdfFormat.js.map +1 -0
  27. package/dist/lib/idCardPdf.d.ts +26 -0
  28. package/dist/lib/idCardPdf.d.ts.map +1 -0
  29. package/dist/lib/idCardPdf.js +272 -0
  30. package/dist/lib/idCardPdf.js.map +1 -0
  31. package/dist/lib/pdfLetterhead.d.ts +41 -0
  32. package/dist/lib/pdfLetterhead.d.ts.map +1 -0
  33. package/dist/lib/pdfLetterhead.js +86 -0
  34. package/dist/lib/pdfLetterhead.js.map +1 -0
  35. package/dist/lib/transcriptPdf.d.ts +55 -0
  36. package/dist/lib/transcriptPdf.d.ts.map +1 -0
  37. package/dist/lib/transcriptPdf.js +303 -0
  38. package/dist/lib/transcriptPdf.js.map +1 -0
  39. package/dist/models/academicCalendar.d.ts +198 -0
  40. package/dist/models/academicCalendar.d.ts.map +1 -0
  41. package/dist/models/academicCalendar.js +106 -0
  42. package/dist/models/academicCalendar.js.map +1 -0
  43. package/dist/models/agenda.d.ts +8 -0
  44. package/dist/models/agenda.d.ts.map +1 -1
  45. package/dist/models/announcement.d.ts +2 -0
  46. package/dist/models/announcement.d.ts.map +1 -1
  47. package/dist/models/assignment.d.ts +66 -0
  48. package/dist/models/assignment.d.ts.map +1 -1
  49. package/dist/models/attendance.d.ts +659 -21
  50. package/dist/models/attendance.d.ts.map +1 -1
  51. package/dist/models/attendance.js +544 -75
  52. package/dist/models/attendance.js.map +1 -1
  53. package/dist/models/auth.d.ts +2 -0
  54. package/dist/models/auth.d.ts.map +1 -1
  55. package/dist/models/class.d.ts +12 -0
  56. package/dist/models/class.d.ts.map +1 -1
  57. package/dist/models/class.js +3 -2
  58. package/dist/models/class.js.map +1 -1
  59. package/dist/models/event.d.ts +16 -0
  60. package/dist/models/event.d.ts.map +1 -1
  61. package/dist/models/fees.d.ts +554 -0
  62. package/dist/models/fees.d.ts.map +1 -0
  63. package/dist/models/fees.js +278 -0
  64. package/dist/models/fees.js.map +1 -0
  65. package/dist/models/file.d.ts +29 -0
  66. package/dist/models/file.d.ts.map +1 -1
  67. package/dist/models/folder.d.ts +2 -0
  68. package/dist/models/folder.d.ts.map +1 -1
  69. package/dist/models/labChat.d.ts +2 -0
  70. package/dist/models/labChat.d.ts.map +1 -1
  71. package/dist/models/marketplace.d.ts +178 -0
  72. package/dist/models/marketplace.d.ts.map +1 -0
  73. package/dist/models/marketplace.js +115 -0
  74. package/dist/models/marketplace.js.map +1 -0
  75. package/dist/models/moduleItem.d.ts +304 -0
  76. package/dist/models/moduleItem.d.ts.map +1 -0
  77. package/dist/models/moduleItem.js +78 -0
  78. package/dist/models/moduleItem.js.map +1 -0
  79. package/dist/models/newtonChat.d.ts +1 -0
  80. package/dist/models/newtonChat.d.ts.map +1 -1
  81. package/dist/models/school.d.ts +18 -0
  82. package/dist/models/school.d.ts.map +1 -1
  83. package/dist/models/school.js +7 -2
  84. package/dist/models/school.js.map +1 -1
  85. package/dist/models/section.d.ts +4 -0
  86. package/dist/models/section.d.ts.map +1 -1
  87. package/dist/models/worksheet.d.ts +19 -0
  88. package/dist/models/worksheet.d.ts.map +1 -1
  89. package/dist/models/worksheet.js +2 -2
  90. package/dist/models/worksheet.js.map +1 -1
  91. package/dist/routers/_app.d.ts +11341 -7097
  92. package/dist/routers/_app.d.ts.map +1 -1
  93. package/dist/routers/_app.js +12 -2
  94. package/dist/routers/_app.js.map +1 -1
  95. package/dist/routers/academicCalendar.d.ts +229 -0
  96. package/dist/routers/academicCalendar.d.ts.map +1 -0
  97. package/dist/routers/academicCalendar.js +75 -0
  98. package/dist/routers/academicCalendar.js.map +1 -0
  99. package/dist/routers/admin.d.ts +2 -0
  100. package/dist/routers/admin.d.ts.map +1 -1
  101. package/dist/routers/agenda.d.ts +8 -0
  102. package/dist/routers/agenda.d.ts.map +1 -1
  103. package/dist/routers/assignment.d.ts +75 -0
  104. package/dist/routers/assignment.d.ts.map +1 -1
  105. package/dist/routers/assignment.js +10 -2
  106. package/dist/routers/assignment.js.map +1 -1
  107. package/dist/routers/attendance.d.ts +103 -0
  108. package/dist/routers/attendance.d.ts.map +1 -1
  109. package/dist/routers/attendance.js +8 -5
  110. package/dist/routers/attendance.js.map +1 -1
  111. package/dist/routers/class.d.ts +27 -0
  112. package/dist/routers/class.d.ts.map +1 -1
  113. package/dist/routers/class.js +6 -2
  114. package/dist/routers/class.js.map +1 -1
  115. package/dist/routers/event.d.ts +8 -0
  116. package/dist/routers/event.d.ts.map +1 -1
  117. package/dist/routers/fees.d.ts +513 -0
  118. package/dist/routers/fees.d.ts.map +1 -0
  119. package/dist/routers/fees.js +88 -0
  120. package/dist/routers/fees.js.map +1 -0
  121. package/dist/routers/file.d.ts +2 -0
  122. package/dist/routers/file.d.ts.map +1 -1
  123. package/dist/routers/gradebook.d.ts +4 -1
  124. package/dist/routers/gradebook.d.ts.map +1 -1
  125. package/dist/routers/marketplace.d.ts +371 -0
  126. package/dist/routers/marketplace.d.ts.map +1 -0
  127. package/dist/routers/marketplace.js +87 -0
  128. package/dist/routers/marketplace.js.map +1 -0
  129. package/dist/routers/moduleItem.d.ts +263 -0
  130. package/dist/routers/moduleItem.d.ts.map +1 -0
  131. package/dist/routers/moduleItem.js +60 -0
  132. package/dist/routers/moduleItem.js.map +1 -0
  133. package/dist/routers/parent.d.ts +169 -0
  134. package/dist/routers/parent.d.ts.map +1 -0
  135. package/dist/routers/parent.js +49 -0
  136. package/dist/routers/parent.js.map +1 -0
  137. package/dist/routers/school.d.ts +363 -7
  138. package/dist/routers/school.d.ts.map +1 -1
  139. package/dist/routers/school.js +100 -3
  140. package/dist/routers/school.js.map +1 -1
  141. package/dist/routers/worksheet.d.ts +27 -0
  142. package/dist/routers/worksheet.d.ts.map +1 -1
  143. package/dist/routers/worksheet.js +10 -3
  144. package/dist/routers/worksheet.js.map +1 -1
  145. package/dist/services/academicCalendar.d.ts +173 -0
  146. package/dist/services/academicCalendar.d.ts.map +1 -0
  147. package/dist/services/academicCalendar.js +214 -0
  148. package/dist/services/academicCalendar.js.map +1 -0
  149. package/dist/services/adminInstitution.d.ts +2 -0
  150. package/dist/services/adminInstitution.d.ts.map +1 -1
  151. package/dist/services/agenda.d.ts +8 -0
  152. package/dist/services/agenda.d.ts.map +1 -1
  153. package/dist/services/assignment.d.ts +75 -0
  154. package/dist/services/assignment.d.ts.map +1 -1
  155. package/dist/services/assignment.js +91 -12
  156. package/dist/services/assignment.js.map +1 -1
  157. package/dist/services/attendance.d.ts +323 -10
  158. package/dist/services/attendance.d.ts.map +1 -1
  159. package/dist/services/attendance.js +385 -25
  160. package/dist/services/attendance.js.map +1 -1
  161. package/dist/services/attendanceMerge.d.ts +53 -0
  162. package/dist/services/attendanceMerge.d.ts.map +1 -0
  163. package/dist/services/attendanceMerge.js +87 -0
  164. package/dist/services/attendanceMerge.js.map +1 -0
  165. package/dist/services/attendanceRate.d.ts +24 -0
  166. package/dist/services/attendanceRate.d.ts.map +1 -0
  167. package/dist/services/attendanceRate.js +31 -0
  168. package/dist/services/attendanceRate.js.map +1 -0
  169. package/dist/services/attendanceStats.d.ts +22 -0
  170. package/dist/services/attendanceStats.d.ts.map +1 -0
  171. package/dist/services/attendanceStats.js +45 -0
  172. package/dist/services/attendanceStats.js.map +1 -0
  173. package/dist/services/class.d.ts +27 -0
  174. package/dist/services/class.d.ts.map +1 -1
  175. package/dist/services/class.js +26 -3
  176. package/dist/services/class.js.map +1 -1
  177. package/dist/services/event.d.ts +8 -0
  178. package/dist/services/event.d.ts.map +1 -1
  179. package/dist/services/fees.d.ts +422 -0
  180. package/dist/services/fees.d.ts.map +1 -0
  181. package/dist/services/fees.js +310 -0
  182. package/dist/services/fees.js.map +1 -0
  183. package/dist/services/feesAuthz.d.ts +49 -0
  184. package/dist/services/feesAuthz.d.ts.map +1 -0
  185. package/dist/services/feesAuthz.js +52 -0
  186. package/dist/services/feesAuthz.js.map +1 -0
  187. package/dist/services/file.d.ts +4 -0
  188. package/dist/services/file.d.ts.map +1 -1
  189. package/dist/services/gradebook.d.ts +18 -1
  190. package/dist/services/gradebook.d.ts.map +1 -1
  191. package/dist/services/gradebook.js +57 -2
  192. package/dist/services/gradebook.js.map +1 -1
  193. package/dist/services/idCard.d.ts +11 -0
  194. package/dist/services/idCard.d.ts.map +1 -0
  195. package/dist/services/idCard.js +122 -0
  196. package/dist/services/idCard.js.map +1 -0
  197. package/dist/services/marketplace.d.ts +301 -0
  198. package/dist/services/marketplace.d.ts.map +1 -0
  199. package/dist/services/marketplace.js +628 -0
  200. package/dist/services/marketplace.js.map +1 -0
  201. package/dist/services/moduleItem.d.ts +196 -0
  202. package/dist/services/moduleItem.d.ts.map +1 -0
  203. package/dist/services/moduleItem.js +188 -0
  204. package/dist/services/moduleItem.js.map +1 -0
  205. package/dist/services/notification.d.ts +58 -0
  206. package/dist/services/notification.d.ts.map +1 -1
  207. package/dist/services/notification.js +30 -2
  208. package/dist/services/notification.js.map +1 -1
  209. package/dist/services/parentPortal.d.ts +133 -0
  210. package/dist/services/parentPortal.d.ts.map +1 -0
  211. package/dist/services/parentPortal.js +328 -0
  212. package/dist/services/parentPortal.js.map +1 -0
  213. package/dist/services/school.d.ts +28 -3
  214. package/dist/services/school.d.ts.map +1 -1
  215. package/dist/services/school.js +65 -45
  216. package/dist/services/school.js.map +1 -1
  217. package/dist/services/studentImport.d.ts +34 -0
  218. package/dist/services/studentImport.d.ts.map +1 -0
  219. package/dist/services/studentImport.js +236 -0
  220. package/dist/services/studentImport.js.map +1 -0
  221. package/dist/services/transcript.d.ts +61 -0
  222. package/dist/services/transcript.d.ts.map +1 -0
  223. package/dist/services/transcript.js +243 -0
  224. package/dist/services/transcript.js.map +1 -0
  225. package/dist/services/worksheet.d.ts +22 -0
  226. package/dist/services/worksheet.d.ts.map +1 -1
  227. package/dist/services/worksheet.js +94 -2
  228. package/dist/services/worksheet.js.map +1 -1
  229. package/dist/trpc.d.ts +1 -1
  230. package/dist/trpc.d.ts.map +1 -1
  231. package/dist/trpc.js +5 -5
  232. package/dist/trpc.js.map +1 -1
  233. package/dist/utils/adminPanelAccess.d.ts +4 -5
  234. package/dist/utils/adminPanelAccess.d.ts.map +1 -1
  235. package/dist/utils/adminPanelAccess.js +12 -34
  236. package/dist/utils/adminPanelAccess.js.map +1 -1
  237. package/package.json +2 -1
  238. package/prisma/migrations/20260804120000_add_attendance_records_and_excusals/migration.sql +114 -0
  239. package/prisma/migrations/20260804120100_add_fee_plans_invoices_payments/migration.sql +101 -0
  240. package/prisma/migrations/20260804120200_add_marketplace_listings/migration.sql +54 -0
  241. package/prisma/migrations/20260804120300_content_platform_b3_b9/migration.sql +63 -0
  242. package/prisma/migrations/20260804120400_c_academic_ops/migration.sql +77 -0
  243. package/prisma/schema.prisma +371 -11
  244. package/scripts/import-nepal-cdc-curriculum.ts +379 -0
  245. package/src/lib/assignmentTypeDefaults.ts +222 -0
  246. package/src/lib/config/env.ts +0 -3
  247. package/src/lib/feePdf.ts +199 -0
  248. package/src/lib/feePdfFormat.ts +14 -0
  249. package/src/lib/idCardPdf.ts +336 -0
  250. package/src/lib/pdfLetterhead.ts +124 -0
  251. package/src/lib/transcriptPdf.ts +502 -0
  252. package/src/models/academicCalendar.ts +139 -0
  253. package/src/models/attendance.ts +704 -73
  254. package/src/models/class.ts +1 -0
  255. package/src/models/fees.ts +349 -0
  256. package/src/models/marketplace.ts +158 -0
  257. package/src/models/moduleItem.ts +107 -0
  258. package/src/models/school.ts +6 -0
  259. package/src/models/worksheet.ts +2 -0
  260. package/src/routers/_app.ts +10 -0
  261. package/src/routers/academicCalendar.ts +103 -0
  262. package/src/routers/assignment.ts +8 -0
  263. package/src/routers/attendance.ts +7 -3
  264. package/src/routers/class.ts +4 -0
  265. package/src/routers/fees.ts +126 -0
  266. package/src/routers/marketplace.ts +134 -0
  267. package/src/routers/moduleItem.ts +89 -0
  268. package/src/routers/parent.ts +68 -0
  269. package/src/routers/school.ts +139 -1
  270. package/src/routers/worksheet.ts +11 -0
  271. package/src/services/academicCalendar.ts +299 -0
  272. package/src/services/assignment.ts +122 -12
  273. package/src/services/attendance.ts +548 -26
  274. package/src/services/attendanceMerge.ts +139 -0
  275. package/src/services/attendanceRate.ts +41 -0
  276. package/src/services/attendanceStats.ts +63 -0
  277. package/src/services/class.ts +25 -1
  278. package/src/services/fees.ts +424 -0
  279. package/src/services/feesAuthz.ts +77 -0
  280. package/src/services/gradebook.ts +70 -0
  281. package/src/services/idCard.ts +147 -0
  282. package/src/services/marketplace.ts +785 -0
  283. package/src/services/moduleItem.ts +274 -0
  284. package/src/services/notification.ts +43 -0
  285. package/src/services/parentPortal.ts +380 -0
  286. package/src/services/school.ts +65 -44
  287. package/src/services/studentImport.ts +273 -0
  288. package/src/services/transcript.ts +313 -0
  289. package/src/services/worksheet.ts +111 -0
  290. package/src/trpc.ts +3 -3
  291. package/src/utils/adminPanelAccess.ts +12 -35
  292. package/tests/unit/assignmentTypeDefaults.test.ts +52 -0
  293. package/tests/unit/lib/feePdf.test.ts +15 -0
  294. package/tests/unit/lib/idCardPdf.test.ts +40 -0
  295. package/tests/unit/lib/transcriptPdf.test.ts +111 -0
  296. package/tests/unit/services/attendanceMerge.test.ts +90 -0
  297. package/tests/unit/services/attendanceRate.test.ts +38 -0
  298. package/tests/unit/services/attendanceStats.test.ts +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marketplace.js","sources":["services/marketplace.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Marketplace service — publish / search / import for shareable markschemes,\n * worksheets, and files. Exercise/poll types are out of scope for B1 v1.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport type {\n MarketplaceListingType,\n MarketplaceVisibility,\n Prisma,\n WorksheetQuestionType,\n} from \"@prisma/client\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { copyFile } from \"../lib/googleCloudStorage.js\";\nimport { classTeacherOrSchoolAdminWhere } from \"../models/access.js\";\nimport { findOrCreateScopeRoot } from \"../models/cloudStorage.js\";\nimport {\n createMarketplaceListing,\n deleteMarketplaceListing,\n findMarketplaceListingById,\n incrementMarketplaceReportCount,\n searchMarketplaceListings,\n setMarketplaceListingHidden,\n} from \"../models/marketplace.js\";\nimport {\n findActiveSchoolAdminMembership,\n findSchoolMembership,\n listUserSchoolMemberships,\n} from \"../models/school.js\";\nimport { duplicateWorksheetWithClient } from \"../models/worksheet.js\";\n\ntype MarkSchemePayload = {\n kind: \"MARKSCHEME\";\n name: string | null;\n structured: string;\n};\n\ntype WorksheetQuestionPayload = {\n type: WorksheetQuestionType;\n question: string;\n answer: string;\n options: Prisma.JsonValue;\n markScheme: Prisma.JsonValue;\n points: number;\n order: number | null;\n};\n\ntype WorksheetPayload = {\n kind: \"WORKSHEET\";\n name: string;\n questions: WorksheetQuestionPayload[];\n};\n\ntype FilePayload = {\n kind: \"FILE\";\n name: string;\n type: string;\n size: number | null;\n path: string;\n};\n\ntype ListingPayload = MarkSchemePayload | WorksheetPayload | FilePayload;\n\nfunction normalizeTags(tags?: string[]) {\n if (!tags?.length) return [];\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of tags) {\n const tag = raw.trim().replace(/\\s+/g, \" \").slice(0, 40);\n if (!tag) continue;\n const key = tag.toLowerCase();\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(tag);\n if (out.length >= 12) break;\n }\n return out;\n}\n\nasync function requireActiveSchoolMember(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\nasync function assertCanTeachClass(classId: string, userId: string) {\n const cls = await prisma.class.findFirst({\n where: { id: classId, ...classTeacherOrSchoolAdminWhere(userId) },\n select: { id: true, schoolId: true, departmentId: true },\n });\n if (!cls) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You don't have permission to use this class\",\n });\n }\n return cls;\n}\n\nasync function assertCanAccessListing(\n listing: NonNullable<Awaited<ReturnType<typeof findMarketplaceListingById>>>,\n userId: string\n) {\n if (listing.authorId === userId) return;\n if (listing.isHidden) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Listing not found\" });\n }\n if (listing.visibility === \"PUBLIC\") return;\n if (!listing.schoolId) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Listing is not available\" });\n }\n await requireActiveSchoolMember(listing.schoolId, userId);\n}\n\nfunction asPayload(raw: Prisma.JsonValue): ListingPayload {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Listing payload is invalid\",\n });\n }\n const kind = (raw as { kind?: string }).kind;\n if (kind === \"MARKSCHEME\" || kind === \"WORKSHEET\" || kind === \"FILE\") {\n return raw as ListingPayload;\n }\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Listing payload is invalid\",\n });\n}\n\nexport async function listMarketplace(\n userId: string,\n input: {\n type?: MarketplaceListingType;\n search?: string;\n tags?: string[];\n schoolId?: string;\n cursor?: string;\n limit?: number;\n }\n) {\n const memberships = await listUserSchoolMemberships(userId);\n const schoolIds = input.schoolId\n ? memberships.some((m) => m.schoolId === input.schoolId)\n ? [input.schoolId]\n : []\n : memberships.map((m) => m.schoolId);\n\n return searchMarketplaceListings({\n schoolIds,\n type: input.type,\n search: input.search,\n tags: input.tags,\n cursor: input.cursor,\n limit: input.limit ?? 20,\n authorId: userId,\n });\n}\n\nexport async function getMarketplaceListing(userId: string, listingId: string) {\n const listing = await findMarketplaceListingById(listingId);\n if (!listing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Listing not found\" });\n }\n await assertCanAccessListing(listing, userId);\n return listing;\n}\n\nexport async function publishMarkSchemeListing(\n userId: string,\n input: {\n markSchemeId: string;\n title: string;\n description?: string;\n visibility: MarketplaceVisibility;\n schoolId?: string;\n tags?: string[];\n }\n) {\n const markScheme = await prisma.markScheme.findUnique({\n where: { id: input.markSchemeId },\n select: {\n id: true,\n name: true,\n structured: true,\n classId: true,\n departmentId: true,\n class: { select: { schoolId: true } },\n department: { select: { schoolId: true } },\n },\n });\n if (!markScheme) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Mark scheme not found\" });\n }\n\n let schoolId =\n input.schoolId ??\n markScheme.class?.schoolId ??\n markScheme.department?.schoolId ??\n null;\n\n if (markScheme.classId) {\n await assertCanTeachClass(markScheme.classId, userId);\n } else if (markScheme.departmentId && schoolId) {\n const admin = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!admin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can publish department mark schemes\",\n });\n }\n } else {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Mark scheme is not scoped to a class or department\",\n });\n }\n\n if (input.visibility === \"SCHOOL\") {\n if (!schoolId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"schoolId is required for SCHOOL visibility\",\n });\n }\n await requireActiveSchoolMember(schoolId, userId);\n } else if (schoolId) {\n await requireActiveSchoolMember(schoolId, userId);\n }\n\n const payload: MarkSchemePayload = {\n kind: \"MARKSCHEME\",\n name: markScheme.name,\n structured: markScheme.structured,\n };\n\n return createMarketplaceListing({\n type: \"MARKSCHEME\",\n title: input.title.trim(),\n description: input.description?.trim() || null,\n authorId: userId,\n schoolId,\n visibility: input.visibility,\n markSchemeId: markScheme.id,\n payload,\n tags: normalizeTags(input.tags),\n });\n}\n\nexport async function publishWorksheetListing(\n userId: string,\n input: {\n worksheetId: string;\n title: string;\n description?: string;\n visibility: MarketplaceVisibility;\n schoolId?: string;\n tags?: string[];\n }\n) {\n const worksheet = await prisma.worksheet.findFirst({\n where: {\n id: input.worksheetId,\n class: classTeacherOrSchoolAdminWhere(userId),\n },\n include: {\n questions: { orderBy: { order: \"asc\" } },\n class: { select: { schoolId: true } },\n },\n });\n if (!worksheet) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet not found or you don't have permission to publish it\",\n });\n }\n\n const schoolId = input.schoolId ?? worksheet.class?.schoolId ?? null;\n if (input.visibility === \"SCHOOL\") {\n if (!schoolId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"schoolId is required for SCHOOL visibility\",\n });\n }\n await requireActiveSchoolMember(schoolId, userId);\n } else if (schoolId) {\n await requireActiveSchoolMember(schoolId, userId);\n }\n\n const payload: WorksheetPayload = {\n kind: \"WORKSHEET\",\n name: worksheet.name,\n questions: worksheet.questions.map((q) => ({\n type: q.type,\n question: q.question,\n answer: q.answer,\n options: q.options ?? {},\n markScheme: q.markScheme ?? {},\n points: q.points,\n order: q.order,\n })),\n };\n\n return createMarketplaceListing({\n type: \"WORKSHEET\",\n title: input.title.trim(),\n description: input.description?.trim() || null,\n authorId: userId,\n schoolId,\n visibility: input.visibility,\n worksheetId: worksheet.id,\n payload,\n tags: normalizeTags(input.tags),\n });\n}\n\nexport async function publishFileListing(\n userId: string,\n input: {\n fileId: string;\n title: string;\n description?: string;\n visibility: MarketplaceVisibility;\n schoolId?: string;\n tags?: string[];\n }\n) {\n const file = await prisma.file.findUnique({\n where: { id: input.fileId },\n select: {\n id: true,\n name: true,\n type: true,\n size: true,\n path: true,\n userId: true,\n folderId: true,\n uploadStatus: true,\n folder: {\n select: {\n classId: true,\n schoolStorageId: true,\n personalOwnerId: true,\n class: { select: { schoolId: true } },\n },\n },\n },\n });\n if (!file || file.uploadStatus === \"FAILED\" || file.uploadStatus === \"CANCELLED\") {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"File not found\" });\n }\n\n const isOwner = file.userId === userId;\n const isPersonal = file.folder?.personalOwnerId === userId;\n let canPublish = isOwner || isPersonal;\n let schoolId = input.schoolId ?? file.folder?.schoolStorageId ?? file.folder?.class?.schoolId ?? null;\n\n if (!canPublish && file.folder?.classId) {\n await assertCanTeachClass(file.folder.classId, userId);\n canPublish = true;\n }\n if (!canPublish && file.folder?.schoolStorageId) {\n const admin = await findActiveSchoolAdminMembership(\n file.folder.schoolStorageId,\n userId\n );\n if (!admin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You don't have permission to publish this file\",\n });\n }\n canPublish = true;\n schoolId = schoolId ?? file.folder.schoolStorageId;\n }\n if (!canPublish) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You don't have permission to publish this file\",\n });\n }\n\n if (input.visibility === \"SCHOOL\") {\n if (!schoolId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"schoolId is required for SCHOOL visibility\",\n });\n }\n await requireActiveSchoolMember(schoolId, userId);\n } else if (schoolId) {\n await requireActiveSchoolMember(schoolId, userId);\n }\n\n const payload: FilePayload = {\n kind: \"FILE\",\n name: file.name,\n type: file.type,\n size: file.size,\n path: file.path,\n };\n\n return createMarketplaceListing({\n type: \"FILE\",\n title: input.title.trim(),\n description: input.description?.trim() || null,\n authorId: userId,\n schoolId,\n visibility: input.visibility,\n fileId: file.id,\n payload,\n tags: normalizeTags(input.tags),\n });\n}\n\n/**\n * Import a listing into the caller's workspace:\n * - MARKSCHEME → class mark scheme (or department template when departmentId given)\n * - WORKSHEET → class worksheet copy\n * - FILE → personal / class / school folder copy (GCS object copied)\n */\nexport async function importMarketplaceListing(\n userId: string,\n input: {\n listingId: string;\n classId?: string;\n departmentId?: string;\n folderId?: string;\n name?: string;\n }\n) {\n const listing = await findMarketplaceListingById(input.listingId);\n if (!listing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Listing not found\" });\n }\n await assertCanAccessListing(listing, userId);\n\n const payload = asPayload(listing.payload);\n const name = input.name?.trim();\n\n if (listing.type === \"MARKSCHEME\") {\n if (payload.kind !== \"MARKSCHEME\") {\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Mark scheme payload mismatch\",\n });\n }\n if (input.departmentId) {\n const department = await prisma.department.findUnique({\n where: { id: input.departmentId },\n select: { id: true, schoolId: true },\n });\n if (!department) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Department not found\" });\n }\n const admin = await findActiveSchoolAdminMembership(\n department.schoolId,\n userId\n );\n if (!admin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can import into a department\",\n });\n }\n return {\n type: \"MARKSCHEME\" as const,\n markScheme: await prisma.markScheme.create({\n data: {\n department: { connect: { id: department.id } },\n name: name || payload.name || listing.title,\n structured: payload.structured,\n },\n select: { id: true, name: true, departmentId: true, classId: true },\n }),\n };\n }\n if (!input.classId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"classId is required to import a mark scheme\",\n });\n }\n await assertCanTeachClass(input.classId, userId);\n return {\n type: \"MARKSCHEME\" as const,\n markScheme: await prisma.markScheme.create({\n data: {\n class: { connect: { id: input.classId } },\n name: name || payload.name || listing.title,\n structured: payload.structured,\n },\n select: { id: true, name: true, departmentId: true, classId: true },\n }),\n };\n }\n\n if (listing.type === \"WORKSHEET\") {\n if (payload.kind !== \"WORKSHEET\") {\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Worksheet payload mismatch\",\n });\n }\n if (!input.classId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"classId is required to import a worksheet\",\n });\n }\n await assertCanTeachClass(input.classId, userId);\n\n // Prefer live source clone when still available; otherwise rebuild from snapshot.\n if (listing.worksheetId) {\n const created = await prisma.$transaction((tx) =>\n duplicateWorksheetWithClient(\n tx,\n listing.worksheetId!,\n input.classId!,\n name || payload.name || listing.title\n )\n );\n if (created) {\n return { type: \"WORKSHEET\" as const, worksheet: created };\n }\n }\n\n const created = await prisma.worksheet.create({\n data: {\n classId: input.classId,\n name: name || payload.name || listing.title,\n questions: {\n create: payload.questions.map((q) => ({\n type: q.type,\n question: q.question,\n answer: q.answer,\n options: (q.options as Prisma.InputJsonValue) ?? {},\n markScheme: (q.markScheme as Prisma.InputJsonValue) ?? {},\n points: q.points,\n order: q.order,\n })),\n },\n },\n select: { id: true, name: true, classId: true },\n });\n return { type: \"WORKSHEET\" as const, worksheet: created };\n }\n\n if (listing.type === \"FILE\") {\n if (payload.kind !== \"FILE\") {\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"File payload mismatch\",\n });\n }\n\n let targetFolderId = input.folderId;\n if (!targetFolderId) {\n if (input.classId) {\n await assertCanTeachClass(input.classId, userId);\n const root = await findOrCreateScopeRoot({\n kind: \"CLASS\",\n classId: input.classId,\n });\n targetFolderId = root.id;\n } else {\n const root = await findOrCreateScopeRoot({\n kind: \"PERSONAL\",\n userId,\n });\n targetFolderId = root.id;\n }\n } else {\n const folder = await prisma.folder.findUnique({\n where: { id: targetFolderId },\n select: {\n id: true,\n classId: true,\n schoolStorageId: true,\n personalOwnerId: true,\n },\n });\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (folder.personalOwnerId && folder.personalOwnerId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You don't have permission to import into this folder\",\n });\n }\n if (folder.classId) {\n await assertCanTeachClass(folder.classId, userId);\n }\n if (folder.schoolStorageId) {\n const admin = await findActiveSchoolAdminMembership(\n folder.schoolStorageId,\n userId\n );\n if (!admin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can import into school storage\",\n });\n }\n }\n }\n\n const ext = payload.name.includes(\".\")\n ? payload.name.slice(payload.name.lastIndexOf(\".\"))\n : \"\";\n const newPath = `marketplace/${uuidv4()}${ext}`;\n await copyFile(payload.path, newPath);\n\n const created = await prisma.file.create({\n data: {\n name: name || payload.name || listing.title,\n type: payload.type,\n size: payload.size ?? undefined,\n path: newPath,\n userId,\n folderId: targetFolderId,\n uploadStatus: \"COMPLETED\",\n uploadedAt: new Date(),\n },\n select: { id: true, name: true, folderId: true, path: true, type: true },\n });\n return { type: \"FILE\" as const, file: created };\n }\n\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Unsupported listing type\",\n });\n}\n\nexport async function reportMarketplaceListing(\n userId: string,\n listingId: string\n) {\n const listing = await findMarketplaceListingById(listingId);\n if (!listing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Listing not found\" });\n }\n await assertCanAccessListing(listing, userId);\n // C10 light stub — increment only; no reporter dedupe table yet.\n return incrementMarketplaceReportCount(listingId);\n}\n\nexport async function hideMarketplaceListing(\n userId: string,\n listingId: string,\n isHidden: boolean\n) {\n const listing = await findMarketplaceListingById(listingId);\n if (!listing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Listing not found\" });\n }\n const isAuthor = listing.authorId === userId;\n const isSchoolAdmin =\n listing.schoolId != null &&\n Boolean(await findActiveSchoolAdminMembership(listing.schoolId, userId));\n if (!isAuthor && !isSchoolAdmin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only the author or a school admin can hide this listing\",\n });\n }\n return setMarketplaceListingHidden(listingId, isHidden);\n}\n\nexport async function unpublishMarketplaceListing(\n userId: string,\n listingId: string\n) {\n const listing = await findMarketplaceListingById(listingId);\n if (!listing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Listing not found\" });\n }\n if (listing.authorId !== userId) {\n const isSchoolAdmin =\n listing.schoolId != null &&\n Boolean(await findActiveSchoolAdminMembership(listing.schoolId, userId));\n if (!isSchoolAdmin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only the author or a school admin can unpublish this listing\",\n });\n }\n await setMarketplaceListingHidden(listingId, true);\n return { success: true as const, deleted: false };\n }\n const result = await deleteMarketplaceListing(listingId, userId);\n return { success: true as const, deleted: result.count > 0 };\n}\n\n/** Publishable sources for the current user (for the publish dialog). */\nexport async function listPublishableSources(\n userId: string,\n input: { classId?: string; schoolId?: string }\n) {\n const classFilter = input.classId\n ? { id: input.classId, ...classTeacherOrSchoolAdminWhere(userId) }\n : classTeacherOrSchoolAdminWhere(userId);\n\n const [markSchemes, worksheets, files] = await Promise.all([\n prisma.markScheme.findMany({\n where: {\n OR: [\n { class: classFilter },\n ...(input.schoolId\n ? [\n {\n department: {\n schoolId: input.schoolId,\n school: {\n memberships: {\n some: {\n userId,\n role: \"ADMIN\" as const,\n status: \"ACTIVE\" as const,\n },\n },\n },\n },\n },\n ]\n : []),\n ],\n },\n select: {\n id: true,\n name: true,\n classId: true,\n departmentId: true,\n class: { select: { name: true } },\n department: { select: { name: true } },\n },\n take: 50,\n orderBy: { id: \"desc\" },\n }),\n prisma.worksheet.findMany({\n where: { class: classFilter },\n select: {\n id: true,\n name: true,\n classId: true,\n class: { select: { name: true } },\n _count: { select: { questions: true } },\n },\n take: 50,\n orderBy: { updatedAt: \"desc\" },\n }),\n prisma.file.findMany({\n where: {\n uploadStatus: \"COMPLETED\",\n OR: [\n { userId },\n { folder: { personalOwnerId: userId } },\n ...(input.classId\n ? [{ folder: { classId: input.classId, class: classFilter } }]\n : [{ folder: { class: classFilter } }]),\n ],\n },\n select: {\n id: true,\n name: true,\n type: true,\n size: true,\n folderId: true,\n },\n take: 50,\n orderBy: { uploadedAt: \"desc\" },\n }),\n ]);\n\n return { markSchemes, worksheets, files };\n}\n"],"names":[],"mappings":"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","debug_id":"d9987991-b0c2-515d-a006-a7e292a202cd"}
@@ -0,0 +1,196 @@
1
+ import type { ModuleItemType } from "@prisma/client";
2
+ export declare function listModuleItemsForClass(classId: string, userId: string): Promise<({
3
+ section: {
4
+ id: string;
5
+ name: string;
6
+ color: string | null;
7
+ classId: string;
8
+ };
9
+ files: {
10
+ path: string;
11
+ type: string;
12
+ id: string;
13
+ name: string;
14
+ size: number | null;
15
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
16
+ }[];
17
+ } & {
18
+ type: import(".prisma/client").$Enums.ModuleItemType;
19
+ id: string;
20
+ createdAt: Date;
21
+ updatedAt: Date;
22
+ title: string;
23
+ body: string | null;
24
+ sectionId: string;
25
+ order: number | null;
26
+ published: boolean;
27
+ })[]>;
28
+ export declare function listModuleItemsForSection(sectionId: string, userId: string): Promise<({
29
+ section: {
30
+ id: string;
31
+ name: string;
32
+ color: string | null;
33
+ classId: string;
34
+ };
35
+ files: {
36
+ path: string;
37
+ type: string;
38
+ id: string;
39
+ name: string;
40
+ size: number | null;
41
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
42
+ }[];
43
+ } & {
44
+ type: import(".prisma/client").$Enums.ModuleItemType;
45
+ id: string;
46
+ createdAt: Date;
47
+ updatedAt: Date;
48
+ title: string;
49
+ body: string | null;
50
+ sectionId: string;
51
+ order: number | null;
52
+ published: boolean;
53
+ })[]>;
54
+ export declare function getModuleItem(id: string, userId: string): Promise<{
55
+ section: {
56
+ id: string;
57
+ name: string;
58
+ color: string | null;
59
+ classId: string;
60
+ };
61
+ files: {
62
+ path: string;
63
+ type: string;
64
+ id: string;
65
+ name: string;
66
+ size: number | null;
67
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
68
+ }[];
69
+ } & {
70
+ type: import(".prisma/client").$Enums.ModuleItemType;
71
+ id: string;
72
+ createdAt: Date;
73
+ updatedAt: Date;
74
+ title: string;
75
+ body: string | null;
76
+ sectionId: string;
77
+ order: number | null;
78
+ published: boolean;
79
+ }>;
80
+ export declare function createModuleItemRecord(userId: string, input: {
81
+ sectionId: string;
82
+ title: string;
83
+ type: ModuleItemType;
84
+ body?: string | null;
85
+ published?: boolean;
86
+ files?: {
87
+ name: string;
88
+ type: string;
89
+ size: number;
90
+ }[];
91
+ existingFileIds?: string[];
92
+ }): Promise<{
93
+ item: {
94
+ section: {
95
+ id: string;
96
+ name: string;
97
+ color: string | null;
98
+ classId: string;
99
+ };
100
+ files: {
101
+ path: string;
102
+ type: string;
103
+ id: string;
104
+ name: string;
105
+ size: number | null;
106
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
107
+ }[];
108
+ } & {
109
+ type: import(".prisma/client").$Enums.ModuleItemType;
110
+ id: string;
111
+ createdAt: Date;
112
+ updatedAt: Date;
113
+ title: string;
114
+ body: string | null;
115
+ sectionId: string;
116
+ order: number | null;
117
+ published: boolean;
118
+ };
119
+ uploadFiles: import("../lib/fileUpload.js").DirectUploadFile[];
120
+ }>;
121
+ export declare function updateModuleItemRecord(userId: string, input: {
122
+ id: string;
123
+ title?: string;
124
+ body?: string | null;
125
+ published?: boolean;
126
+ type?: ModuleItemType;
127
+ sectionId?: string;
128
+ order?: number | null;
129
+ existingFileIds?: string[];
130
+ files?: {
131
+ name: string;
132
+ type: string;
133
+ size: number;
134
+ }[];
135
+ }): Promise<{
136
+ item: {
137
+ section: {
138
+ id: string;
139
+ name: string;
140
+ color: string | null;
141
+ classId: string;
142
+ };
143
+ files: {
144
+ path: string;
145
+ type: string;
146
+ id: string;
147
+ name: string;
148
+ size: number | null;
149
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
150
+ }[];
151
+ } & {
152
+ type: import(".prisma/client").$Enums.ModuleItemType;
153
+ id: string;
154
+ createdAt: Date;
155
+ updatedAt: Date;
156
+ title: string;
157
+ body: string | null;
158
+ sectionId: string;
159
+ order: number | null;
160
+ published: boolean;
161
+ };
162
+ uploadFiles: import("../lib/fileUpload.js").DirectUploadFile[];
163
+ }>;
164
+ export declare function deleteModuleItemRecord(userId: string, id: string): Promise<{
165
+ success: boolean;
166
+ }>;
167
+ export declare function reorderModuleItems(userId: string, input: {
168
+ sectionId: string;
169
+ orderedIds: string[];
170
+ }): Promise<({
171
+ section: {
172
+ id: string;
173
+ name: string;
174
+ color: string | null;
175
+ classId: string;
176
+ };
177
+ files: {
178
+ path: string;
179
+ type: string;
180
+ id: string;
181
+ name: string;
182
+ size: number | null;
183
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
184
+ }[];
185
+ } & {
186
+ type: import(".prisma/client").$Enums.ModuleItemType;
187
+ id: string;
188
+ createdAt: Date;
189
+ updatedAt: Date;
190
+ title: string;
191
+ body: string | null;
192
+ sectionId: string;
193
+ order: number | null;
194
+ published: boolean;
195
+ })[]>;
196
+ //# sourceMappingURL=moduleItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moduleItem.d.ts","sourceRoot":"/","sources":["services/moduleItem.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAgDrD,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;MAqB5E;AAED,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;MA2Bf;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;GAU7D;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDF;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;GAUtE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;;;;;;;;;;;;;;;;;;;;;;;;;MAYF"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * ModuleItem service – Canvas-style class content (pages, file packs, videos).
3
+ */
4
+
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]="59adfd77-e2d9-5f75-9467-06605b2ddc77")}catch(e){}}();
6
+ import { TRPCError } from "@trpc/server";
7
+ import { prisma } from "../lib/prisma.js";
8
+ import { attachFilesToModuleItem, createModuleItem, deleteModuleItem, findModuleItemByIdForMember, findModuleItemByIdForTeacher, findModuleItemsByClassId, findModuleItemsBySectionId, updateModuleItem, } from "../models/moduleItem.js";
9
+ import { classTeacherOrSchoolAdminWhere } from "../models/access.js";
10
+ import { createDirectUploadFiles } from "../lib/fileUpload.js";
11
+ const VIDEO_MIME_PREFIX = "video/";
12
+ function assertVideoFiles(type, files) {
13
+ if (type !== "VIDEO" || !files?.length)
14
+ return;
15
+ const invalid = files.filter((f) => !f.type.startsWith(VIDEO_MIME_PREFIX));
16
+ if (invalid.length > 0) {
17
+ throw new TRPCError({
18
+ code: "BAD_REQUEST",
19
+ message: "VIDEO module items only accept video MIME types",
20
+ });
21
+ }
22
+ }
23
+ async function assertTeacherOwnsSection(sectionId, userId) {
24
+ const section = await prisma.section.findFirst({
25
+ where: {
26
+ id: sectionId,
27
+ class: classTeacherOrSchoolAdminWhere(userId),
28
+ },
29
+ select: { id: true, classId: true },
30
+ });
31
+ if (!section) {
32
+ throw new TRPCError({
33
+ code: "FORBIDDEN",
34
+ message: "Only class teachers can manage module items",
35
+ });
36
+ }
37
+ return section;
38
+ }
39
+ export async function listModuleItemsForClass(classId, userId) {
40
+ const membership = await prisma.class.findFirst({
41
+ where: {
42
+ id: classId,
43
+ OR: [
44
+ { teachers: { some: { id: userId } } },
45
+ { students: { some: { id: userId } } },
46
+ ],
47
+ },
48
+ select: { id: true },
49
+ });
50
+ if (!membership) {
51
+ throw new TRPCError({ code: "FORBIDDEN", message: "Not a class member" });
52
+ }
53
+ const items = await findModuleItemsByClassId(classId);
54
+ const isTeacher = await prisma.class.findFirst({
55
+ where: { id: classId, ...classTeacherOrSchoolAdminWhere(userId) },
56
+ select: { id: true },
57
+ });
58
+ if (isTeacher)
59
+ return items;
60
+ return items.filter((item) => item.published);
61
+ }
62
+ export async function listModuleItemsForSection(sectionId, userId) {
63
+ const section = await prisma.section.findFirst({
64
+ where: {
65
+ id: sectionId,
66
+ class: {
67
+ OR: [
68
+ { teachers: { some: { id: userId } } },
69
+ { students: { some: { id: userId } } },
70
+ ],
71
+ },
72
+ },
73
+ select: { id: true, classId: true },
74
+ });
75
+ if (!section) {
76
+ throw new TRPCError({ code: "FORBIDDEN", message: "Not a class member" });
77
+ }
78
+ const items = await findModuleItemsBySectionId(sectionId);
79
+ const isTeacher = await prisma.class.findFirst({
80
+ where: {
81
+ id: section.classId,
82
+ ...classTeacherOrSchoolAdminWhere(userId),
83
+ },
84
+ select: { id: true },
85
+ });
86
+ if (isTeacher)
87
+ return items;
88
+ return items.filter((item) => item.published);
89
+ }
90
+ export async function getModuleItem(id, userId) {
91
+ const item = await findModuleItemByIdForMember(id, userId);
92
+ if (!item) {
93
+ throw new TRPCError({ code: "NOT_FOUND", message: "Module item not found" });
94
+ }
95
+ const isTeacher = await findModuleItemByIdForTeacher(id, userId);
96
+ if (!item.published && !isTeacher) {
97
+ throw new TRPCError({ code: "NOT_FOUND", message: "Module item not found" });
98
+ }
99
+ return item;
100
+ }
101
+ export async function createModuleItemRecord(userId, input) {
102
+ await assertTeacherOwnsSection(input.sectionId, userId);
103
+ if (input.type === "PAGE" && !input.body && !input.files?.length) {
104
+ // Allow empty page draft; body can be filled later.
105
+ }
106
+ if ((input.type === "FILES" || input.type === "VIDEO") &&
107
+ !input.files?.length &&
108
+ !input.existingFileIds?.length) {
109
+ // Allow empty collection; files attach later via upload URLs.
110
+ }
111
+ assertVideoFiles(input.type, input.files);
112
+ const maxOrder = await prisma.moduleItem.aggregate({
113
+ where: { sectionId: input.sectionId },
114
+ _max: { order: true },
115
+ });
116
+ const order = (maxOrder._max.order ?? 0) + 1;
117
+ const created = await createModuleItem({
118
+ sectionId: input.sectionId,
119
+ title: input.title,
120
+ type: input.type,
121
+ body: input.type === "PAGE" ? input.body ?? "" : input.body,
122
+ order,
123
+ published: input.published ?? true,
124
+ });
125
+ if (input.existingFileIds?.length) {
126
+ await attachFilesToModuleItem(created.id, input.existingFileIds);
127
+ }
128
+ let uploadFiles = [];
129
+ if (input.files?.length) {
130
+ uploadFiles = await createDirectUploadFiles(input.files, userId, undefined, undefined, undefined);
131
+ await attachFilesToModuleItem(created.id, uploadFiles.map((f) => f.id));
132
+ }
133
+ const refreshed = await findModuleItemByIdForTeacher(created.id, userId);
134
+ return { item: refreshed ?? created, uploadFiles };
135
+ }
136
+ export async function updateModuleItemRecord(userId, input) {
137
+ const existing = await findModuleItemByIdForTeacher(input.id, userId);
138
+ if (!existing) {
139
+ throw new TRPCError({
140
+ code: "FORBIDDEN",
141
+ message: "Only class teachers can update module items",
142
+ });
143
+ }
144
+ const nextType = input.type ?? existing.type;
145
+ assertVideoFiles(nextType, input.files);
146
+ if (input.sectionId && input.sectionId !== existing.sectionId) {
147
+ await assertTeacherOwnsSection(input.sectionId, userId);
148
+ }
149
+ const updated = await updateModuleItem(input.id, {
150
+ title: input.title,
151
+ body: input.body,
152
+ published: input.published,
153
+ type: input.type,
154
+ sectionId: input.sectionId,
155
+ order: input.order,
156
+ });
157
+ if (input.existingFileIds?.length) {
158
+ await attachFilesToModuleItem(input.id, input.existingFileIds);
159
+ }
160
+ let uploadFiles = [];
161
+ if (input.files?.length) {
162
+ uploadFiles = await createDirectUploadFiles(input.files, userId, undefined, undefined, undefined);
163
+ await attachFilesToModuleItem(input.id, uploadFiles.map((f) => f.id));
164
+ }
165
+ const refreshed = await findModuleItemByIdForTeacher(input.id, userId);
166
+ return { item: refreshed ?? updated, uploadFiles };
167
+ }
168
+ export async function deleteModuleItemRecord(userId, id) {
169
+ const existing = await findModuleItemByIdForTeacher(id, userId);
170
+ if (!existing) {
171
+ throw new TRPCError({
172
+ code: "FORBIDDEN",
173
+ message: "Only class teachers can delete module items",
174
+ });
175
+ }
176
+ await deleteModuleItem(id);
177
+ return { success: true };
178
+ }
179
+ export async function reorderModuleItems(userId, input) {
180
+ await assertTeacherOwnsSection(input.sectionId, userId);
181
+ await prisma.$transaction(input.orderedIds.map((id, index) => prisma.moduleItem.updateMany({
182
+ where: { id, sectionId: input.sectionId },
183
+ data: { order: index + 1 },
184
+ })));
185
+ return findModuleItemsBySectionId(input.sectionId);
186
+ }
187
+ //# sourceMappingURL=moduleItem.js.map
188
+ //# debugId=59adfd77-e2d9-5f75-9467-06605b2ddc77
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moduleItem.js","sources":["services/moduleItem.ts"],"sourceRoot":"/","sourcesContent":["/**\n * ModuleItem service – Canvas-style class content (pages, file packs, videos).\n */\nimport { TRPCError } from \"@trpc/server\";\nimport type { ModuleItemType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n attachFilesToModuleItem,\n createModuleItem,\n deleteModuleItem,\n findModuleItemByIdForMember,\n findModuleItemByIdForTeacher,\n findModuleItemsByClassId,\n findModuleItemsBySectionId,\n updateModuleItem,\n} from \"../models/moduleItem.js\";\nimport { classTeacherOrSchoolAdminWhere } from \"../models/access.js\";\nimport { createDirectUploadFiles } from \"../lib/fileUpload.js\";\n\nconst VIDEO_MIME_PREFIX = \"video/\";\n\nfunction assertVideoFiles(\n type: ModuleItemType,\n files?: { type: string }[]\n) {\n if (type !== \"VIDEO\" || !files?.length) return;\n const invalid = files.filter((f) => !f.type.startsWith(VIDEO_MIME_PREFIX));\n if (invalid.length > 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"VIDEO module items only accept video MIME types\",\n });\n }\n}\n\nasync function assertTeacherOwnsSection(sectionId: string, userId: string) {\n const section = await prisma.section.findFirst({\n where: {\n id: sectionId,\n class: classTeacherOrSchoolAdminWhere(userId),\n },\n select: { id: true, classId: true },\n });\n if (!section) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can manage module items\",\n });\n }\n return section;\n}\n\nexport async function listModuleItemsForClass(classId: string, userId: string) {\n const membership = await prisma.class.findFirst({\n where: {\n id: classId,\n OR: [\n { teachers: { some: { id: userId } } },\n { students: { some: { id: userId } } },\n ],\n },\n select: { id: true },\n });\n if (!membership) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Not a class member\" });\n }\n const items = await findModuleItemsByClassId(classId);\n const isTeacher = await prisma.class.findFirst({\n where: { id: classId, ...classTeacherOrSchoolAdminWhere(userId) },\n select: { id: true },\n });\n if (isTeacher) return items;\n return items.filter((item) => item.published);\n}\n\nexport async function listModuleItemsForSection(\n sectionId: string,\n userId: string\n) {\n const section = await prisma.section.findFirst({\n where: {\n id: sectionId,\n class: {\n OR: [\n { teachers: { some: { id: userId } } },\n { students: { some: { id: userId } } },\n ],\n },\n },\n select: { id: true, classId: true },\n });\n if (!section) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Not a class member\" });\n }\n const items = await findModuleItemsBySectionId(sectionId);\n const isTeacher = await prisma.class.findFirst({\n where: {\n id: section.classId,\n ...classTeacherOrSchoolAdminWhere(userId),\n },\n select: { id: true },\n });\n if (isTeacher) return items;\n return items.filter((item) => item.published);\n}\n\nexport async function getModuleItem(id: string, userId: string) {\n const item = await findModuleItemByIdForMember(id, userId);\n if (!item) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Module item not found\" });\n }\n const isTeacher = await findModuleItemByIdForTeacher(id, userId);\n if (!item.published && !isTeacher) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Module item not found\" });\n }\n return item;\n}\n\nexport async function createModuleItemRecord(\n userId: string,\n input: {\n sectionId: string;\n title: string;\n type: ModuleItemType;\n body?: string | null;\n published?: boolean;\n files?: { name: string; type: string; size: number }[];\n existingFileIds?: string[];\n }\n) {\n await assertTeacherOwnsSection(input.sectionId, userId);\n\n if (input.type === \"PAGE\" && !input.body && !input.files?.length) {\n // Allow empty page draft; body can be filled later.\n }\n if (\n (input.type === \"FILES\" || input.type === \"VIDEO\") &&\n !input.files?.length &&\n !input.existingFileIds?.length\n ) {\n // Allow empty collection; files attach later via upload URLs.\n }\n\n assertVideoFiles(input.type, input.files);\n\n const maxOrder = await prisma.moduleItem.aggregate({\n where: { sectionId: input.sectionId },\n _max: { order: true },\n });\n const order = (maxOrder._max.order ?? 0) + 1;\n\n const created = await createModuleItem({\n sectionId: input.sectionId,\n title: input.title,\n type: input.type,\n body: input.type === \"PAGE\" ? input.body ?? \"\" : input.body,\n order,\n published: input.published ?? true,\n });\n\n if (input.existingFileIds?.length) {\n await attachFilesToModuleItem(created.id, input.existingFileIds);\n }\n\n let uploadFiles: Awaited<ReturnType<typeof createDirectUploadFiles>> = [];\n if (input.files?.length) {\n uploadFiles = await createDirectUploadFiles(\n input.files,\n userId,\n undefined,\n undefined,\n undefined\n );\n await attachFilesToModuleItem(\n created.id,\n uploadFiles.map((f) => f.id)\n );\n }\n\n const refreshed = await findModuleItemByIdForTeacher(created.id, userId);\n return { item: refreshed ?? created, uploadFiles };\n}\n\nexport async function updateModuleItemRecord(\n userId: string,\n input: {\n id: string;\n title?: string;\n body?: string | null;\n published?: boolean;\n type?: ModuleItemType;\n sectionId?: string;\n order?: number | null;\n existingFileIds?: string[];\n files?: { name: string; type: string; size: number }[];\n }\n) {\n const existing = await findModuleItemByIdForTeacher(input.id, userId);\n if (!existing) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update module items\",\n });\n }\n\n const nextType = input.type ?? existing.type;\n assertVideoFiles(nextType, input.files);\n\n if (input.sectionId && input.sectionId !== existing.sectionId) {\n await assertTeacherOwnsSection(input.sectionId, userId);\n }\n\n const updated = await updateModuleItem(input.id, {\n title: input.title,\n body: input.body,\n published: input.published,\n type: input.type,\n sectionId: input.sectionId,\n order: input.order,\n });\n\n if (input.existingFileIds?.length) {\n await attachFilesToModuleItem(input.id, input.existingFileIds);\n }\n\n let uploadFiles: Awaited<ReturnType<typeof createDirectUploadFiles>> = [];\n if (input.files?.length) {\n uploadFiles = await createDirectUploadFiles(\n input.files,\n userId,\n undefined,\n undefined,\n undefined\n );\n await attachFilesToModuleItem(\n input.id,\n uploadFiles.map((f) => f.id)\n );\n }\n\n const refreshed = await findModuleItemByIdForTeacher(input.id, userId);\n return { item: refreshed ?? updated, uploadFiles };\n}\n\nexport async function deleteModuleItemRecord(userId: string, id: string) {\n const existing = await findModuleItemByIdForTeacher(id, userId);\n if (!existing) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete module items\",\n });\n }\n await deleteModuleItem(id);\n return { success: true };\n}\n\nexport async function reorderModuleItems(\n userId: string,\n input: {\n sectionId: string;\n orderedIds: string[];\n }\n) {\n await assertTeacherOwnsSection(input.sectionId, userId);\n await prisma.$transaction(\n input.orderedIds.map((id, index) =>\n prisma.moduleItem.updateMany({\n where: { id, sectionId: input.sectionId },\n data: { order: index + 1 },\n })\n )\n );\n return findModuleItemsBySectionId(input.sectionId);\n}\n"],"names":[],"mappings":"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","debug_id":"59adfd77-e2d9-5f75-9467-06605b2ddc77"}
@@ -132,4 +132,62 @@ export declare function markAsRead(userId: string, id: string): Promise<{
132
132
  export declare function markAllAsRead(userId: string): Promise<{
133
133
  count: number;
134
134
  }>;
135
+ /** C6: Emit a FEES notification (due / paid / reminder). Used by A2 fee flows. */
136
+ export declare function notifyFeeEvent(input: {
137
+ receiverIds: string[];
138
+ title: string;
139
+ content: string;
140
+ entityId?: string | null;
141
+ senderId?: string | null;
142
+ }): Promise<({
143
+ sender: {
144
+ username: string;
145
+ } | null;
146
+ receiver: {
147
+ username: string;
148
+ };
149
+ } & {
150
+ type: import(".prisma/client").$Enums.NotificationType;
151
+ id: string;
152
+ createdAt: Date;
153
+ classId: string | null;
154
+ title: string;
155
+ content: string;
156
+ senderId: string | null;
157
+ read: boolean;
158
+ receiverId: string;
159
+ entityType: string | null;
160
+ entityId: string | null;
161
+ })[]>;
162
+ /**
163
+ * C6: Excusal notification hook (trivial).
164
+ * A1 owns StudentExcusal; call this when an excusal is created/decided.
165
+ */
166
+ export declare function notifyExcusalEvent(input: {
167
+ receiverIds: string[];
168
+ title: string;
169
+ content: string;
170
+ entityId?: string | null;
171
+ classId?: string | null;
172
+ senderId?: string | null;
173
+ }): Promise<({
174
+ sender: {
175
+ username: string;
176
+ } | null;
177
+ receiver: {
178
+ username: string;
179
+ };
180
+ } & {
181
+ type: import(".prisma/client").$Enums.NotificationType;
182
+ id: string;
183
+ createdAt: Date;
184
+ classId: string | null;
185
+ title: string;
186
+ content: string;
187
+ senderId: string | null;
188
+ read: boolean;
189
+ receiverId: string;
190
+ entityType: string | null;
191
+ entityId: string | null;
192
+ })[]>;
135
193
  //# sourceMappingURL=notification.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notification.d.ts","sourceRoot":"/","sources":["services/notification.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAQL,KAAK,sBAAsB,EAC5B,MAAM,2BAA2B,CAAC;AAInC,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AAsBjE,qEAAqE;AACrE,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;GAYnD;AAED,wDAAwD;AACxD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;GAW/D;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,qEAE5C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,sBAAsB;;;;;;;;;;;;;;;;;;;GAInE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;;;;;;;;;;;;;;;;;;;MAQA;AAED,oDAAoD;AACpD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;GAW1D;AAED,iDAAiD;AACjD,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM;;GAGjD"}
1
+ {"version":3,"file":"notification.d.ts","sourceRoot":"/","sources":["services/notification.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAQL,KAAK,sBAAsB,EAC5B,MAAM,2BAA2B,CAAC;AAInC,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AAsBjE,qEAAqE;AACrE,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;GAYnD;AAED,wDAAwD;AACxD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;GAW/D;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,qEAE5C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,sBAAsB;;;;;;;;;;;;;;;;;;;GAInE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;;;;;;;;;;;;;;;;;;;MAQA;AAED,oDAAoD;AACpD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;GAW1D;AAED,iDAAiD;AACjD,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM;;GAGjD;AAED,kFAAkF;AAClF,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;;;;;;;;;;;;;;;;;;;MAUA;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;;;;;;;;;;;;;;;;;;;MAWA"}
@@ -7,7 +7,7 @@
7
7
  * receiver's private per-user channel.
8
8
  */
9
9
 
10
- !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]="4c2c4c1f-8449-5d74-97fa-b6db6e2b92b0")}catch(e){}}();
10
+ !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]="1a9e09c3-7424-5b2f-8b38-4719aa064f5b")}catch(e){}}();
11
11
  import { TRPCError } from "@trpc/server";
12
12
  import { findNotificationsByReceiverIdPaginated, findNotificationByIdForReceiver, countUnreadNotifications, createNotification, createManyNotifications, markNotificationAsReadForReceiver, markAllNotificationsAsReadForReceiver, } from "../models/notification.js";
13
13
  import { pusher, userNotificationChannel } from "../lib/pusher.js";
@@ -92,5 +92,33 @@ export async function markAllAsRead(userId) {
92
92
  const result = await markAllNotificationsAsReadForReceiver(userId);
93
93
  return { count: result.count };
94
94
  }
95
+ /** C6: Emit a FEES notification (due / paid / reminder). Used by A2 fee flows. */
96
+ export async function notifyFeeEvent(input) {
97
+ return sendToMultiple({
98
+ receiverIds: input.receiverIds,
99
+ title: input.title,
100
+ content: input.content,
101
+ senderId: input.senderId,
102
+ type: "FEES",
103
+ entityType: "FEE",
104
+ entityId: input.entityId ?? null,
105
+ });
106
+ }
107
+ /**
108
+ * C6: Excusal notification hook (trivial).
109
+ * A1 owns StudentExcusal; call this when an excusal is created/decided.
110
+ */
111
+ export async function notifyExcusalEvent(input) {
112
+ return sendToMultiple({
113
+ receiverIds: input.receiverIds,
114
+ title: input.title,
115
+ content: input.content,
116
+ senderId: input.senderId,
117
+ type: "EXCUSAL",
118
+ entityType: "EXCUSAL",
119
+ entityId: input.entityId ?? null,
120
+ classId: input.classId ?? null,
121
+ });
122
+ }
95
123
  //# sourceMappingURL=notification.js.map
96
- //# debugId=4c2c4c1f-8449-5d74-97fa-b6db6e2b92b0
124
+ //# debugId=1a9e09c3-7424-5b2f-8b38-4719aa064f5b