@studious-lms/server 1.13.0 → 1.14.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 (306) hide show
  1. package/.dockerignore +28 -0
  2. package/.env.example +5 -0
  3. package/.github/workflows/deploy.yml +123 -0
  4. package/Dockerfile +54 -0
  5. package/dist/index.js +5 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/config/cors.d.ts.map +1 -1
  8. package/dist/lib/config/cors.js +31 -11
  9. package/dist/lib/config/cors.js.map +1 -1
  10. package/dist/lib/config/env.d.ts +17 -0
  11. package/dist/lib/config/env.d.ts.map +1 -1
  12. package/dist/lib/config/env.js +8 -2
  13. package/dist/lib/config/env.js.map +1 -1
  14. package/dist/lib/googleCloudStorage.d.ts +10 -0
  15. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  16. package/dist/lib/googleCloudStorage.js +22 -2
  17. package/dist/lib/googleCloudStorage.js.map +1 -1
  18. package/dist/lib/prisma.d.ts +104 -5
  19. package/dist/lib/prisma.d.ts.map +1 -1
  20. package/dist/lib/prisma.js +33 -2
  21. package/dist/lib/prisma.js.map +1 -1
  22. package/dist/models/agenda.d.ts +20 -20
  23. package/dist/models/announcement.d.ts +177 -69
  24. package/dist/models/announcement.d.ts.map +1 -1
  25. package/dist/models/assignment.d.ts +551 -353
  26. package/dist/models/assignment.d.ts.map +1 -1
  27. package/dist/models/attendance.d.ts +197 -71
  28. package/dist/models/attendance.d.ts.map +1 -1
  29. package/dist/models/auth.d.ts +348 -114
  30. package/dist/models/auth.d.ts.map +1 -1
  31. package/dist/models/class.d.ts +324 -198
  32. package/dist/models/class.d.ts.map +1 -1
  33. package/dist/models/class.js +2 -2
  34. package/dist/models/class.js.map +1 -1
  35. package/dist/models/cloudStorage.d.ts +146 -56
  36. package/dist/models/cloudStorage.d.ts.map +1 -1
  37. package/dist/models/comment.d.ts +267 -115
  38. package/dist/models/comment.d.ts.map +1 -1
  39. package/dist/models/conversation.d.ts +199 -73
  40. package/dist/models/conversation.d.ts.map +1 -1
  41. package/dist/models/department.d.ts +196 -70
  42. package/dist/models/department.d.ts.map +1 -1
  43. package/dist/models/event.d.ts +287 -125
  44. package/dist/models/event.d.ts.map +1 -1
  45. package/dist/models/file.d.ts +389 -263
  46. package/dist/models/file.d.ts.map +1 -1
  47. package/dist/models/folder.d.ts +433 -181
  48. package/dist/models/folder.d.ts.map +1 -1
  49. package/dist/models/labChat.d.ts +209 -83
  50. package/dist/models/labChat.d.ts.map +1 -1
  51. package/dist/models/marketing.d.ts +80 -26
  52. package/dist/models/marketing.d.ts.map +1 -1
  53. package/dist/models/message.d.ts +95 -41
  54. package/dist/models/message.d.ts.map +1 -1
  55. package/dist/models/newtonChat.d.ts +83 -29
  56. package/dist/models/newtonChat.d.ts.map +1 -1
  57. package/dist/models/notification.d.ts +61 -25
  58. package/dist/models/notification.d.ts.map +1 -1
  59. package/dist/models/school.d.ts +349 -133
  60. package/dist/models/school.d.ts.map +1 -1
  61. package/dist/models/section.d.ts +216 -72
  62. package/dist/models/section.d.ts.map +1 -1
  63. package/dist/models/tag.d.ts +156 -48
  64. package/dist/models/tag.d.ts.map +1 -1
  65. package/dist/models/user.d.ts +82 -28
  66. package/dist/models/user.d.ts.map +1 -1
  67. package/dist/models/worksheet.d.ts +1020 -411
  68. package/dist/models/worksheet.d.ts.map +1 -1
  69. package/dist/models/worksheet.js +8 -2
  70. package/dist/models/worksheet.js.map +1 -1
  71. package/dist/pipelines/aiLabChat.d.ts +0 -8
  72. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  73. package/dist/pipelines/aiLabChat.js +50 -67
  74. package/dist/pipelines/aiLabChat.js.map +1 -1
  75. package/dist/pipelines/aiLabChatContract.d.ts +393 -388
  76. package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
  77. package/dist/pipelines/aiLabChatContract.js +63 -69
  78. package/dist/pipelines/aiLabChatContract.js.map +1 -1
  79. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  80. package/dist/pipelines/aiNewtonChat.js +41 -41
  81. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  82. package/dist/pipelines/gradeWorksheet.d.ts +8 -8
  83. package/dist/pipelines/labChatPrompt.d.ts +10 -8
  84. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  85. package/dist/pipelines/labChatPrompt.js +47 -67
  86. package/dist/pipelines/labChatPrompt.js.map +1 -1
  87. package/dist/pipelines/pdfToWorksheet.d.ts +2 -2
  88. package/dist/routers/_app.d.ts +2203 -1957
  89. package/dist/routers/_app.d.ts.map +1 -1
  90. package/dist/routers/_app.js +4 -2
  91. package/dist/routers/_app.js.map +1 -1
  92. package/dist/routers/admin.d.ts +13 -13
  93. package/dist/routers/agenda.d.ts +18 -18
  94. package/dist/routers/announcement.d.ts +32 -32
  95. package/dist/routers/assignment.d.ts +335 -335
  96. package/dist/routers/attendance.d.ts +10 -10
  97. package/dist/routers/auth.d.ts +2 -2
  98. package/dist/routers/class.d.ts +147 -147
  99. package/dist/routers/comment.d.ts +22 -22
  100. package/dist/routers/conversation.d.ts +17 -17
  101. package/dist/routers/department.d.ts +34 -34
  102. package/dist/routers/discussion.d.ts +65 -53
  103. package/dist/routers/discussion.d.ts.map +1 -1
  104. package/dist/routers/discussion.js +6 -2
  105. package/dist/routers/discussion.js.map +1 -1
  106. package/dist/routers/embedding.d.ts +69 -0
  107. package/dist/routers/embedding.d.ts.map +1 -0
  108. package/dist/routers/embedding.js +143 -0
  109. package/dist/routers/embedding.js.map +1 -0
  110. package/dist/routers/event.d.ts +39 -39
  111. package/dist/routers/file.d.ts +12 -12
  112. package/dist/routers/folder.d.ts +38 -38
  113. package/dist/routers/labChat.d.ts +19 -19
  114. package/dist/routers/marketing.d.ts +2 -2
  115. package/dist/routers/message.d.ts +13 -13
  116. package/dist/routers/newtonChat.d.ts +4 -4
  117. package/dist/routers/notifications.d.ts +9 -9
  118. package/dist/routers/permissions.d.ts +14 -14
  119. package/dist/routers/school.d.ts +55 -55
  120. package/dist/routers/search.d.ts +22 -1
  121. package/dist/routers/search.d.ts.map +1 -1
  122. package/dist/routers/search.js +9 -3
  123. package/dist/routers/search.js.map +1 -1
  124. package/dist/routers/section.d.ts +4 -4
  125. package/dist/routers/studentProgress.d.ts +1 -1
  126. package/dist/routers/tag.d.ts +7 -7
  127. package/dist/routers/user.d.ts +10 -10
  128. package/dist/routers/worksheet.d.ts +95 -57
  129. package/dist/routers/worksheet.d.ts.map +1 -1
  130. package/dist/routers/worksheet.js +26 -2
  131. package/dist/routers/worksheet.js.map +1 -1
  132. package/dist/routes/aiStream.d.ts +2 -0
  133. package/dist/routes/aiStream.d.ts.map +1 -0
  134. package/dist/routes/aiStream.js +696 -0
  135. package/dist/routes/aiStream.js.map +1 -0
  136. package/dist/seedDatabase.d.ts +2 -2
  137. package/dist/seedDatabase.js +6 -6
  138. package/dist/seedDatabase.js.map +1 -1
  139. package/dist/services/adminInstitution.d.ts +8 -8
  140. package/dist/services/adminRunJob.d.ts +4 -4
  141. package/dist/services/agenda.d.ts +18 -18
  142. package/dist/services/aiChatStream.d.ts +68 -0
  143. package/dist/services/aiChatStream.d.ts.map +1 -0
  144. package/dist/services/aiChatStream.js +157 -0
  145. package/dist/services/aiChatStream.js.map +1 -0
  146. package/dist/services/aiModifierTools.d.ts +32 -0
  147. package/dist/services/aiModifierTools.d.ts.map +1 -0
  148. package/dist/services/aiModifierTools.js +165 -0
  149. package/dist/services/aiModifierTools.js.map +1 -0
  150. package/dist/services/aiProposalTools.d.ts +32 -0
  151. package/dist/services/aiProposalTools.d.ts.map +1 -0
  152. package/dist/services/aiProposalTools.js +207 -0
  153. package/dist/services/aiProposalTools.js.map +1 -0
  154. package/dist/services/aiStream.d.ts +22 -0
  155. package/dist/services/aiStream.d.ts.map +1 -0
  156. package/dist/services/aiStream.js +142 -0
  157. package/dist/services/aiStream.js.map +1 -0
  158. package/dist/services/aiTools.d.ts +16 -0
  159. package/dist/services/aiTools.d.ts.map +1 -0
  160. package/dist/services/aiTools.js +271 -0
  161. package/dist/services/aiTools.js.map +1 -0
  162. package/dist/services/announcement.d.ts +12 -12
  163. package/dist/services/announcement.d.ts.map +1 -1
  164. package/dist/services/announcement.js +7 -2
  165. package/dist/services/announcement.js.map +1 -1
  166. package/dist/services/assignment.d.ts +293 -293
  167. package/dist/services/assignment.d.ts.map +1 -1
  168. package/dist/services/assignment.js +28 -9
  169. package/dist/services/assignment.js.map +1 -1
  170. package/dist/services/attendance.d.ts +10 -10
  171. package/dist/services/auth.d.ts +2 -2
  172. package/dist/services/class.d.ts +138 -138
  173. package/dist/services/class.d.ts.map +1 -1
  174. package/dist/services/class.js +19 -3
  175. package/dist/services/class.js.map +1 -1
  176. package/dist/services/comment.d.ts +22 -22
  177. package/dist/services/conversation.d.ts +17 -17
  178. package/dist/services/department.d.ts +33 -33
  179. package/dist/services/discussion.d.ts +58 -58
  180. package/dist/services/discussion.d.ts.map +1 -1
  181. package/dist/services/discussion.js +8 -8
  182. package/dist/services/discussion.js.map +1 -1
  183. package/dist/services/discussionSummary.d.ts +21 -0
  184. package/dist/services/discussionSummary.d.ts.map +1 -0
  185. package/dist/services/discussionSummary.js +179 -0
  186. package/dist/services/discussionSummary.js.map +1 -0
  187. package/dist/services/embedding.d.ts +67 -0
  188. package/dist/services/embedding.d.ts.map +1 -0
  189. package/dist/services/embedding.js +226 -0
  190. package/dist/services/embedding.js.map +1 -0
  191. package/dist/services/event.d.ts +37 -37
  192. package/dist/services/file.d.ts +24 -24
  193. package/dist/services/folder.d.ts +36 -36
  194. package/dist/services/labChat.d.ts +19 -19
  195. package/dist/services/marketing.d.ts +2 -2
  196. package/dist/services/message.d.ts +11 -11
  197. package/dist/services/message.d.ts.map +1 -1
  198. package/dist/services/message.js +2 -2
  199. package/dist/services/message.js.map +1 -1
  200. package/dist/services/newtonChat.d.ts +4 -4
  201. package/dist/services/notification.d.ts +9 -9
  202. package/dist/services/notification.d.ts.map +1 -1
  203. package/dist/services/permissions.d.ts +14 -14
  204. package/dist/services/school.d.ts +50 -50
  205. package/dist/services/search.d.ts +12 -0
  206. package/dist/services/search.d.ts.map +1 -1
  207. package/dist/services/search.js +120 -2
  208. package/dist/services/search.js.map +1 -1
  209. package/dist/services/section.d.ts +3 -3
  210. package/dist/services/tag.d.ts +4 -4
  211. package/dist/services/user.d.ts +4 -4
  212. package/dist/services/user.d.ts.map +1 -1
  213. package/dist/services/user.js +10 -15
  214. package/dist/services/user.js.map +1 -1
  215. package/dist/services/worksheet.d.ts +60 -48
  216. package/dist/services/worksheet.d.ts.map +1 -1
  217. package/dist/services/worksheet.js +11 -4
  218. package/dist/services/worksheet.js.map +1 -1
  219. package/dist/utils/streamBroadcast.d.ts +11 -0
  220. package/dist/utils/streamBroadcast.d.ts.map +1 -0
  221. package/dist/utils/streamBroadcast.js +32 -0
  222. package/dist/utils/streamBroadcast.js.map +1 -0
  223. package/dist/workers/connection.d.ts +15 -0
  224. package/dist/workers/connection.d.ts.map +1 -1
  225. package/dist/workers/connection.js +20 -2
  226. package/dist/workers/connection.js.map +1 -1
  227. package/dist/workers/index.js +4 -2
  228. package/dist/workers/index.js.map +1 -1
  229. package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -1
  230. package/dist/workers/processors/createRunJobWorker.js +31 -8
  231. package/dist/workers/processors/createRunJobWorker.js.map +1 -1
  232. package/dist/workers/processors/embedContent.d.ts +8 -0
  233. package/dist/workers/processors/embedContent.d.ts.map +1 -0
  234. package/dist/workers/processors/embedContent.js +76 -0
  235. package/dist/workers/processors/embedContent.js.map +1 -0
  236. package/dist/workers/queues.d.ts +6 -0
  237. package/dist/workers/queues.d.ts.map +1 -1
  238. package/dist/workers/queues.js +5 -3
  239. package/dist/workers/queues.js.map +1 -1
  240. package/package.json +5 -1
  241. package/prisma/migrations/20260609215135_init/migration.sql +1412 -0
  242. package/prisma/migrations/20260610130000_add_worksheet_tags/migration.sql +13 -0
  243. package/prisma/schema.prisma +38 -7
  244. package/src/index.ts +4 -0
  245. package/src/lib/config/cors.ts +32 -9
  246. package/src/lib/config/env.ts +6 -0
  247. package/src/lib/googleCloudStorage.ts +21 -0
  248. package/src/lib/prisma.ts +40 -1
  249. package/src/models/class.ts +3 -2
  250. package/src/models/worksheet.ts +5 -2
  251. package/src/pipelines/aiLabChat.ts +54 -79
  252. package/src/pipelines/aiLabChatContract.ts +69 -69
  253. package/src/pipelines/aiNewtonChat.ts +46 -43
  254. package/src/pipelines/labChatPrompt.ts +63 -88
  255. package/src/routers/_app.ts +2 -0
  256. package/src/routers/discussion.ts +7 -0
  257. package/src/routers/embedding.ts +159 -0
  258. package/src/routers/search.ts +10 -1
  259. package/src/routers/worksheet.ts +27 -0
  260. package/src/routes/aiStream.ts +789 -0
  261. package/src/seedDatabase.ts +4 -4
  262. package/src/services/aiChatStream.ts +219 -0
  263. package/src/services/aiModifierTools.ts +188 -0
  264. package/src/services/aiProposalTools.ts +272 -0
  265. package/src/services/aiStream.ts +175 -0
  266. package/src/services/aiTools.ts +290 -0
  267. package/src/services/announcement.ts +7 -0
  268. package/src/services/assignment.ts +28 -7
  269. package/src/services/class.ts +20 -1
  270. package/src/services/discussion.ts +9 -8
  271. package/src/services/discussionSummary.ts +226 -0
  272. package/src/services/embedding.ts +285 -0
  273. package/src/services/message.ts +2 -2
  274. package/src/services/search.ts +138 -0
  275. package/src/services/user.ts +9 -17
  276. package/src/services/worksheet.ts +9 -2
  277. package/src/utils/streamBroadcast.ts +48 -0
  278. package/src/workers/connection.ts +19 -0
  279. package/src/workers/index.ts +2 -0
  280. package/src/workers/processors/createRunJobWorker.ts +32 -6
  281. package/src/workers/processors/embedContent.ts +90 -0
  282. package/src/workers/queues.ts +5 -1
  283. package/tests/unit/pipelines/aiLabChatContract.test.ts +38 -21
  284. package/tests/unit/pipelines/labChatPrompt.test.ts +23 -36
  285. package/prisma/migrations/20250807062924_init/migration.sql +0 -436
  286. package/prisma/migrations/20250919063704_init2/migration.sql +0 -24
  287. package/prisma/migrations/20250920143543_add_profile_fields/migration.sql +0 -15
  288. package/prisma/migrations/20250922165147_add_chat_models/migration.sql +0 -68
  289. package/prisma/migrations/20250922165314_add_chat_roles/migration.sql +0 -5
  290. package/prisma/migrations/20250922171242_refactor_notification_system/migration.sql +0 -20
  291. package/prisma/migrations/20250922172208_add_mentions_system/migration.sql +0 -21
  292. package/prisma/migrations/20250925072732_add_lab_chat_system/migration.sql +0 -25
  293. package/prisma/migrations/20251020151505_add_upload_tracking_fields/migration.sql +0 -57
  294. package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +0 -30
  295. package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +0 -35
  296. package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +0 -14
  297. package/prisma/migrations/20260415103500_add_session_type/migration.sql +0 -33
  298. package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +0 -19
  299. package/prisma/migrations/20260417083000_add_conversation_member_hidden_at/migration.sql +0 -2
  300. package/prisma/migrations/20260502230500_assignment_submission_comments/migration.sql +0 -60
  301. package/prisma/migrations/20260504071500_school_memberships/migration.sql +0 -81
  302. package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +0 -166
  303. package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +0 -184
  304. package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +0 -93
  305. package/prisma/migrations/20260510183000_add_discussions/migration.sql +0 -151
  306. /package/.github/workflows/{npm.yml → npm-publish.yml} +0 -0
