@studious-lms/server 1.23.0 → 1.25.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 +547 -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 +180 -0
  72. package/dist/models/marketplace.d.ts.map +1 -0
  73. package/dist/models/marketplace.js +116 -0
  74. package/dist/models/marketplace.js.map +1 -0
  75. package/dist/models/moduleItem.d.ts +308 -0
  76. package/dist/models/moduleItem.d.ts.map +1 -0
  77. package/dist/models/moduleItem.js +93 -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 +12024 -7778
  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 +61 -0
  132. package/dist/routers/moduleItem.js.map +1 -0
  133. package/dist/routers/parent.d.ts +170 -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 +110 -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 +242 -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 +327 -10
  158. package/dist/services/attendance.d.ts.map +1 -1
  159. package/dist/services/attendance.js +389 -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 +34 -2
  174. package/dist/services/class.d.ts.map +1 -1
  175. package/dist/services/class.js +27 -4
  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 +330 -0
  182. package/dist/services/fees.js.map +1 -0
  183. package/dist/services/feesAuthz.d.ts +46 -0
  184. package/dist/services/feesAuthz.d.ts.map +1 -0
  185. package/dist/services/feesAuthz.js +49 -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 +631 -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 +204 -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 +131 -0
  210. package/dist/services/parentPortal.d.ts.map +1 -0
  211. package/dist/services/parentPortal.js +314 -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 +98 -54
  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 +249 -0
  220. package/dist/services/studentImport.js.map +1 -0
  221. package/dist/services/transcript.d.ts +60 -0
  222. package/dist/services/transcript.d.ts.map +1 -0
  223. package/dist/services/transcript.js +231 -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 +402 -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 +702 -73
  254. package/src/models/class.ts +1 -0
  255. package/src/models/fees.ts +349 -0
  256. package/src/models/marketplace.ts +161 -0
  257. package/src/models/moduleItem.ts +125 -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 +91 -0
  268. package/src/routers/parent.ts +68 -0
  269. package/src/routers/school.ts +149 -1
  270. package/src/routers/worksheet.ts +11 -0
  271. package/src/services/academicCalendar.ts +327 -0
  272. package/src/services/assignment.ts +122 -12
  273. package/src/services/attendance.ts +552 -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 +26 -2
  278. package/src/services/fees.ts +447 -0
  279. package/src/services/feesAuthz.ts +73 -0
  280. package/src/services/gradebook.ts +70 -0
  281. package/src/services/idCard.ts +147 -0
  282. package/src/services/marketplace.ts +790 -0
  283. package/src/services/moduleItem.ts +298 -0
  284. package/src/services/notification.ts +43 -0
  285. package/src/services/parentPortal.ts +367 -0
  286. package/src/services/school.ts +100 -54
  287. package/src/services/studentImport.ts +285 -0
  288. package/src/services/transcript.ts +301 -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