@@ -0,0 +1,271 @@
1
+ /**
2
+ * AI Tool definitions and execution for agent tool calls.
3
+ * Provides RAG search, assignment lookup, worksheet creation, student progress,
4
+ * and web search tools.
5
+ */
6
+
7
+ !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]="08e8556c-8e72-5041-a040-17a623b35c7c")}catch(e){}}();
8
+ import { jsonSchema } from 'ai';
9
+ import OpenAI from 'openai';
10
+ import { searchEmbeddings } from './embedding.js';
11
+ import { prisma } from '../lib/prisma.js';
12
+ import { logger } from '../utils/logger.js';
13
+ /**
14
+ * Build the tool set for a given class context.
15
+ * Tools are scoped to a specific class for security.
16
+ */
17
+ export function aiTools(classId) {
18
+ return {
19
+ search_materials: {
20
+ description: 'Search class materials, files, assignments, worksheets, and announcements using semantic similarity. Use this to find relevant context for answering questions.',
21
+ inputSchema: jsonSchema({
22
+ type: 'object',
23
+ properties: {
24
+ query: { type: 'string', description: 'The search query to find relevant materials' },
25
+ limit: { type: 'number', description: 'Maximum results (1-10)', default: 5 },
26
+ },
27
+ required: ['query'],
28
+ }),
29
+ execute: async ({ query, limit = 5 }) => {
30
+ try {
31
+ const results = await searchEmbeddings({ classId, query, limit });
32
+ return results.map((r) => ({
33
+ sourceType: r.sourceType,
34
+ sourceId: r.sourceId,
35
+ content: r.content.slice(0, 500),
36
+ similarity: Math.round(r.similarity * 100) / 100,
37
+ metadata: r.metadata,
38
+ }));
39
+ }
40
+ catch (error) {
41
+ logger.error('search_materials tool failed', { error, classId, query });
42
+ return [];
43
+ }
44
+ },
45
+ },
46
+ search_assignments: {
47
+ description: 'Search existing assignments by title, instructions, or type. Returns assignment details including due dates and submission types.',
48
+ inputSchema: jsonSchema({
49
+ type: 'object',
50
+ properties: {
51
+ query: { type: 'string', description: 'Search query for assignments' },
52
+ limit: { type: 'number', description: 'Maximum results (1-10)', default: 5 },
53
+ },
54
+ required: ['query'],
55
+ }),
56
+ execute: async ({ query, limit = 5 }) => {
57
+ try {
58
+ const results = await searchEmbeddings({
59
+ classId,
60
+ query,
61
+ limit,
62
+ sourceTypes: ['ASSIGNMENT'],
63
+ });
64
+ const assignmentIds = results.map((r) => r.sourceId);
65
+ const assignments = await prisma.assignment.findMany({
66
+ where: { id: { in: assignmentIds } },
67
+ select: {
68
+ id: true,
69
+ title: true,
70
+ instructions: true,
71
+ type: true,
72
+ dueDate: true,
73
+ maxGrade: true,
74
+ section: { select: { name: true } },
75
+ },
76
+ });
77
+ return assignments.map((a) => ({
78
+ id: a.id,
79
+ title: a.title,
80
+ instructions: a.instructions?.slice(0, 200) ?? null,
81
+ type: a.type,
82
+ dueDate: a.dueDate.toISOString(),
83
+ maxGrade: a.maxGrade,
84
+ section: a.section?.name ?? null,
85
+ }));
86
+ }
87
+ catch (error) {
88
+ logger.error('search_assignments tool failed', { error, classId, query });
89
+ return [];
90
+ }
91
+ },
92
+ },
93
+ get_student_progress: {
94
+ description: 'Get progress summary for a specific student in this class, including grades and submission status.',
95
+ inputSchema: jsonSchema({
96
+ type: 'object',
97
+ properties: {
98
+ studentId: { type: 'string', description: 'The student user ID' },
99
+ },
100
+ required: ['studentId'],
101
+ }),
102
+ execute: async ({ studentId }) => {
103
+ try {
104
+ const submissions = await prisma.submission.findMany({
105
+ where: {
106
+ studentId,
107
+ assignment: { classId },
108
+ },
109
+ select: {
110
+ id: true,
111
+ gradeReceived: true,
112
+ submittedAt: true,
113
+ assignment: {
114
+ select: {
115
+ title: true,
116
+ maxGrade: true,
117
+ type: true,
118
+ },
119
+ },
120
+ },
121
+ orderBy: { submittedAt: 'desc' },
122
+ take: 20,
123
+ });
124
+ const totalAssignments = await prisma.assignment.count({
125
+ where: { classId },
126
+ });
127
+ const submitted = submissions.filter((s) => s.submittedAt);
128
+ const graded = submissions.filter((s) => s.gradeReceived !== null);
129
+ const avgGrade = graded.length > 0
130
+ ? graded.reduce((sum, s) => sum + (s.gradeReceived || 0), 0) / graded.length
131
+ : null;
132
+ return {
133
+ studentId,
134
+ totalAssignments,
135
+ submittedCount: submitted.length,
136
+ gradedCount: graded.length,
137
+ averageGrade: avgGrade ? Math.round(avgGrade * 10) / 10 : null,
138
+ recentSubmissions: submissions.slice(0, 5).map((s) => ({
139
+ assignment: s.assignment.title,
140
+ gradeReceived: s.gradeReceived,
141
+ maxGrade: s.assignment.maxGrade,
142
+ submittedAt: s.submittedAt?.toISOString() ?? null,
143
+ })),
144
+ };
145
+ }
146
+ catch (error) {
147
+ logger.error('get_student_progress tool failed', { error, classId, studentId });
148
+ return { error: 'Failed to fetch student progress' };
149
+ }
150
+ },
151
+ },
152
+ get_worksheet_details: {
153
+ description: 'Get full details of a worksheet including all questions, answers, and mark schemes.',
154
+ inputSchema: jsonSchema({
155
+ type: 'object',
156
+ properties: {
157
+ worksheetId: { type: 'string', description: 'The worksheet ID to look up' },
158
+ },
159
+ required: ['worksheetId'],
160
+ }),
161
+ execute: async ({ worksheetId }) => {
162
+ try {
163
+ const worksheet = await prisma.worksheet.findUnique({
164
+ where: { id: worksheetId, classId },
165
+ include: {
166
+ questions: {
167
+ orderBy: { order: 'asc' },
168
+ },
169
+ },
170
+ });
171
+ if (!worksheet) {
172
+ return { error: 'Worksheet not found' };
173
+ }
174
+ return {
175
+ id: worksheet.id,
176
+ name: worksheet.name,
177
+ questions: worksheet.questions.map((q) => ({
178
+ id: q.id,
179
+ type: q.type,
180
+ question: q.question,
181
+ answer: q.answer,
182
+ options: q.options,
183
+ points: q.points,
184
+ order: q.order,
185
+ })),
186
+ };
187
+ }
188
+ catch (error) {
189
+ logger.error('get_worksheet_details tool failed', { error, classId, worksheetId });
190
+ return { error: 'Failed to fetch worksheet' };
191
+ }
192
+ },
193
+ },
194
+ get_mark_scheme: {
195
+ description: 'Get a mark scheme (rubric) by ID, including criteria and point allocations.',
196
+ inputSchema: jsonSchema({
197
+ type: 'object',
198
+ properties: {
199
+ markSchemeId: { type: 'string', description: 'The mark scheme ID' },
200
+ },
201
+ required: ['markSchemeId'],
202
+ }),
203
+ execute: async ({ markSchemeId }) => {
204
+ try {
205
+ const markScheme = await prisma.markScheme.findUnique({
206
+ where: { id: markSchemeId },
207
+ select: { id: true, name: true, structured: true },
208
+ });
209
+ if (!markScheme) {
210
+ return { error: 'Mark scheme not found' };
211
+ }
212
+ let parsed = {};
213
+ try {
214
+ parsed = JSON.parse(markScheme.structured || '{}');
215
+ }
216
+ catch { /* noop */ }
217
+ return {
218
+ id: markScheme.id,
219
+ name: markScheme.name,
220
+ structured: parsed,
221
+ };
222
+ }
223
+ catch (error) {
224
+ logger.error('get_mark_scheme tool failed', { error, classId, markSchemeId });
225
+ return { error: 'Failed to fetch mark scheme' };
226
+ }
227
+ },
228
+ },
229
+ web_search: {
230
+ description: 'Search the web for current information on a topic. Use this when you need up-to-date facts, definitions, explanations, or context beyond the class materials.',
231
+ inputSchema: jsonSchema({
232
+ type: 'object',
233
+ properties: {
234
+ query: { type: 'string', description: 'The search query to look up on the web' },
235
+ },
236
+ required: ['query'],
237
+ }),
238
+ execute: async ({ query }) => {
239
+ try {
240
+ const client = new OpenAI();
241
+ const response = await client.responses.create({
242
+ model: 'gpt-4o-mini',
243
+ tools: [{ type: 'web_search_preview' }],
244
+ input: query,
245
+ });
246
+ const messageOutput = response.output.find((item) => item.type === 'message');
247
+ if (!messageOutput || messageOutput.type !== 'message') {
248
+ return { summary: 'No results found.', sources: [] };
249
+ }
250
+ const textContent = messageOutput.content.find((c) => c.type === 'output_text');
251
+ if (!textContent || textContent.type !== 'output_text') {
252
+ return { summary: 'No results found.', sources: [] };
253
+ }
254
+ const sources = textContent.annotations
255
+ .filter((a) => a.type === 'url_citation')
256
+ .map((a) => ({ url: a.url, title: a.title }));
257
+ return {
258
+ summary: textContent.text.slice(0, 2000),
259
+ sources: sources.slice(0, 5),
260
+ };
261
+ }
262
+ catch (error) {
263
+ logger.error('web_search tool failed', { error, query });
264
+ return { summary: 'Web search failed. Please try again.', sources: [] };
265
+ }
266
+ },
267
+ },
268
+ };
269
+ }
270
+ //# sourceMappingURL=aiTools.js.map
271
+ //# debugId=08e8556c-8e72-5041-a040-17a623b35c7c
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiTools.js","sources":["services/aiTools.ts"],"sourceRoot":"/","sourcesContent":["/**\n * AI Tool definitions and execution for agent tool calls.\n * Provides RAG search, assignment lookup, worksheet creation, student progress,\n * and web search tools.\n */\nimport { jsonSchema, type ToolSet } from 'ai';\nimport OpenAI from 'openai';\nimport { searchEmbeddings } from './embedding.js';\nimport { prisma } from '../lib/prisma.js';\nimport { logger } from '../utils/logger.js';\n\nexport interface ToolCallResult {\n toolName: string;\n result: unknown;\n}\n\n/**\n * Build the tool set for a given class context.\n * Tools are scoped to a specific class for security.\n */\nexport function aiTools(classId: string): ToolSet {\n return {\n search_materials: {\n description: 'Search class materials, files, assignments, worksheets, and announcements using semantic similarity. Use this to find relevant context for answering questions.',\n inputSchema: jsonSchema({\n type: 'object',\n properties: {\n query: { type: 'string', description: 'The search query to find relevant materials' },\n limit: { type: 'number', description: 'Maximum results (1-10)', default: 5 },\n },\n required: ['query'],\n }),\n execute: async ({ query, limit = 5 }: { query: string; limit?: number }) => {\n try {\n const results = await searchEmbeddings({ classId, query, limit });\n return results.map((r) => ({\n sourceType: r.sourceType,\n sourceId: r.sourceId,\n content: r.content.slice(0, 500),\n similarity: Math.round(r.similarity * 100) / 100,\n metadata: r.metadata,\n }));\n } catch (error) {\n logger.error('search_materials tool failed', { error, classId, query });\n return [];\n }\n },\n },\n\n search_assignments: {\n description: 'Search existing assignments by title, instructions, or type. Returns assignment details including due dates and submission types.',\n inputSchema: jsonSchema({\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query for assignments' },\n limit: { type: 'number', description: 'Maximum results (1-10)', default: 5 },\n },\n required: ['query'],\n }),\n execute: async ({ query, limit = 5 }: { query: string; limit?: number }) => {\n try {\n const results = await searchEmbeddings({\n classId,\n query,\n limit,\n sourceTypes: ['ASSIGNMENT'],\n });\n\n const assignmentIds = results.map((r) => r.sourceId);\n const assignments = await prisma.assignment.findMany({\n where: { id: { in: assignmentIds } },\n select: {\n id: true,\n title: true,\n instructions: true,\n type: true,\n dueDate: true,\n maxGrade: true,\n section: { select: { name: true } },\n },\n });\n\n return assignments.map((a) => ({\n id: a.id,\n title: a.title,\n instructions: a.instructions?.slice(0, 200) ?? null,\n type: a.type,\n dueDate: a.dueDate.toISOString(),\n maxGrade: a.maxGrade,\n section: a.section?.name ?? null,\n }));\n } catch (error) {\n logger.error('search_assignments tool failed', { error, classId, query });\n return [];\n }\n },\n },\n\n get_student_progress: {\n description: 'Get progress summary for a specific student in this class, including grades and submission status.',\n inputSchema: jsonSchema({\n type: 'object',\n properties: {\n studentId: { type: 'string', description: 'The student user ID' },\n },\n required: ['studentId'],\n }),\n execute: async ({ studentId }: { studentId: string }) => {\n try {\n const submissions = await prisma.submission.findMany({\n where: {\n studentId,\n assignment: { classId },\n },\n select: {\n id: true,\n gradeReceived: true,\n submittedAt: true,\n assignment: {\n select: {\n title: true,\n maxGrade: true,\n type: true,\n },\n },\n },\n orderBy: { submittedAt: 'desc' },\n take: 20,\n });\n\n const totalAssignments = await prisma.assignment.count({\n where: { classId },\n });\n\n const submitted = submissions.filter((s) => s.submittedAt);\n const graded = submissions.filter((s) => s.gradeReceived !== null);\n const avgGrade = graded.length > 0\n ? graded.reduce((sum, s) => sum + (s.gradeReceived || 0), 0) / graded.length\n : null;\n\n return {\n studentId,\n totalAssignments,\n submittedCount: submitted.length,\n gradedCount: graded.length,\n averageGrade: avgGrade ? Math.round(avgGrade * 10) / 10 : null,\n recentSubmissions: submissions.slice(0, 5).map((s) => ({\n assignment: s.assignment.title,\n gradeReceived: s.gradeReceived,\n maxGrade: s.assignment.maxGrade,\n submittedAt: s.submittedAt?.toISOString() ?? null,\n })),\n };\n } catch (error) {\n logger.error('get_student_progress tool failed', { error, classId, studentId });\n return { error: 'Failed to fetch student progress' };\n }\n },\n },\n\n get_worksheet_details: {\n description: 'Get full details of a worksheet including all questions, answers, and mark schemes.',\n inputSchema: jsonSchema({\n type: 'object',\n properties: {\n worksheetId: { type: 'string', description: 'The worksheet ID to look up' },\n },\n required: ['worksheetId'],\n }),\n execute: async ({ worksheetId }: { worksheetId: string }) => {\n try {\n const worksheet = await prisma.worksheet.findUnique({\n where: { id: worksheetId, classId },\n include: {\n questions: {\n orderBy: { order: 'asc' },\n },\n },\n });\n\n if (!worksheet) {\n return { error: 'Worksheet not found' };\n }\n\n return {\n id: worksheet.id,\n name: worksheet.name,\n questions: worksheet.questions.map((q) => ({\n id: q.id,\n type: q.type,\n question: q.question,\n answer: q.answer,\n options: q.options,\n points: q.points,\n order: q.order,\n })),\n };\n } catch (error) {\n logger.error('get_worksheet_details tool failed', { error, classId, worksheetId });\n return { error: 'Failed to fetch worksheet' };\n }\n },\n },\n\n get_mark_scheme: {\n description: 'Get a mark scheme (rubric) by ID, including criteria and point allocations.',\n inputSchema: jsonSchema({\n type: 'object',\n properties: {\n markSchemeId: { type: 'string', description: 'The mark scheme ID' },\n },\n required: ['markSchemeId'],\n }),\n execute: async ({ markSchemeId }: { markSchemeId: string }) => {\n try {\n const markScheme = await prisma.markScheme.findUnique({\n where: { id: markSchemeId },\n select: { id: true, name: true, structured: true },\n });\n\n if (!markScheme) {\n return { error: 'Mark scheme not found' };\n }\n\n let parsed: unknown = {};\n try { parsed = JSON.parse(markScheme.structured || '{}'); } catch { /* noop */ }\n\n return {\n id: markScheme.id,\n name: markScheme.name,\n structured: parsed,\n };\n } catch (error) {\n logger.error('get_mark_scheme tool failed', { error, classId, markSchemeId });\n return { error: 'Failed to fetch mark scheme' };\n }\n },\n },\n\n web_search: {\n description: 'Search the web for current information on a topic. Use this when you need up-to-date facts, definitions, explanations, or context beyond the class materials.',\n inputSchema: jsonSchema({\n type: 'object',\n properties: {\n query: { type: 'string', description: 'The search query to look up on the web' },\n },\n required: ['query'],\n }),\n execute: async ({ query }: { query: string }) => {\n try {\n const client = new OpenAI();\n const response = await client.responses.create({\n model: 'gpt-4o-mini',\n tools: [{ type: 'web_search_preview' }],\n input: query,\n });\n\n const messageOutput = response.output.find(\n (item) => item.type === 'message',\n );\n if (!messageOutput || messageOutput.type !== 'message') {\n return { summary: 'No results found.', sources: [] };\n }\n\n const textContent = messageOutput.content.find(\n (c) => c.type === 'output_text',\n );\n if (!textContent || textContent.type !== 'output_text') {\n return { summary: 'No results found.', sources: [] };\n }\n\n const sources = textContent.annotations\n .filter(\n (a): a is Extract<typeof a, { type: 'url_citation' }> =>\n a.type === 'url_citation',\n )\n .map((a) => ({ url: a.url, title: a.title }));\n\n return {\n summary: textContent.text.slice(0, 2000),\n sources: sources.slice(0, 5),\n };\n } catch (error) {\n logger.error('web_search tool failed', { error, query });\n return { summary: 'Web search failed. Please try again.', sources: [] };\n }\n },\n },\n };\n}\n"],"names":[],"mappings":"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","debug_id":"08e8556c-8e72-5041-a040-17a623b35c7c"}
@@ -5,22 +5,22 @@ export declare function getAllAnnouncements(classId: string): Promise<{
5
5
  _count: undefined;
6
6
  id: string;
7
7
  createdAt: Date;
8
- modifiedAt: Date | null;
9
8
  remarks: string;
9
+ modifiedAt: Date | null;
10
10
  teacher: {
11
11
  id: string;
12
12
  username: string;
13
13
  profile: {
14
- displayName: string | null;
15
14
  profilePicture: string | null;
15
+ displayName: string | null;
16
16
  profilePictureThumbnail: string | null;
17
17
  } | null;
18
18
  };
19
19
  attachments: {
20
20
  path: string;
21
21
  type: string;
22
- name: string;
23
22
  id: string;
23
+ name: string;
24
24
  size: number | null;
25
25
  uploadedAt: Date | null;
26
26
  thumbnailId: string | null;
@@ -31,22 +31,22 @@ export declare function getAnnouncement(id: string, classId: string): Promise<{
31
31
  announcement: {
32
32
  id: string;
33
33
  createdAt: Date;
34
- modifiedAt: Date | null;
35
34
  remarks: string;
35
+ modifiedAt: Date | null;
36
36
  teacher: {
37
37
  id: string;
38
38
  username: string;
39
39
  profile: {
40
- displayName: string | null;
41
40
  profilePicture: string | null;
41
+ displayName: string | null;
42
42
  profilePictureThumbnail: string | null;
43
43
  } | null;
44
44
  };
45
45
  attachments: {
46
46
  path: string;
47
47
  type: string;
48
- name: string;
49
48
  id: string;
49
+ name: string;
50
50
  size: number | null;
51
51
  uploadedAt: Date | null;
52
52
  thumbnailId: string | null;
@@ -66,22 +66,22 @@ export declare function createAnnouncementRecord(userId: string, input: {
66
66
  announcement: {
67
67
  id: string;
68
68
  createdAt: Date;
69
- modifiedAt: Date | null;
70
69
  remarks: string;
70
+ modifiedAt: Date | null;
71
71
  teacher: {
72
72
  id: string;
73
73
  username: string;
74
74
  profile: {
75
- displayName: string | null;
76
75
  profilePicture: string | null;
76
+ displayName: string | null;
77
77
  profilePictureThumbnail: string | null;
78
78
  } | null;
79
79
  };
80
80
  attachments: {
81
81
  path: string;
82
82
  type: string;
83
- name: string;
84
83
  id: string;
84
+ name: string;
85
85
  size: number | null;
86
86
  uploadedAt: Date | null;
87
87
  thumbnailId: string | null;
@@ -106,22 +106,22 @@ export declare function updateAnnouncementRecord(userId: string, input: {
106
106
  announcement: {
107
107
  id: string;
108
108
  createdAt: Date;
109
- modifiedAt: Date | null;
110
109
  remarks: string;
110
+ modifiedAt: Date | null;
111
111
  teacher: {
112
112
  id: string;
113
113
  username: string;
114
114
  profile: {
115
- displayName: string | null;
116
115
  profilePicture: string | null;
116
+ displayName: string | null;
117
117
  profilePictureThumbnail: string | null;
118
118
  } | null;
119
119
  };
120
120
  attachments: {
121
121
  path: string;
122
122
  type: string;
123
- name: string;
124
123
  id: string;
124
+ name: string;
125
125
  size: number | null;
126
126
  uploadedAt: Date | null;
127
127
  thumbnailId: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"announcement.d.ts","sourceRoot":"/","sources":["services/announcement.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKtF,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;GAQxD;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;GAShE;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,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;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFF;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QACJ,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACvD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC/B,CAAC;CACH;;;;;;;;;;;;;;;;;;;;;;;;;;GA2HF;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;GA0BzF"}
1
+ {"version":3,"file":"announcement.d.ts","sourceRoot":"/","sources":["services/announcement.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAMtF,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;GAQxD;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;GAShE;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,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;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFF;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QACJ,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACvD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC/B,CAAC;CACH;;;;;;;;;;;;;;;;;;;;;;;;;;GA8HF;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;GA0BzF"}
@@ -2,7 +2,7 @@
2
2
  * Announcement service – create, read, update, delete announcements.
3
3
  */
4
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]="88216f33-8458-56ce-84c5-509e783dc4e6")}catch(e){}}();
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c5ff2131-07ed-588e-a43a-05af5641c732")}catch(e){}}();
6
6
  import { TRPCError } from "@trpc/server";
7
7
  import { findAnnouncementsByClassId, findAnnouncementByIdAndClass, findAnnouncementWithClass, createAnnouncement, updateAnnouncementAttachments, deleteAnnouncement, } from "../models/announcement.js";
8
8
  import { prisma } from "../lib/prisma.js";
@@ -10,6 +10,7 @@ import { createDirectUploadFiles } from "../lib/fileUpload.js";
10
10
  import { deleteFile } from "../lib/googleCloudStorage.js";
11
11
  import { sendToMultiple } from "./notification.js";
12
12
  import { logger } from "../utils/logger.js";
13
+ import { enqueueEmbedding } from "./embedding.js";
13
14
  export async function getAllAnnouncements(classId) {
14
15
  const announcements = await findAnnouncementsByClassId(classId);
15
16
  const announcementsWithCounts = announcements.map((a) => ({
@@ -95,6 +96,8 @@ export async function createAnnouncementRecord(userId, input) {
95
96
  }).catch((error) => {
96
97
  logger.error("Failed to send announcement notifications:", error);
97
98
  });
99
+ // Auto-index announcement for RAG search
100
+ enqueueEmbedding(classId, 'ANNOUNCEMENT', announcement.id);
98
101
  return {
99
102
  announcement: announcementWithAttachments || announcement,
100
103
  uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,
@@ -197,6 +200,8 @@ export async function updateAnnouncementRecord(userId, input) {
197
200
  },
198
201
  },
199
202
  });
203
+ // Re-index announcement for RAG search after update
204
+ enqueueEmbedding(input.classId, 'ANNOUNCEMENT', input.id);
200
205
  return {
201
206
  announcement: updatedAnnouncement,
202
207
  uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,
@@ -224,4 +229,4 @@ export async function deleteAnnouncementRecord(userId, id, classId) {
224
229
  return { success: true };
225
230
  }
226
231
  //# sourceMappingURL=announcement.js.map
227
- //# debugId=88216f33-8458-56ce-84c5-509e783dc4e6
232
+ //# debugId=c5ff2131-07ed-588e-a43a-05af5641c732
@@ -1 +1 @@
1
- {"version":3,"file":"announcement.js","sources":["services/announcement.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Announcement service – create, read, update, delete announcements.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport {\n findAnnouncementsByClassId,\n findAnnouncementByIdAndClass,\n findAnnouncementWithClass,\n createAnnouncement,\n updateAnnouncement,\n updateAnnouncementAttachments,\n deleteAnnouncement,\n} from \"../models/announcement.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { createDirectUploadFiles, type DirectUploadFile } from \"../lib/fileUpload.js\";\nimport { deleteFile } from \"../lib/googleCloudStorage.js\";\nimport { sendToMultiple } from \"./notification.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport async function getAllAnnouncements(classId: string) {\n const announcements = await findAnnouncementsByClassId(classId);\n const announcementsWithCounts = announcements.map((a) => ({\n ...a,\n commentCount: a._count.comments,\n _count: undefined,\n }));\n return { announcements: announcementsWithCounts };\n}\n\nexport async function getAnnouncement(id: string, classId: string) {\n const announcement = await findAnnouncementByIdAndClass(id, classId);\n if (!announcement) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Announcement not found\",\n });\n }\n return { announcement };\n}\n\nexport async function createAnnouncementRecord(\n userId: string,\n input: {\n classId: string;\n remarks: string;\n files?: { name: string; type: string; size: number }[];\n existingFileIds?: string[];\n }\n) {\n const { classId, remarks, files, existingFileIds } = input;\n\n const classData = await prisma.class.findUnique({\n where: { id: classId },\n include: { students: { select: { id: true } } },\n });\n\n if (!classData) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Class not found\",\n });\n }\n\n const announcement = await createAnnouncement({\n remarks,\n teacherId: userId,\n classId,\n });\n\n let directUploadFiles: DirectUploadFile[] = [];\n if (files && files.length > 0) {\n directUploadFiles = await createDirectUploadFiles(\n files,\n userId,\n undefined,\n undefined,\n undefined,\n announcement.id\n );\n }\n\n if (existingFileIds && existingFileIds.length > 0) {\n await updateAnnouncementAttachments(announcement.id, {\n connect: existingFileIds.map((fileId) => ({ id: fileId })),\n });\n }\n\n const announcementWithAttachments = await prisma.announcement.findUnique({\n where: { id: announcement.id },\n select: {\n id: true,\n teacher: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n remarks: true,\n createdAt: true,\n modifiedAt: true,\n attachments: {\n select: {\n id: true,\n name: true,\n type: true,\n size: true,\n path: true,\n uploadedAt: true,\n thumbnailId: true,\n },\n },\n },\n });\n\n sendToMultiple({\n receiverIds: classData.students.map((s) => s.id),\n title: `New announcement in ${classData.name}`,\n content: `${remarks}\\n\\n[View announcement](/class/${classId})`,\n }).catch((error) => {\n logger.error(\"Failed to send announcement notifications:\", error);\n });\n\n return {\n announcement: announcementWithAttachments || announcement,\n uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,\n };\n}\n\nexport async function updateAnnouncementRecord(\n userId: string,\n input: {\n id: string;\n classId: string;\n data: {\n remarks?: string;\n files?: { name: string; type: string; size: number }[];\n existingFileIds?: string[];\n removedAttachments?: string[];\n };\n }\n) {\n const announcement = await findAnnouncementWithClass(input.id);\n\n if (!announcement) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Announcement not found\",\n });\n }\n\n const isCreator = announcement.teacherId === userId;\n const isClassTeacher = announcement.class.teachers.some((t) => t.id === userId);\n const isSchoolAdmin = Boolean(\n announcement.class.school?.memberships.some((admin) => admin.userId === userId) ||\n announcement.class.school?.users.some((admin) => admin.id === userId)\n );\n\n if (!isCreator && !isClassTeacher && !isSchoolAdmin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only the announcement creator, class teachers, or school admins can update announcements\",\n });\n }\n\n let directUploadFiles: DirectUploadFile[] = [];\n if (input.data.files && input.data.files.length > 0) {\n directUploadFiles = await createDirectUploadFiles(\n input.data.files,\n userId,\n undefined,\n undefined,\n undefined,\n input.id\n );\n }\n\n if (input.data.removedAttachments && input.data.removedAttachments.length > 0) {\n const filesToDelete = announcement.attachments.filter((f) =>\n input.data.removedAttachments!.includes(f.id)\n );\n await Promise.all(\n filesToDelete.map(async (file) => {\n try {\n if (file.uploadStatus === \"COMPLETED\") {\n await deleteFile(file.path);\n if (file.thumbnail?.path) {\n await deleteFile(file.thumbnail.path);\n }\n }\n } catch (error) {\n logger.warn(`Failed to delete file ${file.path}:`, {\n error: error instanceof Error ? { name: error.name, message: error.message } : error,\n });\n }\n })\n );\n }\n\n const updateData: { remarks?: string; attachments?: object } = {};\n if (input.data.remarks) updateData.remarks = input.data.remarks;\n\n if (input.data.existingFileIds && input.data.existingFileIds.length > 0) {\n await prisma.announcement.update({\n where: { id: input.id },\n data: {\n attachments: {\n connect: input.data.existingFileIds.map((fileId) => ({ id: fileId })),\n },\n },\n });\n }\n\n if (input.data.removedAttachments && input.data.removedAttachments.length > 0) {\n await prisma.announcement.update({\n where: { id: input.id },\n data: {\n attachments: {\n deleteMany: { id: { in: input.data.removedAttachments } },\n },\n },\n });\n }\n\n const updatedAnnouncement = await prisma.announcement.update({\n where: { id: input.id },\n data: updateData,\n select: {\n id: true,\n teacher: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n remarks: true,\n createdAt: true,\n modifiedAt: true,\n attachments: {\n select: {\n id: true,\n name: true,\n type: true,\n size: true,\n path: true,\n uploadedAt: true,\n thumbnailId: true,\n },\n },\n },\n });\n\n return {\n announcement: updatedAnnouncement,\n uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,\n };\n}\n\nexport async function deleteAnnouncementRecord(userId: string, id: string, classId: string) {\n const announcement = await findAnnouncementWithClass(id);\n\n if (!announcement) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Announcement not found\",\n });\n }\n\n const isCreator = announcement.teacherId === userId;\n const isClassTeacher = announcement.class.teachers.some((t) => t.id === userId);\n const isSchoolAdmin = Boolean(\n announcement.class.school?.memberships.some((admin) => admin.userId === userId) ||\n announcement.class.school?.users.some((admin) => admin.id === userId)\n );\n\n if (!isCreator && !isClassTeacher && !isSchoolAdmin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only the announcement creator, class teachers, or school admins can delete announcements\",\n });\n }\n\n await deleteAnnouncement(id);\n return { success: true };\n}\n"],"names":[],"mappings":"AAAA;;GAEG;;;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EACL,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,EACzB,kBAAkB,EAElB,6BAA6B,EAC7B,kBAAkB,GACnB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAyB,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAe;IACvD,MAAM,aAAa,GAAG,MAAM,0BAA0B,CAAC,OAAO,CAAC,CAAC;IAChE,MAAM,uBAAuB,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,GAAG,CAAC;QACJ,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ;QAC/B,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC,CAAC;IACJ,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAU,EAAE,OAAe;IAC/D,MAAM,YAAY,GAAG,MAAM,4BAA4B,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACrE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,SAAS,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAc,EACd,KAKC;IAED,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;IAE3D,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC;QAC9C,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;QACtB,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;KAChD,CAAC,CAAC;IAEH,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,SAAS,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,iBAAiB;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAAC;QAC5C,OAAO;QACP,SAAS,EAAE,MAAM;QACjB,OAAO;KACR,CAAC,CAAC;IAEH,IAAI,iBAAiB,GAAuB,EAAE,CAAC;IAC/C,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,iBAAiB,GAAG,MAAM,uBAAuB,CAC/C,KAAK,EACL,MAAM,EACN,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,CAAC,EAAE,CAChB,CAAC;IACJ,CAAC;IAED,IAAI,eAAe,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,6BAA6B,CAAC,YAAY,CAAC,EAAE,EAAE;YACnD,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;SAC3D,CAAC,CAAC;IACL,CAAC;IAED,MAAM,2BAA2B,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QACvE,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,CAAC,EAAE,EAAE;QAC9B,MAAM,EAAE;YACN,EAAE,EAAE,IAAI;YACR,OAAO,EAAE;gBACP,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,QAAQ,EAAE,IAAI;oBACd,OAAO,EAAE;wBACP,MAAM,EAAE;4BACN,WAAW,EAAE,IAAI;4BACjB,cAAc,EAAE,IAAI;4BACpB,uBAAuB,EAAE,IAAI;yBAC9B;qBACF;iBACF;aACF;YACD,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE;gBACX,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,IAAI,EAAE,IAAI;oBACV,IAAI,EAAE,IAAI;oBACV,IAAI,EAAE,IAAI;oBACV,IAAI,EAAE,IAAI;oBACV,UAAU,EAAE,IAAI;oBAChB,WAAW,EAAE,IAAI;iBAClB;aACF;SACF;KACF,CAAC,CAAC;IAEH,cAAc,CAAC;QACb,WAAW,EAAE,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,KAAK,EAAE,uBAAuB,SAAS,CAAC,IAAI,EAAE;QAC9C,OAAO,EAAE,GAAG,OAAO,kCAAkC,OAAO,GAAG;KAChE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,MAAM,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,YAAY,EAAE,2BAA2B,IAAI,YAAY;QACzD,WAAW,EAAE,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS;KAC1E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAc,EACd,KASC;IAED,MAAM,YAAY,GAAG,MAAM,yBAAyB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAE/D,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,SAAS,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,KAAK,MAAM,CAAC;IACpD,MAAM,cAAc,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;IAChF,MAAM,aAAa,GAAG,OAAO,CAC3B,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC;QAC7E,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,CACxE,CAAC;IAEF,IAAI,CAAC,SAAS,IAAI,CAAC,cAAc,IAAI,CAAC,aAAa,EAAE,CAAC;QACpD,MAAM,IAAI,SAAS,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,0FAA0F;SACpG,CAAC,CAAC;IACL,CAAC;IAED,IAAI,iBAAiB,GAAuB,EAAE,CAAC;IAC/C,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,iBAAiB,GAAG,MAAM,uBAAuB,CAC/C,KAAK,CAAC,IAAI,CAAC,KAAK,EAChB,MAAM,EACN,SAAS,EACT,SAAS,EACT,SAAS,EACT,KAAK,CAAC,EAAE,CACT,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,CAAC,kBAAkB,IAAI,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9E,MAAM,aAAa,GAAG,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAC1D,KAAK,CAAC,IAAI,CAAC,kBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAC9C,CAAC;QACF,MAAM,OAAO,CAAC,GAAG,CACf,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YAC/B,IAAI,CAAC;gBACH,IAAI,IAAI,CAAC,YAAY,KAAK,WAAW,EAAE,CAAC;oBACtC,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC5B,IAAI,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;wBACzB,MAAM,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;oBACxC,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,yBAAyB,IAAI,CAAC,IAAI,GAAG,EAAE;oBACjD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK;iBACrF,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAA+C,EAAE,CAAC;IAClE,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO;QAAE,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;IAEhE,IAAI,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YAC/B,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE;YACvB,IAAI,EAAE;gBACJ,WAAW,EAAE;oBACX,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;iBACtE;aACF;SACF,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,CAAC,kBAAkB,IAAI,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9E,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YAC/B,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE;YACvB,IAAI,EAAE;gBACJ,WAAW,EAAE;oBACX,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE;iBAC1D;aACF;SACF,CAAC,CAAC;IACL,CAAC;IAED,MAAM,mBAAmB,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;QAC3D,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE;QACvB,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE;YACN,EAAE,EAAE,IAAI;YACR,OAAO,EAAE;gBACP,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,QAAQ,EAAE,IAAI;oBACd,OAAO,EAAE;wBACP,MAAM,EAAE;4BACN,WAAW,EAAE,IAAI;4BACjB,cAAc,EAAE,IAAI;4BACpB,uBAAuB,EAAE,IAAI;yBAC9B;qBACF;iBACF;aACF;YACD,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE;gBACX,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,IAAI,EAAE,IAAI;oBACV,IAAI,EAAE,IAAI;oBACV,IAAI,EAAE,IAAI;oBACV,IAAI,EAAE,IAAI;oBACV,UAAU,EAAE,IAAI;oBAChB,WAAW,EAAE,IAAI;iBAClB;aACF;SACF;KACF,CAAC,CAAC;IAEH,OAAO;QACL,YAAY,EAAE,mBAAmB;QACjC,WAAW,EAAE,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS;KAC1E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,MAAc,EAAE,EAAU,EAAE,OAAe;IACxF,MAAM,YAAY,GAAG,MAAM,yBAAyB,CAAC,EAAE,CAAC,CAAC;IAEzD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,SAAS,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,KAAK,MAAM,CAAC;IACpD,MAAM,cAAc,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;IAChF,MAAM,aAAa,GAAG,OAAO,CAC3B,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC;QAC7E,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,CACxE,CAAC;IAEF,IAAI,CAAC,SAAS,IAAI,CAAC,cAAc,IAAI,CAAC,aAAa,EAAE,CAAC;QACpD,MAAM,IAAI,SAAS,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,0FAA0F;SACpG,CAAC,CAAC;IACL,CAAC;IAED,MAAM,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC","debug_id":"88216f33-8458-56ce-84c5-509e783dc4e6"}
1
+ {"version":3,"file":"announcement.js","sources":["services/announcement.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Announcement service – create, read, update, delete announcements.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport {\n findAnnouncementsByClassId,\n findAnnouncementByIdAndClass,\n findAnnouncementWithClass,\n createAnnouncement,\n updateAnnouncement,\n updateAnnouncementAttachments,\n deleteAnnouncement,\n} from \"../models/announcement.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { createDirectUploadFiles, type DirectUploadFile } from \"../lib/fileUpload.js\";\nimport { deleteFile } from \"../lib/googleCloudStorage.js\";\nimport { sendToMultiple } from \"./notification.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { enqueueEmbedding } from \"./embedding.js\";\n\nexport async function getAllAnnouncements(classId: string) {\n const announcements = await findAnnouncementsByClassId(classId);\n const announcementsWithCounts = announcements.map((a) => ({\n ...a,\n commentCount: a._count.comments,\n _count: undefined,\n }));\n return { announcements: announcementsWithCounts };\n}\n\nexport async function getAnnouncement(id: string, classId: string) {\n const announcement = await findAnnouncementByIdAndClass(id, classId);\n if (!announcement) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Announcement not found\",\n });\n }\n return { announcement };\n}\n\nexport async function createAnnouncementRecord(\n userId: string,\n input: {\n classId: string;\n remarks: string;\n files?: { name: string; type: string; size: number }[];\n existingFileIds?: string[];\n }\n) {\n const { classId, remarks, files, existingFileIds } = input;\n\n const classData = await prisma.class.findUnique({\n where: { id: classId },\n include: { students: { select: { id: true } } },\n });\n\n if (!classData) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Class not found\",\n });\n }\n\n const announcement = await createAnnouncement({\n remarks,\n teacherId: userId,\n classId,\n });\n\n let directUploadFiles: DirectUploadFile[] = [];\n if (files && files.length > 0) {\n directUploadFiles = await createDirectUploadFiles(\n files,\n userId,\n undefined,\n undefined,\n undefined,\n announcement.id\n );\n }\n\n if (existingFileIds && existingFileIds.length > 0) {\n await updateAnnouncementAttachments(announcement.id, {\n connect: existingFileIds.map((fileId) => ({ id: fileId })),\n });\n }\n\n const announcementWithAttachments = await prisma.announcement.findUnique({\n where: { id: announcement.id },\n select: {\n id: true,\n teacher: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n remarks: true,\n createdAt: true,\n modifiedAt: true,\n attachments: {\n select: {\n id: true,\n name: true,\n type: true,\n size: true,\n path: true,\n uploadedAt: true,\n thumbnailId: true,\n },\n },\n },\n });\n\n sendToMultiple({\n receiverIds: classData.students.map((s) => s.id),\n title: `New announcement in ${classData.name}`,\n content: `${remarks}\\n\\n[View announcement](/class/${classId})`,\n }).catch((error) => {\n logger.error(\"Failed to send announcement notifications:\", error);\n });\n\n // Auto-index announcement for RAG search\n enqueueEmbedding(classId, 'ANNOUNCEMENT', announcement.id);\n\n return {\n announcement: announcementWithAttachments || announcement,\n uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,\n };\n}\n\nexport async function updateAnnouncementRecord(\n userId: string,\n input: {\n id: string;\n classId: string;\n data: {\n remarks?: string;\n files?: { name: string; type: string; size: number }[];\n existingFileIds?: string[];\n removedAttachments?: string[];\n };\n }\n) {\n const announcement = await findAnnouncementWithClass(input.id);\n\n if (!announcement) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Announcement not found\",\n });\n }\n\n const isCreator = announcement.teacherId === userId;\n const isClassTeacher = announcement.class.teachers.some((t) => t.id === userId);\n const isSchoolAdmin = Boolean(\n announcement.class.school?.memberships.some((admin) => admin.userId === userId) ||\n announcement.class.school?.users.some((admin) => admin.id === userId)\n );\n\n if (!isCreator && !isClassTeacher && !isSchoolAdmin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only the announcement creator, class teachers, or school admins can update announcements\",\n });\n }\n\n let directUploadFiles: DirectUploadFile[] = [];\n if (input.data.files && input.data.files.length > 0) {\n directUploadFiles = await createDirectUploadFiles(\n input.data.files,\n userId,\n undefined,\n undefined,\n undefined,\n input.id\n );\n }\n\n if (input.data.removedAttachments && input.data.removedAttachments.length > 0) {\n const filesToDelete = announcement.attachments.filter((f) =>\n input.data.removedAttachments!.includes(f.id)\n );\n await Promise.all(\n filesToDelete.map(async (file) => {\n try {\n if (file.uploadStatus === \"COMPLETED\") {\n await deleteFile(file.path);\n if (file.thumbnail?.path) {\n await deleteFile(file.thumbnail.path);\n }\n }\n } catch (error) {\n logger.warn(`Failed to delete file ${file.path}:`, {\n error: error instanceof Error ? { name: error.name, message: error.message } : error,\n });\n }\n })\n );\n }\n\n const updateData: { remarks?: string; attachments?: object } = {};\n if (input.data.remarks) updateData.remarks = input.data.remarks;\n\n if (input.data.existingFileIds && input.data.existingFileIds.length > 0) {\n await prisma.announcement.update({\n where: { id: input.id },\n data: {\n attachments: {\n connect: input.data.existingFileIds.map((fileId) => ({ id: fileId })),\n },\n },\n });\n }\n\n if (input.data.removedAttachments && input.data.removedAttachments.length > 0) {\n await prisma.announcement.update({\n where: { id: input.id },\n data: {\n attachments: {\n deleteMany: { id: { in: input.data.removedAttachments } },\n },\n },\n });\n }\n\n const updatedAnnouncement = await prisma.announcement.update({\n where: { id: input.id },\n data: updateData,\n select: {\n id: true,\n teacher: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n remarks: true,\n createdAt: true,\n modifiedAt: true,\n attachments: {\n select: {\n id: true,\n name: true,\n type: true,\n size: true,\n path: true,\n uploadedAt: true,\n thumbnailId: true,\n },\n },\n },\n });\n\n // Re-index announcement for RAG search after update\n enqueueEmbedding(input.classId, 'ANNOUNCEMENT', input.id);\n\n return {\n announcement: updatedAnnouncement,\n uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,\n };\n}\n\nexport async function deleteAnnouncementRecord(userId: string, id: string, classId: string) {\n const announcement = await findAnnouncementWithClass(id);\n\n if (!announcement) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Announcement not found\",\n });\n }\n\n const isCreator = announcement.teacherId === userId;\n const isClassTeacher = announcement.class.teachers.some((t) => t.id === userId);\n const isSchoolAdmin = Boolean(\n announcement.class.school?.memberships.some((admin) => admin.userId === userId) ||\n announcement.class.school?.users.some((admin) => admin.id === userId)\n );\n\n if (!isCreator && !isClassTeacher && !isSchoolAdmin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only the announcement creator, class teachers, or school admins can delete announcements\",\n });\n }\n\n await deleteAnnouncement(id);\n return { success: true };\n}\n"],"names":[],"mappings":"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","debug_id":"c5ff2131-07ed-588e-a43a-05af5641c732"}