@@ -1 +1 @@
1
- {"version":3,"file":"notification.js","sources":["services/notification.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Notification service – list, emit, and mark notifications as read.\n *\n * Notifications are only emitted server-side by domain services (assignments,\n * announcements, discussions, attendance, …); there is no public \"send\"\n * endpoint. New notifications are also pushed in realtime over Pusher on the\n * receiver's private per-user channel.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport type { NotificationType } from \"@prisma/client\";\nimport {\n findNotificationsByReceiverIdPaginated,\n findNotificationByIdForReceiver,\n countUnreadNotifications,\n createNotification,\n createManyNotifications,\n markNotificationAsReadForReceiver,\n markAllNotificationsAsReadForReceiver,\n type NotificationCreateData,\n} from \"../models/notification.js\";\nimport { pusher, userNotificationChannel } from \"../lib/pusher.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport const NOTIFICATION_CREATED_EVENT = \"notification-created\";\n\ntype CreatedNotification = Awaited<ReturnType<typeof createNotification>>;\n\n/** Pusher batch triggers accept at most 10 events per call. */\nconst PUSHER_BATCH_SIZE = 10;\n\nasync function pushNotifications(notifications: CreatedNotification[]) {\n for (let i = 0; i < notifications.length; i += PUSHER_BATCH_SIZE) {\n const batch = notifications.slice(i, i + PUSHER_BATCH_SIZE).map((notification) => ({\n channel: userNotificationChannel(notification.receiverId),\n name: NOTIFICATION_CREATED_EVENT,\n data: notification,\n }));\n try {\n await pusher.triggerBatch(batch);\n } catch (error) {\n logger.error(\"Failed to push notifications over Pusher\", { error });\n }\n }\n}\n\n/** List notifications for a user, newest first, cursor-paginated. */\nexport async function listNotifications(\n userId: string,\n input?: { limit?: number; cursor?: string | null }\n) {\n const limit = input?.limit ?? 20;\n const rows = await findNotificationsByReceiverIdPaginated(userId, limit, input?.cursor);\n\n let nextCursor: string | null = null;\n if (rows.length > limit) {\n const nextItem = rows.pop();\n nextCursor = nextItem!.id;\n }\n\n return { items: rows, nextCursor };\n}\n\n/** Get a single notification by ID for its receiver. */\nexport async function getNotification(userId: string, id: string) {\n const notification = await findNotificationByIdForReceiver(id, userId);\n\n if (!notification) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Notification not found\",\n });\n }\n\n return notification;\n}\n\n/** Number of unread notifications for a user. */\nexport function getUnreadCount(userId: string) {\n return countUnreadNotifications(userId);\n}\n\n/**\n * Emit a notification to one user (server-side only) and push it over Pusher.\n */\nexport async function sendNotification(input: NotificationCreateData) {\n const notification = await createNotification(input);\n void pushNotifications([notification]);\n return notification;\n}\n\n/**\n * Emit the same notification to multiple users (server-side only) and push\n * each over Pusher.\n */\nexport async function sendToMultiple(input: {\n receiverIds: string[];\n title: string;\n content: string;\n senderId?: string | null;\n type?: NotificationType;\n entityType?: string | null;\n entityId?: string | null;\n classId?: string | null;\n}) {\n const { receiverIds, ...rest } = input;\n if (receiverIds.length === 0) return [];\n const notifications = await createManyNotifications(\n receiverIds.map((receiverId) => ({ receiverId, ...rest }))\n );\n void pushNotifications(notifications);\n return notifications;\n}\n\n/** Mark a notification as read for its receiver. */\nexport async function markAsRead(userId: string, id: string) {\n const notification = await markNotificationAsReadForReceiver(id, userId);\n\n if (!notification) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Notification not found\",\n });\n }\n\n return notification;\n}\n\n/** Mark all notifications as read for a user. */\nexport async function markAllAsRead(userId: string) {\n const result = await markAllNotificationsAsReadForReceiver(userId);\n return { count: result.count };\n}\n"],"names":[],"mappings":"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","debug_id":"4c2c4c1f-8449-5d74-97fa-b6db6e2b92b0"}
1
+ {"version":3,"file":"notification.js","sources":["services/notification.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Notification service – list, emit, and mark notifications as read.\n *\n * Notifications are only emitted server-side by domain services (assignments,\n * announcements, discussions, attendance, …); there is no public \"send\"\n * endpoint. New notifications are also pushed in realtime over Pusher on the\n * receiver's private per-user channel.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport type { NotificationType } from \"@prisma/client\";\nimport {\n findNotificationsByReceiverIdPaginated,\n findNotificationByIdForReceiver,\n countUnreadNotifications,\n createNotification,\n createManyNotifications,\n markNotificationAsReadForReceiver,\n markAllNotificationsAsReadForReceiver,\n type NotificationCreateData,\n} from \"../models/notification.js\";\nimport { pusher, userNotificationChannel } from \"../lib/pusher.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport const NOTIFICATION_CREATED_EVENT = \"notification-created\";\n\ntype CreatedNotification = Awaited<ReturnType<typeof createNotification>>;\n\n/** Pusher batch triggers accept at most 10 events per call. */\nconst PUSHER_BATCH_SIZE = 10;\n\nasync function pushNotifications(notifications: CreatedNotification[]) {\n for (let i = 0; i < notifications.length; i += PUSHER_BATCH_SIZE) {\n const batch = notifications.slice(i, i + PUSHER_BATCH_SIZE).map((notification) => ({\n channel: userNotificationChannel(notification.receiverId),\n name: NOTIFICATION_CREATED_EVENT,\n data: notification,\n }));\n try {\n await pusher.triggerBatch(batch);\n } catch (error) {\n logger.error(\"Failed to push notifications over Pusher\", { error });\n }\n }\n}\n\n/** List notifications for a user, newest first, cursor-paginated. */\nexport async function listNotifications(\n userId: string,\n input?: { limit?: number; cursor?: string | null }\n) {\n const limit = input?.limit ?? 20;\n const rows = await findNotificationsByReceiverIdPaginated(userId, limit, input?.cursor);\n\n let nextCursor: string | null = null;\n if (rows.length > limit) {\n const nextItem = rows.pop();\n nextCursor = nextItem!.id;\n }\n\n return { items: rows, nextCursor };\n}\n\n/** Get a single notification by ID for its receiver. */\nexport async function getNotification(userId: string, id: string) {\n const notification = await findNotificationByIdForReceiver(id, userId);\n\n if (!notification) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Notification not found\",\n });\n }\n\n return notification;\n}\n\n/** Number of unread notifications for a user. */\nexport function getUnreadCount(userId: string) {\n return countUnreadNotifications(userId);\n}\n\n/**\n * Emit a notification to one user (server-side only) and push it over Pusher.\n */\nexport async function sendNotification(input: NotificationCreateData) {\n const notification = await createNotification(input);\n void pushNotifications([notification]);\n return notification;\n}\n\n/**\n * Emit the same notification to multiple users (server-side only) and push\n * each over Pusher.\n */\nexport async function sendToMultiple(input: {\n receiverIds: string[];\n title: string;\n content: string;\n senderId?: string | null;\n type?: NotificationType;\n entityType?: string | null;\n entityId?: string | null;\n classId?: string | null;\n}) {\n const { receiverIds, ...rest } = input;\n if (receiverIds.length === 0) return [];\n const notifications = await createManyNotifications(\n receiverIds.map((receiverId) => ({ receiverId, ...rest }))\n );\n void pushNotifications(notifications);\n return notifications;\n}\n\n/** Mark a notification as read for its receiver. */\nexport async function markAsRead(userId: string, id: string) {\n const notification = await markNotificationAsReadForReceiver(id, userId);\n\n if (!notification) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Notification not found\",\n });\n }\n\n return notification;\n}\n\n/** Mark all notifications as read for a user. */\nexport async function markAllAsRead(userId: string) {\n const result = await markAllNotificationsAsReadForReceiver(userId);\n return { count: result.count };\n}\n\n/** C6: Emit a FEES notification (due / paid / reminder). Used by A2 fee flows. */\nexport async function notifyFeeEvent(input: {\n receiverIds: string[];\n title: string;\n content: string;\n entityId?: string | null;\n senderId?: string | null;\n}) {\n return sendToMultiple({\n receiverIds: input.receiverIds,\n title: input.title,\n content: input.content,\n senderId: input.senderId,\n type: \"FEES\",\n entityType: \"FEE\",\n entityId: input.entityId ?? null,\n });\n}\n\n/**\n * C6: Excusal notification hook (trivial).\n * A1 owns StudentExcusal; call this when an excusal is created/decided.\n */\nexport async function notifyExcusalEvent(input: {\n receiverIds: string[];\n title: string;\n content: string;\n entityId?: string | null;\n classId?: string | null;\n senderId?: string | null;\n}) {\n return sendToMultiple({\n receiverIds: input.receiverIds,\n title: input.title,\n content: input.content,\n senderId: input.senderId,\n type: \"EXCUSAL\",\n entityType: \"EXCUSAL\",\n entityId: input.entityId ?? null,\n classId: input.classId ?? null,\n });\n}\n"],"names":[],"mappings":"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","debug_id":"1a9e09c3-7424-5b2f-8b38-4719aa064f5b"}
@@ -0,0 +1,131 @@
1
+ export declare function linkParentToStudent(userId: string, input: {
2
+ schoolId: string;
3
+ parentUserId: string;
4
+ studentUserId: string;
5
+ }): Promise<{
6
+ link: {
7
+ id: string;
8
+ createdAt: Date;
9
+ updatedAt: Date;
10
+ schoolId: string;
11
+ parentUserId: string;
12
+ studentUserId: string;
13
+ };
14
+ }>;
15
+ export declare function unlinkParentFromStudent(userId: string, input: {
16
+ schoolId: string;
17
+ parentUserId: string;
18
+ studentUserId: string;
19
+ }): Promise<{
20
+ ok: true;
21
+ }>;
22
+ export declare function listParentLinks(userId: string, input: {
23
+ schoolId: string;
24
+ parentUserId?: string;
25
+ studentUserId?: string;
26
+ }): Promise<{
27
+ links: ({
28
+ parent: {
29
+ id: string;
30
+ username: string;
31
+ email: string;
32
+ profile: {
33
+ displayName: string | null;
34
+ } | null;
35
+ };
36
+ student: {
37
+ id: string;
38
+ username: string;
39
+ email: string;
40
+ profile: {
41
+ displayName: string | null;
42
+ } | null;
43
+ };
44
+ } & {
45
+ id: string;
46
+ createdAt: Date;
47
+ updatedAt: Date;
48
+ schoolId: string;
49
+ parentUserId: string;
50
+ studentUserId: string;
51
+ })[];
52
+ }>;
53
+ export declare function listMyChildren(userId: string, schoolId: string): Promise<{
54
+ children: {
55
+ linkId: string;
56
+ student: {
57
+ id: string;
58
+ username: string;
59
+ email: string;
60
+ displayName: string | null;
61
+ profilePicture: string | null;
62
+ profilePictureThumbnail: string | null;
63
+ classes: {
64
+ section: string;
65
+ id: string;
66
+ name: string;
67
+ color: string | null;
68
+ subject: string;
69
+ }[];
70
+ };
71
+ }[];
72
+ }>;
73
+ /** Read-only attendance view for a linked child (reads AttendanceRecord). */
74
+ export declare function getChildAttendance(userId: string, input: {
75
+ schoolId: string;
76
+ studentUserId: string;
77
+ classId?: string;
78
+ from?: Date;
79
+ to?: Date;
80
+ }): Promise<{
81
+ summary: {
82
+ present: number;
83
+ late: number;
84
+ absent: number;
85
+ excused: number;
86
+ total: number;
87
+ };
88
+ records: {
89
+ attendanceId: string;
90
+ date: Date;
91
+ classId: string;
92
+ class: {
93
+ section: string;
94
+ id: string;
95
+ name: string;
96
+ subject: string;
97
+ } | null;
98
+ status: import(".prisma/client").$Enums.AttendanceStatus;
99
+ }[];
100
+ }>;
101
+ /**
102
+ * Read-only grades stub — returns assignment score rows for the linked child.
103
+ */
104
+ export declare function getChildGrades(userId: string, input: {
105
+ schoolId: string;
106
+ studentUserId: string;
107
+ classId?: string;
108
+ }): Promise<{
109
+ classes: {
110
+ id: string;
111
+ name: string;
112
+ subject: string;
113
+ section: string;
114
+ assignments: {
115
+ id: string;
116
+ title: string;
117
+ type: import(".prisma/client").$Enums.AssignmentType;
118
+ dueDate: Date;
119
+ maxGrade: number | null;
120
+ submission: {
121
+ id: string;
122
+ gradeReceived: number | null;
123
+ returned: boolean | null;
124
+ submittedAt: Date | null;
125
+ } | null;
126
+ }[];
127
+ }[];
128
+ }>;
129
+ /** Helper for other services: is this user a parent of the student? */
130
+ export declare function isParentOfStudent(schoolId: string, parentUserId: string, studentUserId: string): Promise<boolean>;
131
+ //# sourceMappingURL=parentPortal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parentPortal.d.ts","sourceRoot":"/","sources":["services/parentPortal.ts"],"names":[],"mappings":"AAkCA,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE;;;;;;;;;GAiDzE;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE;;GAWzE;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;GA8B3E;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;GAgDpE;AA0BD,6EAA6E;AAC7E,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,EAAE,CAAC,EAAE,IAAI,CAAC;CACX;;;;;;;;;;;;;;;;;;;;GA2DF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;GA8DrE;AAED,uEAAuE;AACvE,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,oBAatB"}
@@ -0,0 +1,314 @@
1
+ /**
2
+ * C4: Minimal parent portal — link parents to students and expose
3
+ * read-only attendance / grades stubs for linked children.
4
+ */
5
+
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e0a84844-f117-5700-89b0-1d460b45e236")}catch(e){}}();
7
+ import { TRPCError } from "@trpc/server";
8
+ import { prisma } from "../lib/prisma.js";
9
+ import { findActiveSchoolAdminMembership, findActiveSchoolMembership, findSchoolMembership, } from "../models/school.js";
10
+ async function requireSchoolAdmin(schoolId, userId) {
11
+ const membership = await findActiveSchoolAdminMembership(schoolId, userId);
12
+ if (!membership) {
13
+ throw new TRPCError({
14
+ code: "FORBIDDEN",
15
+ message: "Only school admins can perform this action",
16
+ });
17
+ }
18
+ return membership;
19
+ }
20
+ async function requireParentMembership(schoolId, userId) {
21
+ const membership = await findActiveSchoolMembership(schoolId, userId);
22
+ if (!membership || membership.role !== "PARENT") {
23
+ throw new TRPCError({
24
+ code: "FORBIDDEN",
25
+ message: "Only parents can access this resource",
26
+ });
27
+ }
28
+ return membership;
29
+ }
30
+ export async function linkParentToStudent(userId, input) {
31
+ await requireSchoolAdmin(input.schoolId, userId);
32
+ const [parentMembership, studentMembership] = await Promise.all([
33
+ findSchoolMembership(input.schoolId, input.parentUserId),
34
+ findSchoolMembership(input.schoolId, input.studentUserId),
35
+ ]);
36
+ if (!parentMembership || parentMembership.status !== "ACTIVE") {
37
+ throw new TRPCError({
38
+ code: "BAD_REQUEST",
39
+ message: "Parent must be an active school member",
40
+ });
41
+ }
42
+ if (parentMembership.role !== "PARENT") {
43
+ throw new TRPCError({
44
+ code: "BAD_REQUEST",
45
+ message: "parentUserId must have the PARENT role",
46
+ });
47
+ }
48
+ if (!studentMembership ||
49
+ studentMembership.status !== "ACTIVE" ||
50
+ studentMembership.role !== "STUDENT") {
51
+ throw new TRPCError({
52
+ code: "BAD_REQUEST",
53
+ message: "studentUserId must be an active STUDENT member",
54
+ });
55
+ }
56
+ const link = await prisma.parentStudentLink.upsert({
57
+ where: {
58
+ schoolId_parentUserId_studentUserId: {
59
+ schoolId: input.schoolId,
60
+ parentUserId: input.parentUserId,
61
+ studentUserId: input.studentUserId,
62
+ },
63
+ },
64
+ update: {},
65
+ create: {
66
+ schoolId: input.schoolId,
67
+ parentUserId: input.parentUserId,
68
+ studentUserId: input.studentUserId,
69
+ },
70
+ });
71
+ return { link };
72
+ }
73
+ export async function unlinkParentFromStudent(userId, input) {
74
+ await requireSchoolAdmin(input.schoolId, userId);
75
+ await prisma.parentStudentLink.deleteMany({
76
+ where: {
77
+ schoolId: input.schoolId,
78
+ parentUserId: input.parentUserId,
79
+ studentUserId: input.studentUserId,
80
+ },
81
+ });
82
+ return { ok: true };
83
+ }
84
+ export async function listParentLinks(userId, input) {
85
+ await requireSchoolAdmin(input.schoolId, userId);
86
+ const links = await prisma.parentStudentLink.findMany({
87
+ where: {
88
+ schoolId: input.schoolId,
89
+ ...(input.parentUserId ? { parentUserId: input.parentUserId } : {}),
90
+ ...(input.studentUserId ? { studentUserId: input.studentUserId } : {}),
91
+ },
92
+ include: {
93
+ parent: {
94
+ select: {
95
+ id: true,
96
+ username: true,
97
+ email: true,
98
+ profile: { select: { displayName: true } },
99
+ },
100
+ },
101
+ student: {
102
+ select: {
103
+ id: true,
104
+ username: true,
105
+ email: true,
106
+ profile: { select: { displayName: true } },
107
+ },
108
+ },
109
+ },
110
+ orderBy: { createdAt: "desc" },
111
+ });
112
+ return { links };
113
+ }
114
+ export async function listMyChildren(userId, schoolId) {
115
+ await requireParentMembership(schoolId, userId);
116
+ const links = await prisma.parentStudentLink.findMany({
117
+ where: { schoolId, parentUserId: userId },
118
+ include: {
119
+ student: {
120
+ select: {
121
+ id: true,
122
+ username: true,
123
+ email: true,
124
+ profile: {
125
+ select: {
126
+ displayName: true,
127
+ profilePicture: true,
128
+ profilePictureThumbnail: true,
129
+ },
130
+ },
131
+ studentIn: {
132
+ where: { schoolId },
133
+ select: {
134
+ id: true,
135
+ name: true,
136
+ subject: true,
137
+ section: true,
138
+ color: true,
139
+ },
140
+ },
141
+ },
142
+ },
143
+ },
144
+ orderBy: { createdAt: "asc" },
145
+ });
146
+ return {
147
+ children: links.map((link) => ({
148
+ linkId: link.id,
149
+ student: {
150
+ id: link.student.id,
151
+ username: link.student.username,
152
+ email: link.student.email,
153
+ displayName: link.student.profile?.displayName ?? null,
154
+ profilePicture: link.student.profile?.profilePicture ?? null,
155
+ profilePictureThumbnail: link.student.profile?.profilePictureThumbnail ?? null,
156
+ classes: link.student.studentIn,
157
+ },
158
+ })),
159
+ };
160
+ }
161
+ async function assertParentOfStudent(schoolId, parentUserId, studentUserId) {
162
+ await requireParentMembership(schoolId, parentUserId);
163
+ const link = await prisma.parentStudentLink.findUnique({
164
+ where: {
165
+ schoolId_parentUserId_studentUserId: {
166
+ schoolId,
167
+ parentUserId,
168
+ studentUserId,
169
+ },
170
+ },
171
+ });
172
+ if (!link) {
173
+ throw new TRPCError({
174
+ code: "FORBIDDEN",
175
+ message: "You are not linked to this student",
176
+ });
177
+ }
178
+ return link;
179
+ }
180
+ /** Read-only attendance view for a linked child (reads AttendanceRecord). */
181
+ export async function getChildAttendance(userId, input) {
182
+ await assertParentOfStudent(input.schoolId, userId, input.studentUserId);
183
+ const classes = await prisma.class.findMany({
184
+ where: {
185
+ schoolId: input.schoolId,
186
+ students: { some: { id: input.studentUserId } },
187
+ ...(input.classId ? { id: input.classId } : {}),
188
+ },
189
+ select: { id: true, name: true, subject: true, section: true },
190
+ });
191
+ const classIds = classes.map((c) => c.id);
192
+ const attendanceRecords = await prisma.attendanceRecord.findMany({
193
+ where: {
194
+ studentId: input.studentUserId,
195
+ attendance: {
196
+ classId: { in: classIds },
197
+ ...(input.from || input.to
198
+ ? {
199
+ date: {
200
+ ...(input.from ? { gte: input.from } : {}),
201
+ ...(input.to ? { lte: input.to } : {}),
202
+ },
203
+ }
204
+ : {}),
205
+ },
206
+ },
207
+ select: {
208
+ status: true,
209
+ attendance: { select: { id: true, date: true, classId: true } },
210
+ },
211
+ orderBy: { attendance: { date: "desc" } },
212
+ take: 200,
213
+ });
214
+ const classMap = new Map(classes.map((c) => [c.id, c]));
215
+ const records = attendanceRecords.map((row) => ({
216
+ attendanceId: row.attendance.id,
217
+ date: row.attendance.date,
218
+ classId: row.attendance.classId,
219
+ class: classMap.get(row.attendance.classId) ?? null,
220
+ status: row.status,
221
+ }));
222
+ const summary = records.reduce((acc, r) => {
223
+ acc.total += 1;
224
+ if (r.status === "PRESENT")
225
+ acc.present += 1;
226
+ else if (r.status === "LATE")
227
+ acc.late += 1;
228
+ else if (r.status === "EXCUSED")
229
+ acc.excused += 1;
230
+ else
231
+ acc.absent += 1;
232
+ return acc;
233
+ }, { present: 0, late: 0, absent: 0, excused: 0, total: 0 });
234
+ return { summary, records };
235
+ }
236
+ /**
237
+ * Read-only grades stub — returns assignment score rows for the linked child.
238
+ */
239
+ export async function getChildGrades(userId, input) {
240
+ await assertParentOfStudent(input.schoolId, userId, input.studentUserId);
241
+ const classes = await prisma.class.findMany({
242
+ where: {
243
+ schoolId: input.schoolId,
244
+ students: { some: { id: input.studentUserId } },
245
+ ...(input.classId ? { id: input.classId } : {}),
246
+ },
247
+ select: {
248
+ id: true,
249
+ name: true,
250
+ subject: true,
251
+ section: true,
252
+ assignments: {
253
+ select: {
254
+ id: true,
255
+ title: true,
256
+ type: true,
257
+ dueDate: true,
258
+ maxGrade: true,
259
+ submissions: {
260
+ where: { studentId: input.studentUserId },
261
+ select: {
262
+ id: true,
263
+ gradeReceived: true,
264
+ returned: true,
265
+ submittedAt: true,
266
+ },
267
+ take: 1,
268
+ },
269
+ },
270
+ orderBy: { dueDate: "desc" },
271
+ take: 100,
272
+ },
273
+ },
274
+ });
275
+ return {
276
+ classes: classes.map((cls) => ({
277
+ id: cls.id,
278
+ name: cls.name,
279
+ subject: cls.subject,
280
+ section: cls.section,
281
+ assignments: cls.assignments.map((a) => ({
282
+ id: a.id,
283
+ title: a.title,
284
+ type: a.type,
285
+ dueDate: a.dueDate,
286
+ maxGrade: a.maxGrade,
287
+ submission: a.submissions[0]
288
+ ? {
289
+ id: a.submissions[0].id,
290
+ gradeReceived: a.submissions[0].gradeReceived,
291
+ returned: a.submissions[0].returned,
292
+ submittedAt: a.submissions[0].submittedAt,
293
+ }
294
+ : null,
295
+ })),
296
+ })),
297
+ };
298
+ }
299
+ /** Helper for other services: is this user a parent of the student? */
300
+ export async function isParentOfStudent(schoolId, parentUserId, studentUserId) {
301
+ const link = await prisma.parentStudentLink.findUnique({
302
+ where: {
303
+ schoolId_parentUserId_studentUserId: {
304
+ schoolId,
305
+ parentUserId,
306
+ studentUserId,
307
+ },
308
+ },
309
+ select: { id: true },
310
+ });
311
+ return Boolean(link);
312
+ }
313
+ //# sourceMappingURL=parentPortal.js.map
314
+ //# debugId=e0a84844-f117-5700-89b0-1d460b45e236
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parentPortal.js","sources":["services/parentPortal.ts"],"sourceRoot":"/","sourcesContent":["/**\n * C4: Minimal parent portal — link parents to students and expose\n * read-only attendance / grades stubs for linked children.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n findActiveSchoolAdminMembership,\n findActiveSchoolMembership,\n findSchoolMembership,\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 requireParentMembership(schoolId: string, userId: string) {\n const membership = await findActiveSchoolMembership(schoolId, userId);\n if (!membership || membership.role !== \"PARENT\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only parents can access this resource\",\n });\n }\n return membership;\n}\n\nexport async function linkParentToStudent(\n userId: string,\n input: { schoolId: string; parentUserId: string; studentUserId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n\n const [parentMembership, studentMembership] = await Promise.all([\n findSchoolMembership(input.schoolId, input.parentUserId),\n findSchoolMembership(input.schoolId, input.studentUserId),\n ]);\n\n if (!parentMembership || parentMembership.status !== \"ACTIVE\") {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Parent must be an active school member\",\n });\n }\n if (parentMembership.role !== \"PARENT\") {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"parentUserId must have the PARENT role\",\n });\n }\n if (\n !studentMembership ||\n studentMembership.status !== \"ACTIVE\" ||\n studentMembership.role !== \"STUDENT\"\n ) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"studentUserId must be an active STUDENT member\",\n });\n }\n\n const link = await prisma.parentStudentLink.upsert({\n where: {\n schoolId_parentUserId_studentUserId: {\n schoolId: input.schoolId,\n parentUserId: input.parentUserId,\n studentUserId: input.studentUserId,\n },\n },\n update: {},\n create: {\n schoolId: input.schoolId,\n parentUserId: input.parentUserId,\n studentUserId: input.studentUserId,\n },\n });\n\n return { link };\n}\n\nexport async function unlinkParentFromStudent(\n userId: string,\n input: { schoolId: string; parentUserId: string; studentUserId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n await prisma.parentStudentLink.deleteMany({\n where: {\n schoolId: input.schoolId,\n parentUserId: input.parentUserId,\n studentUserId: input.studentUserId,\n },\n });\n return { ok: true as const };\n}\n\nexport async function listParentLinks(\n userId: string,\n input: { schoolId: string; parentUserId?: string; studentUserId?: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const links = await prisma.parentStudentLink.findMany({\n where: {\n schoolId: input.schoolId,\n ...(input.parentUserId ? { parentUserId: input.parentUserId } : {}),\n ...(input.studentUserId ? { studentUserId: input.studentUserId } : {}),\n },\n include: {\n parent: {\n select: {\n id: true,\n username: true,\n email: true,\n profile: { select: { displayName: true } },\n },\n },\n student: {\n select: {\n id: true,\n username: true,\n email: true,\n profile: { select: { displayName: true } },\n },\n },\n },\n orderBy: { createdAt: \"desc\" },\n });\n return { links };\n}\n\nexport async function listMyChildren(userId: string, schoolId: string) {\n await requireParentMembership(schoolId, userId);\n const links = await prisma.parentStudentLink.findMany({\n where: { schoolId, parentUserId: userId },\n include: {\n student: {\n select: {\n id: true,\n username: true,\n email: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n studentIn: {\n where: { schoolId },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n },\n },\n },\n },\n },\n orderBy: { createdAt: \"asc\" },\n });\n\n return {\n children: links.map((link) => ({\n linkId: link.id,\n student: {\n id: link.student.id,\n username: link.student.username,\n email: link.student.email,\n displayName: link.student.profile?.displayName ?? null,\n profilePicture: link.student.profile?.profilePicture ?? null,\n profilePictureThumbnail:\n link.student.profile?.profilePictureThumbnail ?? null,\n classes: link.student.studentIn,\n },\n })),\n };\n}\n\nasync function assertParentOfStudent(\n schoolId: string,\n parentUserId: string,\n studentUserId: string\n) {\n await requireParentMembership(schoolId, parentUserId);\n const link = await prisma.parentStudentLink.findUnique({\n where: {\n schoolId_parentUserId_studentUserId: {\n schoolId,\n parentUserId,\n studentUserId,\n },\n },\n });\n if (!link) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not linked to this student\",\n });\n }\n return link;\n}\n\n/** Read-only attendance view for a linked child (reads AttendanceRecord). */\nexport async function getChildAttendance(\n userId: string,\n input: {\n schoolId: string;\n studentUserId: string;\n classId?: string;\n from?: Date;\n to?: Date;\n }\n) {\n await assertParentOfStudent(input.schoolId, userId, input.studentUserId);\n\n const classes = await prisma.class.findMany({\n where: {\n schoolId: input.schoolId,\n students: { some: { id: input.studentUserId } },\n ...(input.classId ? { id: input.classId } : {}),\n },\n select: { id: true, name: true, subject: true, section: true },\n });\n const classIds = classes.map((c) => c.id);\n\n const attendanceRecords = await prisma.attendanceRecord.findMany({\n where: {\n studentId: input.studentUserId,\n attendance: {\n classId: { in: classIds },\n ...(input.from || input.to\n ? {\n date: {\n ...(input.from ? { gte: input.from } : {}),\n ...(input.to ? { lte: input.to } : {}),\n },\n }\n : {}),\n },\n },\n select: {\n status: true,\n attendance: { select: { id: true, date: true, classId: true } },\n },\n orderBy: { attendance: { date: \"desc\" } },\n take: 200,\n });\n\n const classMap = new Map(classes.map((c) => [c.id, c]));\n const records = attendanceRecords.map((row) => ({\n attendanceId: row.attendance.id,\n date: row.attendance.date,\n classId: row.attendance.classId,\n class: classMap.get(row.attendance.classId) ?? null,\n status: row.status,\n }));\n\n const summary = records.reduce(\n (acc, r) => {\n acc.total += 1;\n if (r.status === \"PRESENT\") acc.present += 1;\n else if (r.status === \"LATE\") acc.late += 1;\n else if (r.status === \"EXCUSED\") acc.excused += 1;\n else acc.absent += 1;\n return acc;\n },\n { present: 0, late: 0, absent: 0, excused: 0, total: 0 }\n );\n\n return { summary, records };\n}\n\n/**\n * Read-only grades stub — returns assignment score rows for the linked child.\n */\nexport async function getChildGrades(\n userId: string,\n input: { schoolId: string; studentUserId: string; classId?: string }\n) {\n await assertParentOfStudent(input.schoolId, userId, input.studentUserId);\n\n const classes = await prisma.class.findMany({\n where: {\n schoolId: input.schoolId,\n students: { some: { id: input.studentUserId } },\n ...(input.classId ? { id: input.classId } : {}),\n },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n assignments: {\n select: {\n id: true,\n title: true,\n type: true,\n dueDate: true,\n maxGrade: true,\n submissions: {\n where: { studentId: input.studentUserId },\n select: {\n id: true,\n gradeReceived: true,\n returned: true,\n submittedAt: true,\n },\n take: 1,\n },\n },\n orderBy: { dueDate: \"desc\" },\n take: 100,\n },\n },\n });\n\n return {\n classes: classes.map((cls) => ({\n id: cls.id,\n name: cls.name,\n subject: cls.subject,\n section: cls.section,\n assignments: cls.assignments.map((a) => ({\n id: a.id,\n title: a.title,\n type: a.type,\n dueDate: a.dueDate,\n maxGrade: a.maxGrade,\n submission: a.submissions[0]\n ? {\n id: a.submissions[0].id,\n gradeReceived: a.submissions[0].gradeReceived,\n returned: a.submissions[0].returned,\n submittedAt: a.submissions[0].submittedAt,\n }\n : null,\n })),\n })),\n };\n}\n\n/** Helper for other services: is this user a parent of the student? */\nexport async function isParentOfStudent(\n schoolId: string,\n parentUserId: string,\n studentUserId: string\n) {\n const link = await prisma.parentStudentLink.findUnique({\n where: {\n schoolId_parentUserId_studentUserId: {\n schoolId,\n parentUserId,\n studentUserId,\n },\n },\n select: { id: true },\n });\n return Boolean(link);\n}\n"],"names":[],"mappings":"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","debug_id":"e0a84844-f117-5700-89b0-1d460b45e236"}
@@ -11,6 +11,9 @@ export declare function getCurrentSchool(userId: string, schoolId?: string): Pro
11
11
  logoId: string | null;
12
12
  logoPath: string | null;
13
13
  logoType: string | null;
14
+ letterheadFileId: string | null;
15
+ letterheadPath: string | null;
16
+ letterheadType: string | null;
14
17
  } | null;
15
18
  activeRole: import(".prisma/client").$Enums.SchoolMembershipRole | null;
16
19
  memberships: ({
@@ -21,12 +24,19 @@ export declare function getCurrentSchool(userId: string, schoolId?: string): Pro
21
24
  name: string;
22
25
  description: string | null;
23
26
  logoId: string | null;
27
+ letterheadFileId: string | null;
24
28
  subdomain: string | null;
25
29
  logo: {
26
30
  path: string;
27
31
  type: string;
28
32
  id: string;
29
33
  } | null;
34
+ letterhead: {
35
+ path: string;
36
+ type: string;
37
+ id: string;
38
+ name: string;
39
+ } | null;
30
40
  };
31
41
  } & {
32
42
  status: import(".prisma/client").$Enums.SchoolMembershipStatus;
@@ -61,6 +71,7 @@ export declare function createSchoolRecord(userId: string, input: {
61
71
  name: string;
62
72
  description: string | null;
63
73
  logoId: string | null;
74
+ letterheadFileId: string | null;
64
75
  subdomain: string | null;
65
76
  };
66
77
  }>;
@@ -70,12 +81,14 @@ export declare function updateSchoolDetails(userId: string, input: {
70
81
  description?: string | null;
71
82
  subdomain?: string | null;
72
83
  logoId?: string | null;
84
+ letterheadFileId?: string | null;
73
85
  }): Promise<{
74
86
  school: {
75
87
  id: string;
76
88
  name: string;
77
89
  description: string | null;
78
90
  logoId: string | null;
91
+ letterheadFileId: string | null;
79
92
  subdomain: string | null;
80
93
  };
81
94
  }>;
@@ -88,6 +101,8 @@ export declare function getAdminOverview(userId: string, schoolId: string): Prom
88
101
  ADMIN: number;
89
102
  TEACHER: number;
90
103
  STUDENT: number;
104
+ ACCOUNTANT: number;
105
+ PARENT: number;
91
106
  classes: number;
92
107
  members: number;
93
108
  pendingInvites: number;
@@ -161,6 +176,7 @@ export declare function listClasses(userId: string, schoolId: string): Promise<{
161
176
  discussionsEnabled: boolean;
162
177
  studentsCanCreateDiscussions: boolean;
163
178
  studentsCanReply: boolean;
179
+ sidebarFeatures: import("@prisma/client/runtime/library.js").JsonValue | null;
164
180
  treatMissingAsZero: boolean;
165
181
  gradeRoundingMode: import(".prisma/client").$Enums.GradeRoundingMode;
166
182
  })[];
@@ -210,6 +226,7 @@ export declare function createSchoolClass(userId: string, input: {
210
226
  discussionsEnabled: boolean;
211
227
  studentsCanCreateDiscussions: boolean;
212
228
  studentsCanReply: boolean;
229
+ sidebarFeatures: import("@prisma/client/runtime/library.js").JsonValue | null;
213
230
  treatMissingAsZero: boolean;
214
231
  gradeRoundingMode: import(".prisma/client").$Enums.GradeRoundingMode;
215
232
  };
@@ -384,7 +401,7 @@ export declare function confirmSchoolAssetUpload(userId: string, fileId: string,
384
401
  */
385
402
  export declare function listMemberIds(userId: string, schoolId: string): Promise<string[]>;
386
403
  /** Search active institution members by username, displayName, or email. */
387
- export declare function searchMembers(userId: string, schoolId: string, query: string, limit?: number, role?: "ADMIN" | "TEACHER" | "STUDENT"): Promise<{
404
+ export declare function searchMembers(userId: string, schoolId: string, query: string, limit?: number, role?: SchoolMembershipRole): Promise<{
388
405
  id: string;
389
406
  username: string;
390
407
  profile: {
@@ -454,13 +471,17 @@ export declare function getStudentTimeline(userId: string, input: {
454
471
  }[];
455
472
  attendance: {
456
473
  id: string;
474
+ recordId: string;
457
475
  date: Date;
458
476
  class: {
459
477
  id: string;
460
478
  name: string;
461
479
  color: string | null;
462
480
  };
463
- status: "PRESENT" | "LATE" | "ABSENT";
481
+ status: import(".prisma/client").$Enums.AttendanceStatus;
482
+ note: string | null;
483
+ lockedFromExcusal: boolean;
484
+ reason: string | null;
464
485
  }[];
465
486
  }>;
466
487
  /**
@@ -531,13 +552,17 @@ export declare function getMemberTimeline(userId: string, input: {
531
552
  }[];
532
553
  attendance: {
533
554
  id: string;
555
+ recordId: string;
534
556
  date: Date;
535
557
  class: {
536
558
  id: string;
537
559
  name: string;
538
560
  color: string | null;
539
561
  };
540
- status: "PRESENT" | "LATE" | "ABSENT";
562
+ status: import(".prisma/client").$Enums.AttendanceStatus;
563
+ note: string | null;
564
+ lockedFromExcusal: boolean;
565
+ reason: string | null;
541
566
  }[];
542
567
  classes?: undefined;
543
568
  } | {