@studious-lms/server 1.13.0 → 1.15.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 (375) 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/instrument.js +3 -3
  8. package/dist/instrument.js.map +1 -1
  9. package/dist/lib/config/cors.d.ts.map +1 -1
  10. package/dist/lib/config/cors.js +31 -11
  11. package/dist/lib/config/cors.js.map +1 -1
  12. package/dist/lib/config/env.d.ts +17 -0
  13. package/dist/lib/config/env.d.ts.map +1 -1
  14. package/dist/lib/config/env.js +8 -2
  15. package/dist/lib/config/env.js.map +1 -1
  16. package/dist/lib/googleCloudStorage.d.ts +10 -0
  17. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  18. package/dist/lib/googleCloudStorage.js +22 -2
  19. package/dist/lib/googleCloudStorage.js.map +1 -1
  20. package/dist/lib/inferDocumentFormat.d.ts +10 -0
  21. package/dist/lib/inferDocumentFormat.d.ts.map +1 -0
  22. package/dist/lib/inferDocumentFormat.js +48 -0
  23. package/dist/lib/inferDocumentFormat.js.map +1 -0
  24. package/dist/lib/legal.d.ts +3 -0
  25. package/dist/lib/legal.d.ts.map +1 -0
  26. package/dist/lib/legal.js +6 -0
  27. package/dist/lib/legal.js.map +1 -0
  28. package/dist/lib/prisma.d.ts +104 -5
  29. package/dist/lib/prisma.d.ts.map +1 -1
  30. package/dist/lib/prisma.js +33 -2
  31. package/dist/lib/prisma.js.map +1 -1
  32. package/dist/models/agenda.d.ts +20 -20
  33. package/dist/models/announcement.d.ts +211 -101
  34. package/dist/models/announcement.d.ts.map +1 -1
  35. package/dist/models/assignment.d.ts +560 -362
  36. package/dist/models/assignment.d.ts.map +1 -1
  37. package/dist/models/attendance.d.ts +197 -71
  38. package/dist/models/attendance.d.ts.map +1 -1
  39. package/dist/models/auth.d.ts +356 -114
  40. package/dist/models/auth.d.ts.map +1 -1
  41. package/dist/models/auth.js +4 -2
  42. package/dist/models/auth.js.map +1 -1
  43. package/dist/models/class.d.ts +327 -201
  44. package/dist/models/class.d.ts.map +1 -1
  45. package/dist/models/class.js +2 -2
  46. package/dist/models/class.js.map +1 -1
  47. package/dist/models/cloudStorage.d.ts +146 -56
  48. package/dist/models/cloudStorage.d.ts.map +1 -1
  49. package/dist/models/comment.d.ts +262 -110
  50. package/dist/models/comment.d.ts.map +1 -1
  51. package/dist/models/conversation.d.ts +206 -80
  52. package/dist/models/conversation.d.ts.map +1 -1
  53. package/dist/models/department.d.ts +196 -70
  54. package/dist/models/department.d.ts.map +1 -1
  55. package/dist/models/event.d.ts +305 -141
  56. package/dist/models/event.d.ts.map +1 -1
  57. package/dist/models/file.d.ts +422 -272
  58. package/dist/models/file.d.ts.map +1 -1
  59. package/dist/models/folder.d.ts +433 -181
  60. package/dist/models/folder.d.ts.map +1 -1
  61. package/dist/models/labChat.d.ts +205 -79
  62. package/dist/models/labChat.d.ts.map +1 -1
  63. package/dist/models/marketing.d.ts +79 -38
  64. package/dist/models/marketing.d.ts.map +1 -1
  65. package/dist/models/marketing.js +12 -4
  66. package/dist/models/marketing.js.map +1 -1
  67. package/dist/models/message.d.ts +97 -43
  68. package/dist/models/message.d.ts.map +1 -1
  69. package/dist/models/newtonChat.d.ts +84 -30
  70. package/dist/models/newtonChat.d.ts.map +1 -1
  71. package/dist/models/notification.d.ts +57 -21
  72. package/dist/models/notification.d.ts.map +1 -1
  73. package/dist/models/school.d.ts +349 -133
  74. package/dist/models/school.d.ts.map +1 -1
  75. package/dist/models/section.d.ts +216 -72
  76. package/dist/models/section.d.ts.map +1 -1
  77. package/dist/models/tag.d.ts +156 -48
  78. package/dist/models/tag.d.ts.map +1 -1
  79. package/dist/models/user.d.ts +82 -28
  80. package/dist/models/user.d.ts.map +1 -1
  81. package/dist/models/worksheet.d.ts +1032 -423
  82. package/dist/models/worksheet.d.ts.map +1 -1
  83. package/dist/models/worksheet.js +8 -2
  84. package/dist/models/worksheet.js.map +1 -1
  85. package/dist/pipelines/aiLabChat.d.ts +7 -9
  86. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  87. package/dist/pipelines/aiLabChat.js +194 -125
  88. package/dist/pipelines/aiLabChat.js.map +1 -1
  89. package/dist/pipelines/aiLabChatContract.d.ts +233 -389
  90. package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
  91. package/dist/pipelines/aiLabChatContract.js +54 -70
  92. package/dist/pipelines/aiLabChatContract.js.map +1 -1
  93. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  94. package/dist/pipelines/aiNewtonChat.js +41 -41
  95. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  96. package/dist/pipelines/gradeWorksheet.d.ts +8 -8
  97. package/dist/pipelines/labChatPrompt.d.ts +18 -11
  98. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  99. package/dist/pipelines/labChatPrompt.js +93 -75
  100. package/dist/pipelines/labChatPrompt.js.map +1 -1
  101. package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
  102. package/dist/routers/_app.d.ts +2601 -2151
  103. package/dist/routers/_app.d.ts.map +1 -1
  104. package/dist/routers/_app.js +4 -2
  105. package/dist/routers/_app.js.map +1 -1
  106. package/dist/routers/admin.d.ts +13 -13
  107. package/dist/routers/agenda.d.ts +18 -18
  108. package/dist/routers/announcement.d.ts +60 -60
  109. package/dist/routers/assignment.d.ts +393 -383
  110. package/dist/routers/assignment.d.ts.map +1 -1
  111. package/dist/routers/attendance.d.ts +10 -10
  112. package/dist/routers/auth.d.ts +4 -2
  113. package/dist/routers/auth.d.ts.map +1 -1
  114. package/dist/routers/auth.js +15 -2
  115. package/dist/routers/auth.js.map +1 -1
  116. package/dist/routers/class.d.ts +157 -153
  117. package/dist/routers/class.d.ts.map +1 -1
  118. package/dist/routers/comment.d.ts +18 -18
  119. package/dist/routers/conversation.d.ts +11 -11
  120. package/dist/routers/department.d.ts +34 -34
  121. package/dist/routers/discussion.d.ts +68 -56
  122. package/dist/routers/discussion.d.ts.map +1 -1
  123. package/dist/routers/discussion.js +6 -2
  124. package/dist/routers/discussion.js.map +1 -1
  125. package/dist/routers/embedding.d.ts +73 -0
  126. package/dist/routers/embedding.d.ts.map +1 -0
  127. package/dist/routers/embedding.js +151 -0
  128. package/dist/routers/embedding.js.map +1 -0
  129. package/dist/routers/event.d.ts +57 -55
  130. package/dist/routers/event.d.ts.map +1 -1
  131. package/dist/routers/file.d.ts +14 -14
  132. package/dist/routers/folder.d.ts +38 -38
  133. package/dist/routers/labChat.d.ts +16 -16
  134. package/dist/routers/marketing.d.ts +1 -14
  135. package/dist/routers/marketing.d.ts.map +1 -1
  136. package/dist/routers/message.d.ts +13 -13
  137. package/dist/routers/newtonChat.d.ts +4 -4
  138. package/dist/routers/notifications.d.ts +5 -5
  139. package/dist/routers/permissions.d.ts +14 -14
  140. package/dist/routers/school.d.ts +59 -55
  141. package/dist/routers/school.d.ts.map +1 -1
  142. package/dist/routers/search.d.ts +22 -1
  143. package/dist/routers/search.d.ts.map +1 -1
  144. package/dist/routers/search.js +9 -3
  145. package/dist/routers/search.js.map +1 -1
  146. package/dist/routers/section.d.ts +4 -4
  147. package/dist/routers/studentProgress.d.ts +1 -1
  148. package/dist/routers/tag.d.ts +7 -7
  149. package/dist/routers/user.d.ts +99 -10
  150. package/dist/routers/user.d.ts.map +1 -1
  151. package/dist/routers/user.js +19 -2
  152. package/dist/routers/user.js.map +1 -1
  153. package/dist/routers/worksheet.d.ts +93 -55
  154. package/dist/routers/worksheet.d.ts.map +1 -1
  155. package/dist/routers/worksheet.js +26 -2
  156. package/dist/routers/worksheet.js.map +1 -1
  157. package/dist/routes/aiStream.d.ts +2 -0
  158. package/dist/routes/aiStream.d.ts.map +1 -0
  159. package/dist/routes/aiStream.js +726 -0
  160. package/dist/routes/aiStream.js.map +1 -0
  161. package/dist/seedDatabase.d.ts +3 -1
  162. package/dist/seedDatabase.d.ts.map +1 -1
  163. package/dist/seedDatabase.js +6 -6
  164. package/dist/seedDatabase.js.map +1 -1
  165. package/dist/services/adminInstitution.d.ts +8 -8
  166. package/dist/services/adminRunJob.d.ts +4 -4
  167. package/dist/services/agenda.d.ts +18 -18
  168. package/dist/services/aiChatStream.d.ts +88 -0
  169. package/dist/services/aiChatStream.d.ts.map +1 -0
  170. package/dist/services/aiChatStream.js +254 -0
  171. package/dist/services/aiChatStream.js.map +1 -0
  172. package/dist/services/aiModifierTools.d.ts +16 -0
  173. package/dist/services/aiModifierTools.d.ts.map +1 -0
  174. package/dist/services/aiModifierTools.js +274 -0
  175. package/dist/services/aiModifierTools.js.map +1 -0
  176. package/dist/services/aiProposalTools.d.ts +32 -0
  177. package/dist/services/aiProposalTools.d.ts.map +1 -0
  178. package/dist/services/aiProposalTools.js +207 -0
  179. package/dist/services/aiProposalTools.js.map +1 -0
  180. package/dist/services/aiStream.d.ts +22 -0
  181. package/dist/services/aiStream.d.ts.map +1 -0
  182. package/dist/services/aiStream.js +142 -0
  183. package/dist/services/aiStream.js.map +1 -0
  184. package/dist/services/aiTools.d.ts +16 -0
  185. package/dist/services/aiTools.d.ts.map +1 -0
  186. package/dist/services/aiTools.js +271 -0
  187. package/dist/services/aiTools.js.map +1 -0
  188. package/dist/services/announcement.d.ts +42 -42
  189. package/dist/services/announcement.d.ts.map +1 -1
  190. package/dist/services/announcement.js +7 -2
  191. package/dist/services/announcement.js.map +1 -1
  192. package/dist/services/assignment.d.ts +355 -345
  193. package/dist/services/assignment.d.ts.map +1 -1
  194. package/dist/services/assignment.js +30 -9
  195. package/dist/services/assignment.js.map +1 -1
  196. package/dist/services/attendance.d.ts +10 -10
  197. package/dist/services/auth.d.ts +4 -2
  198. package/dist/services/auth.d.ts.map +1 -1
  199. package/dist/services/auth.js +13 -4
  200. package/dist/services/auth.js.map +1 -1
  201. package/dist/services/class.d.ts +146 -142
  202. package/dist/services/class.d.ts.map +1 -1
  203. package/dist/services/class.js +24 -4
  204. package/dist/services/class.js.map +1 -1
  205. package/dist/services/comment.d.ts +18 -18
  206. package/dist/services/conversation.d.ts +11 -11
  207. package/dist/services/department.d.ts +33 -33
  208. package/dist/services/discussion.d.ts +58 -58
  209. package/dist/services/discussion.d.ts.map +1 -1
  210. package/dist/services/discussion.js +15 -8
  211. package/dist/services/discussion.js.map +1 -1
  212. package/dist/services/discussionSummary.d.ts +21 -0
  213. package/dist/services/discussionSummary.d.ts.map +1 -0
  214. package/dist/services/discussionSummary.js +179 -0
  215. package/dist/services/discussionSummary.js.map +1 -0
  216. package/dist/services/embedding.d.ts +85 -0
  217. package/dist/services/embedding.d.ts.map +1 -0
  218. package/dist/services/embedding.js +372 -0
  219. package/dist/services/embedding.js.map +1 -0
  220. package/dist/services/event.d.ts +55 -53
  221. package/dist/services/event.d.ts.map +1 -1
  222. package/dist/services/file.d.ts +28 -28
  223. package/dist/services/folder.d.ts +36 -36
  224. package/dist/services/folder.d.ts.map +1 -1
  225. package/dist/services/folder.js +32 -2
  226. package/dist/services/folder.js.map +1 -1
  227. package/dist/services/labChat.d.ts +16 -16
  228. package/dist/services/marketing.d.ts +2 -15
  229. package/dist/services/marketing.d.ts.map +1 -1
  230. package/dist/services/marketing.js +5 -5
  231. package/dist/services/marketing.js.map +1 -1
  232. package/dist/services/message.d.ts +11 -11
  233. package/dist/services/message.d.ts.map +1 -1
  234. package/dist/services/message.js +2 -2
  235. package/dist/services/message.js.map +1 -1
  236. package/dist/services/newtonChat.d.ts +4 -4
  237. package/dist/services/notification.d.ts +5 -5
  238. package/dist/services/notification.d.ts.map +1 -1
  239. package/dist/services/openaiDocumentGen.d.ts +37 -0
  240. package/dist/services/openaiDocumentGen.d.ts.map +1 -0
  241. package/dist/services/openaiDocumentGen.js +262 -0
  242. package/dist/services/openaiDocumentGen.js.map +1 -0
  243. package/dist/services/permissions.d.ts +14 -14
  244. package/dist/services/school.d.ts +54 -50
  245. package/dist/services/school.d.ts.map +1 -1
  246. package/dist/services/search.d.ts +13 -1
  247. package/dist/services/search.d.ts.map +1 -1
  248. package/dist/services/search.js +145 -2
  249. package/dist/services/search.js.map +1 -1
  250. package/dist/services/section.d.ts +3 -3
  251. package/dist/services/tag.d.ts +4 -4
  252. package/dist/services/user.d.ts +4 -4
  253. package/dist/services/user.d.ts.map +1 -1
  254. package/dist/services/user.js +10 -15
  255. package/dist/services/user.js.map +1 -1
  256. package/dist/services/userPrivacy.d.ts +86 -0
  257. package/dist/services/userPrivacy.d.ts.map +1 -0
  258. package/dist/services/userPrivacy.js +184 -0
  259. package/dist/services/userPrivacy.js.map +1 -0
  260. package/dist/services/worksheet.d.ts +86 -57
  261. package/dist/services/worksheet.d.ts.map +1 -1
  262. package/dist/services/worksheet.js +57 -5
  263. package/dist/services/worksheet.js.map +1 -1
  264. package/dist/utils/email.d.ts.map +1 -1
  265. package/dist/utils/email.js +7 -3
  266. package/dist/utils/email.js.map +1 -1
  267. package/dist/utils/streamBroadcast.d.ts +11 -0
  268. package/dist/utils/streamBroadcast.d.ts.map +1 -0
  269. package/dist/utils/streamBroadcast.js +32 -0
  270. package/dist/utils/streamBroadcast.js.map +1 -0
  271. package/dist/workers/connection.d.ts +15 -0
  272. package/dist/workers/connection.d.ts.map +1 -1
  273. package/dist/workers/connection.js +20 -2
  274. package/dist/workers/connection.js.map +1 -1
  275. package/dist/workers/index.js +4 -2
  276. package/dist/workers/index.js.map +1 -1
  277. package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -1
  278. package/dist/workers/processors/createRunJobWorker.js +31 -8
  279. package/dist/workers/processors/createRunJobWorker.js.map +1 -1
  280. package/dist/workers/processors/embedContent.d.ts +8 -0
  281. package/dist/workers/processors/embedContent.d.ts.map +1 -0
  282. package/dist/workers/processors/embedContent.js +67 -0
  283. package/dist/workers/processors/embedContent.js.map +1 -0
  284. package/dist/workers/queues.d.ts +6 -0
  285. package/dist/workers/queues.d.ts.map +1 -1
  286. package/dist/workers/queues.js +5 -3
  287. package/dist/workers/queues.js.map +1 -1
  288. package/package.json +5 -1
  289. package/prisma/migrations/20260609215135_init/migration.sql +1412 -0
  290. package/prisma/migrations/20260610130000_add_worksheet_tags/migration.sql +13 -0
  291. package/prisma/migrations/20260610140000_expand_embedding_source_types/migration.sql +4 -0
  292. package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +3 -0
  293. package/prisma/schema.prisma +44 -7
  294. package/src/index.ts +4 -0
  295. package/src/instrument.ts +1 -1
  296. package/src/lib/config/cors.ts +32 -9
  297. package/src/lib/config/env.ts +6 -0
  298. package/src/lib/googleCloudStorage.ts +21 -0
  299. package/src/lib/inferDocumentFormat.ts +58 -0
  300. package/src/lib/legal.ts +2 -0
  301. package/src/lib/prisma.ts +40 -1
  302. package/src/models/auth.ts +4 -0
  303. package/src/models/class.ts +3 -2
  304. package/src/models/marketing.ts +10 -2
  305. package/src/models/worksheet.ts +5 -2
  306. package/src/pipelines/aiLabChat.ts +245 -146
  307. package/src/pipelines/aiLabChatContract.ts +59 -69
  308. package/src/pipelines/aiNewtonChat.ts +46 -43
  309. package/src/pipelines/labChatPrompt.ts +115 -90
  310. package/src/routers/_app.ts +2 -0
  311. package/src/routers/auth.ts +13 -0
  312. package/src/routers/discussion.ts +7 -0
  313. package/src/routers/embedding.ts +164 -0
  314. package/src/routers/search.ts +10 -1
  315. package/src/routers/user.ts +26 -0
  316. package/src/routers/worksheet.ts +27 -0
  317. package/src/routes/aiStream.ts +827 -0
  318. package/src/seedDatabase.ts +4 -4
  319. package/src/services/aiChatStream.ts +341 -0
  320. package/src/services/aiModifierTools.ts +341 -0
  321. package/src/services/aiProposalTools.ts +274 -0
  322. package/src/services/aiStream.ts +175 -0
  323. package/src/services/aiTools.ts +290 -0
  324. package/src/services/announcement.ts +7 -0
  325. package/src/services/assignment.ts +31 -7
  326. package/src/services/auth.ts +14 -2
  327. package/src/services/class.ts +27 -2
  328. package/src/services/discussion.ts +22 -8
  329. package/src/services/discussionSummary.ts +226 -0
  330. package/src/services/embedding.ts +446 -0
  331. package/src/services/folder.ts +31 -0
  332. package/src/services/marketing.ts +3 -3
  333. package/src/services/message.ts +2 -2
  334. package/src/services/openaiDocumentGen.ts +319 -0
  335. package/src/services/search.ts +163 -1
  336. package/src/services/user.ts +9 -17
  337. package/src/services/userPrivacy.ts +196 -0
  338. package/src/services/worksheet.ts +77 -3
  339. package/src/utils/email.ts +5 -1
  340. package/src/utils/streamBroadcast.ts +48 -0
  341. package/src/workers/connection.ts +19 -0
  342. package/src/workers/index.ts +2 -0
  343. package/src/workers/processors/createRunJobWorker.ts +32 -6
  344. package/src/workers/processors/embedContent.ts +86 -0
  345. package/src/workers/queues.ts +5 -1
  346. package/tests/helpers/registerInput.ts +20 -0
  347. package/tests/integration/globalSetup.ts +10 -18
  348. package/tests/integration/routers/auth.test.ts +13 -18
  349. package/tests/unit/lib/inferDocumentFormat.test.ts +33 -0
  350. package/tests/unit/pipelines/aiLabChatContract.test.ts +38 -21
  351. package/tests/unit/pipelines/labChatPrompt.test.ts +23 -36
  352. package/tests/unit/services/aiChatStream.test.ts +92 -0
  353. package/tests/unit/services/openaiDocumentGen.test.ts +65 -0
  354. package/prisma/migrations/20250807062924_init/migration.sql +0 -436
  355. package/prisma/migrations/20250919063704_init2/migration.sql +0 -24
  356. package/prisma/migrations/20250920143543_add_profile_fields/migration.sql +0 -15
  357. package/prisma/migrations/20250922165147_add_chat_models/migration.sql +0 -68
  358. package/prisma/migrations/20250922165314_add_chat_roles/migration.sql +0 -5
  359. package/prisma/migrations/20250922171242_refactor_notification_system/migration.sql +0 -20
  360. package/prisma/migrations/20250922172208_add_mentions_system/migration.sql +0 -21
  361. package/prisma/migrations/20250925072732_add_lab_chat_system/migration.sql +0 -25
  362. package/prisma/migrations/20251020151505_add_upload_tracking_fields/migration.sql +0 -57
  363. package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +0 -30
  364. package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +0 -35
  365. package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +0 -14
  366. package/prisma/migrations/20260415103500_add_session_type/migration.sql +0 -33
  367. package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +0 -19
  368. package/prisma/migrations/20260417083000_add_conversation_member_hidden_at/migration.sql +0 -2
  369. package/prisma/migrations/20260502230500_assignment_submission_comments/migration.sql +0 -60
  370. package/prisma/migrations/20260504071500_school_memberships/migration.sql +0 -81
  371. package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +0 -166
  372. package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +0 -184
  373. package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +0 -93
  374. package/prisma/migrations/20260510183000_add_discussions/migration.sql +0 -151
  375. /package/.github/workflows/{npm.yml → npm-publish.yml} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"folder.js","sources":["services/folder.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Folder service — single source of truth for folders across every scope.\n *\n * A `Folder` can live inside one of four scopes (see `models/cloudStorage.ts`):\n * class files, school storage, school documents, or a user's personal storage.\n * Permissions and ownership are derived from the scope by walking up the tree\n * to the root, so callers can either pass a `scope` (when there is no folder\n * id yet, e.g. browsing the root) or just a `folderId` (everything else).\n *\n * The class files page and the global cloud-storage view both consume these\n * endpoints — no duplicate routers.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n CloudScope,\n createSubFolder,\n deleteFolderById,\n findFolderById,\n findFolderWithContents,\n findOrCreateScopeRoot,\n listFolderBreadcrumbs,\n renameFolder,\n resolveFolderScope,\n} from \"../models/cloudStorage.js\";\nimport {\n createDirectUploadFiles,\n type DirectFileData,\n confirmDirectUpload,\n} from \"../lib/fileUpload.js\";\nimport {\n type AccessLevel,\n getScopeDefault,\n listSharedWithMe,\n requireFolderAccess as requireFolderAccessResolved,\n} from \"./permissions.js\";\n\ninterface AccessResult {\n scope: CloudScope;\n canWrite: boolean;\n /**\n * True when the viewer's access comes through an explicit grant rather than\n * scope membership. Used by `browseFolder` to surface a \"Shared with me\"\n * heading instead of the underlying scope's title — the grantee shouldn't\n * see the folder labelled with the owner's scope.\n */\n viaGrant: boolean;\n}\n\n/**\n * Scope-default-only check: used when there is no folder id yet (browsing or\n * creating at the scope root). Does *not* consult explicit grants — those only\n * apply to specific folders. Explicit-grant access to sub-folders surfaces via\n * the \"Shared with me\" virtual scope, not by being a scope member.\n */\nasync function requireScopeAccess(\n userId: string,\n scope: CloudScope,\n level: AccessLevel\n): Promise<AccessResult> {\n if (scope.kind === \"CLASS\") {\n const classExists = await prisma.class.findFirst({\n where: { id: scope.classId },\n select: { id: true },\n });\n if (!classExists) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n }\n const granted = await getScopeDefault(userId, scope);\n if (!granted) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"You don't have access to this class\"\n : scope.kind === \"SCHOOL_STORAGE\"\n ? \"You aren't a member of this institution\"\n : \"You can only access your own personal storage\",\n });\n }\n if (level === \"WRITE\" && granted !== \"WRITE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"Only class teachers can modify class files\"\n : \"Only teachers and admins can modify institution storage\",\n });\n }\n // Scope-default-only path; access cannot be via a grant here.\n return { scope, canWrite: granted === \"WRITE\", viaGrant: false };\n}\n\n/**\n * Folder-level access check. Delegates to the central resolver which consults\n * explicit grants in addition to the scope default. Returns the scope and a\n * canWrite flag indicating whether the user has WRITE here (used by the UI to\n * gate the \"New folder\" / \"Upload\" buttons during a READ-level browse).\n *\n * Pass `mutatesSelf: true` for actions that target the folder itself (rename,\n * delete, move-this-folder). Drive-style: explicit grants don't authorize\n * those — only scope-default WRITE does. Mutations of children should NOT\n * pass this flag.\n */\nexport async function requireFolderAccess(\n userId: string,\n folderId: string,\n level: AccessLevel,\n opts: { mutatesSelf?: boolean } = {}\n): Promise<AccessResult> {\n const result = await requireFolderAccessResolved(userId, folderId, level, opts);\n if (!result.scope) {\n // resolveFolderScope returns null only for missing folders; the resolver\n // would have already thrown NOT_FOUND in that case.\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n let canWrite = result.scopeDefault === \"WRITE\";\n if (!canWrite && level !== \"WRITE\") {\n // We came in with READ; check whether the user *also* has WRITE so the UI\n // can show the right affordances. Cheaper to do this only when needed.\n try {\n await requireFolderAccessResolved(userId, folderId, \"WRITE\");\n canWrite = true;\n } catch {\n canWrite = false;\n }\n } else if (level === \"WRITE\") {\n canWrite = true;\n }\n return { scope: result.scope, canWrite, viaGrant: result.viaGrant };\n}\n\nfunction scopesEqual(a: CloudScope, b: CloudScope): boolean {\n if (a.kind !== b.kind) return false;\n switch (a.kind) {\n case \"CLASS\":\n return a.classId === (b as typeof a).classId;\n case \"SCHOOL_STORAGE\":\n return a.schoolId === (b as typeof a).schoolId;\n case \"PERSONAL\":\n return a.userId === (b as typeof a).userId;\n }\n}\n\n/** Browse a folder. Pass `folderId` to drill into a specific folder, or a `scope` to land on its root. */\nexport async function browseFolder(\n userId: string,\n input: { folderId: string } | { scope: CloudScope }\n) {\n let scope: CloudScope;\n let folderId: string | undefined;\n let canWrite: boolean;\n let viaGrant: boolean;\n\n if (\"folderId\" in input) {\n folderId = input.folderId;\n // `requireFolderAccess` consults explicit grants in addition to the scope\n // default, so users can drill into folders shared with them via \"Shared\n // with me\" without being scope members.\n const access = await requireFolderAccess(userId, folderId, \"READ\");\n scope = access.scope;\n canWrite = access.canWrite;\n viaGrant = access.viaGrant;\n } else {\n scope = input.scope;\n const access = await requireScopeAccess(userId, scope, \"READ\");\n canWrite = access.canWrite;\n viaGrant = false;\n }\n\n const targetId = folderId ?? (await findOrCreateScopeRoot(scope)).id;\n\n const [folder, breadcrumbs] = await Promise.all([\n findFolderWithContents(targetId),\n listFolderBreadcrumbs(targetId, userId),\n ]);\n\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n\n return {\n folder,\n breadcrumbs,\n canWrite,\n scope,\n /**\n * When the viewer's access comes through an explicit grant (not scope\n * membership), the UI should label the root as \"Shared with me\" rather\n * than the underlying scope's title. The grantee may not even be a\n * member of that scope — showing \"My Files\" or \"Class Files\" would be\n * misleading. Null when the viewer is a regular scope member.\n */\n displayRoot: viaGrant\n ? { kind: \"SHARED_WITH_ME\" as const, name: \"Shared with me\" }\n : null,\n };\n}\n\n/**\n * Drive-style \"Shared with me\" — surfaces every top-most folder/file the user\n * has an explicit grant on (filtering out anything already covered by their\n * scope defaults). Shaped like a folder so the existing folder-browser UI can\n * render it without special cases.\n *\n * The pseudo-folder has a synthetic id (`shared-with-me`) and the foreign-key\n * columns set to null — the UI should never try to mutate it directly. The\n * union shape matches `browseFolder`'s return type so the same client query\n * can render either.\n */\nexport async function browseSharedWithMe(userId: string) {\n const { folders } = await listSharedWithMe(userId);\n return {\n folder: {\n id: \"shared-with-me\",\n name: \"Shared with me\",\n color: null,\n classId: null,\n schoolStorageId: null,\n personalOwnerId: null,\n scope: \"GENERIC\" as const,\n parentFolderId: null,\n parentFolder: null,\n childFolders: folders.map((f) => ({\n id: f.id,\n name: f.name,\n color: f.color,\n _count: { files: 0, childFolders: 0 },\n })),\n // No file-level grants in v1 — files always live under a granted folder.\n files: [] as {\n id: string;\n name: string;\n type: string;\n size: number | null;\n path: string;\n uploadedAt: Date | null;\n user: { id: string; username: string } | null;\n }[],\n },\n breadcrumbs: [{ id: \"shared-with-me\", name: \"Shared with me\" }],\n canWrite: false,\n scope: { kind: \"PERSONAL\" as const, userId } satisfies CloudScope,\n /** Same `displayRoot` shape as `browseFolder` so the client union is uniform. */\n displayRoot: {\n kind: \"SHARED_WITH_ME\" as const,\n name: \"Shared with me\",\n },\n };\n}\n\nexport async function getRootFolder(userId: string, scope: CloudScope) {\n const result = await browseFolder(userId, { scope });\n return result.folder;\n}\n\nexport async function getFolder(userId: string, folderId: string) {\n const result = await browseFolder(userId, { folderId });\n return result.folder;\n}\n\nexport async function getFolderBreadcrumbs(userId: string, folderId: string) {\n await requireFolderAccess(userId, folderId, \"READ\");\n return listFolderBreadcrumbs(folderId);\n}\n\nexport async function getFolderParents(userId: string, folderId: string) {\n // Permission: user can read this folder.\n await requireFolderAccess(userId, folderId, \"READ\");\n const breadcrumbs = await listFolderBreadcrumbs(folderId);\n // Existing API returned full folder records — re-fetch them.\n return Promise.all(\n breadcrumbs.map((b) => findFolderById(b.id))\n );\n}\n\nexport async function createFolderRecord(\n userId: string,\n input: {\n name: string;\n parentFolderId?: string | null;\n color?: string;\n scope?: CloudScope;\n }\n) {\n let parentId: string;\n if (input.parentFolderId) {\n parentId = input.parentFolderId;\n await requireFolderAccess(userId, parentId, \"WRITE\");\n } else {\n if (!input.scope) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"scope is required when parentFolderId is omitted\",\n });\n }\n await requireScopeAccess(userId, input.scope, \"WRITE\");\n parentId = (await findOrCreateScopeRoot(input.scope)).id;\n }\n return createSubFolder({\n name: input.name.trim(),\n parentFolderId: parentId,\n color: input.color,\n });\n}\n\nexport async function updateFolderRecord(\n userId: string,\n input: { folderId: string; name?: string; color?: string }\n) {\n const folder = await findFolderById(input.folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot rename a root folder\",\n });\n }\n // Rename targets the folder itself — Drive-style guard reserves this for\n // scope-default WRITE (explicit grants don't authorize renaming the share).\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n return renameFolder(\n input.folderId,\n input.name?.trim() ?? folder.name,\n input.color\n );\n}\n\nexport async function deleteFolder(userId: string, folderId: string) {\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot delete a root folder\",\n });\n }\n // Delete targets the folder itself — same Drive-style guard as rename.\n await requireFolderAccess(userId, folderId, \"WRITE\", { mutatesSelf: true });\n await deleteFolderById(folderId);\n return { success: true };\n}\n\nexport async function moveFolder(\n userId: string,\n input: { folderId: string; targetParentFolderId: string }\n) {\n if (input.folderId === input.targetParentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into itself\",\n });\n }\n const [sourceScope, targetScope] = await Promise.all([\n resolveFolderScope(input.folderId),\n resolveFolderScope(input.targetParentFolderId),\n ]);\n if (!sourceScope || !targetScope) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!scopesEqual(sourceScope, targetScope)) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move folders between scopes\",\n });\n }\n // Moving the folder targets it (and the new parent's contents). Source-side\n // is mutates-self; target-side is regular WRITE on the new parent.\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n await requireFolderAccess(userId, input.targetParentFolderId, \"WRITE\");\n\n // Don't let a folder land under one of its own descendants.\n let cursor: string | null = input.targetParentFolderId;\n while (cursor) {\n if (cursor === input.folderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into its own descendant\",\n });\n }\n const node = await prisma.folder.findUnique({\n where: { id: cursor },\n select: { parentFolderId: true },\n });\n cursor = node?.parentFolderId ?? null;\n }\n\n await prisma.folder.update({\n where: { id: input.folderId },\n data: { parentFolderId: input.targetParentFolderId },\n });\n return { success: true };\n}\n\nexport async function getFolderUploadUrls(\n userId: string,\n input: { folderId: string; files: DirectFileData[] }\n) {\n await requireFolderAccess(userId, input.folderId, \"WRITE\");\n return createDirectUploadFiles(input.files, userId, input.folderId);\n}\n\nexport async function confirmFolderUpload(\n _userId: string,\n input: { fileId: string; uploadSuccess: boolean; errorMessage?: string }\n) {\n // Permission was already checked at upload-url issuance — the file record\n // exists with PENDING status, and the user holds the session id needed to\n // PUT bytes. Confirming flips status, so we just trust the call.\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n return { success: true };\n}\n\n/** What scopes can this user pick from in cloud-storage UIs? */\nexport async function listUserScopes(userId: string) {\n const memberships = await prisma.schoolMembership.findMany({\n where: { userId, status: \"ACTIVE\" },\n select: {\n role: true,\n school: {\n select: { id: true, name: true, logoId: true },\n },\n },\n orderBy: { createdAt: \"asc\" },\n });\n\n const classes = await prisma.class.findMany({\n where: {\n OR: [\n { teachers: { some: { id: userId } } },\n { students: { some: { id: userId } } },\n ],\n },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n schoolId: true,\n },\n orderBy: { name: \"asc\" },\n });\n\n // Light counts for the \"Shared with me\" bucket so the UI can hide the entry\n // when there's nothing to show. Cheap to compute — just counts top-most\n // grants, not folder/file metadata.\n const sharedWithMe = await listSharedWithMe(userId);\n\n return {\n personal: { kind: \"PERSONAL\" as const, userId },\n schools: memberships.map((m) => ({\n id: m.school.id,\n name: m.school.name,\n logoId: m.school.logoId,\n role: m.role,\n })),\n classes,\n sharedWithMe: {\n kind: \"SHARED_WITH_ME\" as const,\n folderCount: sharedWithMe.folders.length,\n fileCount: sharedWithMe.files.length,\n },\n };\n}\n"],"names":[],"mappings":"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","debug_id":"338c145a-d8eb-5931-9a42-2a9c7c9acc94"}
1
+ {"version":3,"file":"folder.js","sources":["services/folder.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Folder service — single source of truth for folders across every scope.\n *\n * A `Folder` can live inside one of four scopes (see `models/cloudStorage.ts`):\n * class files, school storage, school documents, or a user's personal storage.\n * Permissions and ownership are derived from the scope by walking up the tree\n * to the root, so callers can either pass a `scope` (when there is no folder\n * id yet, e.g. browsing the root) or just a `folderId` (everything else).\n *\n * The class files page and the global cloud-storage view both consume these\n * endpoints — no duplicate routers.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n CloudScope,\n createSubFolder,\n deleteFolderById,\n findFolderById,\n findFolderWithContents,\n findOrCreateScopeRoot,\n listFolderBreadcrumbs,\n renameFolder,\n resolveFolderScope,\n} from \"../models/cloudStorage.js\";\nimport {\n createDirectUploadFiles,\n type DirectFileData,\n confirmDirectUpload,\n} from \"../lib/fileUpload.js\";\nimport { enqueueEmbedding } from \"./embedding.js\";\nimport {\n type AccessLevel,\n getScopeDefault,\n listSharedWithMe,\n requireFolderAccess as requireFolderAccessResolved,\n} from \"./permissions.js\";\n\ninterface AccessResult {\n scope: CloudScope;\n canWrite: boolean;\n /**\n * True when the viewer's access comes through an explicit grant rather than\n * scope membership. Used by `browseFolder` to surface a \"Shared with me\"\n * heading instead of the underlying scope's title — the grantee shouldn't\n * see the folder labelled with the owner's scope.\n */\n viaGrant: boolean;\n}\n\n/**\n * Scope-default-only check: used when there is no folder id yet (browsing or\n * creating at the scope root). Does *not* consult explicit grants — those only\n * apply to specific folders. Explicit-grant access to sub-folders surfaces via\n * the \"Shared with me\" virtual scope, not by being a scope member.\n */\nasync function requireScopeAccess(\n userId: string,\n scope: CloudScope,\n level: AccessLevel\n): Promise<AccessResult> {\n if (scope.kind === \"CLASS\") {\n const classExists = await prisma.class.findFirst({\n where: { id: scope.classId },\n select: { id: true },\n });\n if (!classExists) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n }\n const granted = await getScopeDefault(userId, scope);\n if (!granted) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"You don't have access to this class\"\n : scope.kind === \"SCHOOL_STORAGE\"\n ? \"You aren't a member of this institution\"\n : \"You can only access your own personal storage\",\n });\n }\n if (level === \"WRITE\" && granted !== \"WRITE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"Only class teachers can modify class files\"\n : \"Only teachers and admins can modify institution storage\",\n });\n }\n // Scope-default-only path; access cannot be via a grant here.\n return { scope, canWrite: granted === \"WRITE\", viaGrant: false };\n}\n\n/**\n * Folder-level access check. Delegates to the central resolver which consults\n * explicit grants in addition to the scope default. Returns the scope and a\n * canWrite flag indicating whether the user has WRITE here (used by the UI to\n * gate the \"New folder\" / \"Upload\" buttons during a READ-level browse).\n *\n * Pass `mutatesSelf: true` for actions that target the folder itself (rename,\n * delete, move-this-folder). Drive-style: explicit grants don't authorize\n * those — only scope-default WRITE does. Mutations of children should NOT\n * pass this flag.\n */\nexport async function requireFolderAccess(\n userId: string,\n folderId: string,\n level: AccessLevel,\n opts: { mutatesSelf?: boolean } = {}\n): Promise<AccessResult> {\n const result = await requireFolderAccessResolved(userId, folderId, level, opts);\n if (!result.scope) {\n // resolveFolderScope returns null only for missing folders; the resolver\n // would have already thrown NOT_FOUND in that case.\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n let canWrite = result.scopeDefault === \"WRITE\";\n if (!canWrite && level !== \"WRITE\") {\n // We came in with READ; check whether the user *also* has WRITE so the UI\n // can show the right affordances. Cheaper to do this only when needed.\n try {\n await requireFolderAccessResolved(userId, folderId, \"WRITE\");\n canWrite = true;\n } catch {\n canWrite = false;\n }\n } else if (level === \"WRITE\") {\n canWrite = true;\n }\n return { scope: result.scope, canWrite, viaGrant: result.viaGrant };\n}\n\nfunction scopesEqual(a: CloudScope, b: CloudScope): boolean {\n if (a.kind !== b.kind) return false;\n switch (a.kind) {\n case \"CLASS\":\n return a.classId === (b as typeof a).classId;\n case \"SCHOOL_STORAGE\":\n return a.schoolId === (b as typeof a).schoolId;\n case \"PERSONAL\":\n return a.userId === (b as typeof a).userId;\n }\n}\n\n/** Browse a folder. Pass `folderId` to drill into a specific folder, or a `scope` to land on its root. */\nexport async function browseFolder(\n userId: string,\n input: { folderId: string } | { scope: CloudScope }\n) {\n let scope: CloudScope;\n let folderId: string | undefined;\n let canWrite: boolean;\n let viaGrant: boolean;\n\n if (\"folderId\" in input) {\n folderId = input.folderId;\n // `requireFolderAccess` consults explicit grants in addition to the scope\n // default, so users can drill into folders shared with them via \"Shared\n // with me\" without being scope members.\n const access = await requireFolderAccess(userId, folderId, \"READ\");\n scope = access.scope;\n canWrite = access.canWrite;\n viaGrant = access.viaGrant;\n } else {\n scope = input.scope;\n const access = await requireScopeAccess(userId, scope, \"READ\");\n canWrite = access.canWrite;\n viaGrant = false;\n }\n\n const targetId = folderId ?? (await findOrCreateScopeRoot(scope)).id;\n\n const [folder, breadcrumbs] = await Promise.all([\n findFolderWithContents(targetId),\n listFolderBreadcrumbs(targetId, userId),\n ]);\n\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n\n return {\n folder,\n breadcrumbs,\n canWrite,\n scope,\n /**\n * When the viewer's access comes through an explicit grant (not scope\n * membership), the UI should label the root as \"Shared with me\" rather\n * than the underlying scope's title. The grantee may not even be a\n * member of that scope — showing \"My Files\" or \"Class Files\" would be\n * misleading. Null when the viewer is a regular scope member.\n */\n displayRoot: viaGrant\n ? { kind: \"SHARED_WITH_ME\" as const, name: \"Shared with me\" }\n : null,\n };\n}\n\n/**\n * Drive-style \"Shared with me\" — surfaces every top-most folder/file the user\n * has an explicit grant on (filtering out anything already covered by their\n * scope defaults). Shaped like a folder so the existing folder-browser UI can\n * render it without special cases.\n *\n * The pseudo-folder has a synthetic id (`shared-with-me`) and the foreign-key\n * columns set to null — the UI should never try to mutate it directly. The\n * union shape matches `browseFolder`'s return type so the same client query\n * can render either.\n */\nexport async function browseSharedWithMe(userId: string) {\n const { folders } = await listSharedWithMe(userId);\n return {\n folder: {\n id: \"shared-with-me\",\n name: \"Shared with me\",\n color: null,\n classId: null,\n schoolStorageId: null,\n personalOwnerId: null,\n scope: \"GENERIC\" as const,\n parentFolderId: null,\n parentFolder: null,\n childFolders: folders.map((f) => ({\n id: f.id,\n name: f.name,\n color: f.color,\n _count: { files: 0, childFolders: 0 },\n })),\n // No file-level grants in v1 — files always live under a granted folder.\n files: [] as {\n id: string;\n name: string;\n type: string;\n size: number | null;\n path: string;\n uploadedAt: Date | null;\n user: { id: string; username: string } | null;\n }[],\n },\n breadcrumbs: [{ id: \"shared-with-me\", name: \"Shared with me\" }],\n canWrite: false,\n scope: { kind: \"PERSONAL\" as const, userId } satisfies CloudScope,\n /** Same `displayRoot` shape as `browseFolder` so the client union is uniform. */\n displayRoot: {\n kind: \"SHARED_WITH_ME\" as const,\n name: \"Shared with me\",\n },\n };\n}\n\nexport async function getRootFolder(userId: string, scope: CloudScope) {\n const result = await browseFolder(userId, { scope });\n return result.folder;\n}\n\nexport async function getFolder(userId: string, folderId: string) {\n const result = await browseFolder(userId, { folderId });\n return result.folder;\n}\n\nexport async function getFolderBreadcrumbs(userId: string, folderId: string) {\n await requireFolderAccess(userId, folderId, \"READ\");\n return listFolderBreadcrumbs(folderId);\n}\n\nexport async function getFolderParents(userId: string, folderId: string) {\n // Permission: user can read this folder.\n await requireFolderAccess(userId, folderId, \"READ\");\n const breadcrumbs = await listFolderBreadcrumbs(folderId);\n // Existing API returned full folder records — re-fetch them.\n return Promise.all(\n breadcrumbs.map((b) => findFolderById(b.id))\n );\n}\n\nexport async function createFolderRecord(\n userId: string,\n input: {\n name: string;\n parentFolderId?: string | null;\n color?: string;\n scope?: CloudScope;\n }\n) {\n let parentId: string;\n if (input.parentFolderId) {\n parentId = input.parentFolderId;\n await requireFolderAccess(userId, parentId, \"WRITE\");\n } else {\n if (!input.scope) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"scope is required when parentFolderId is omitted\",\n });\n }\n await requireScopeAccess(userId, input.scope, \"WRITE\");\n parentId = (await findOrCreateScopeRoot(input.scope)).id;\n }\n return createSubFolder({\n name: input.name.trim(),\n parentFolderId: parentId,\n color: input.color,\n });\n}\n\nexport async function updateFolderRecord(\n userId: string,\n input: { folderId: string; name?: string; color?: string }\n) {\n const folder = await findFolderById(input.folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot rename a root folder\",\n });\n }\n // Rename targets the folder itself — Drive-style guard reserves this for\n // scope-default WRITE (explicit grants don't authorize renaming the share).\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n return renameFolder(\n input.folderId,\n input.name?.trim() ?? folder.name,\n input.color\n );\n}\n\nexport async function deleteFolder(userId: string, folderId: string) {\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot delete a root folder\",\n });\n }\n // Delete targets the folder itself — same Drive-style guard as rename.\n await requireFolderAccess(userId, folderId, \"WRITE\", { mutatesSelf: true });\n await deleteFolderById(folderId);\n return { success: true };\n}\n\nexport async function moveFolder(\n userId: string,\n input: { folderId: string; targetParentFolderId: string }\n) {\n if (input.folderId === input.targetParentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into itself\",\n });\n }\n const [sourceScope, targetScope] = await Promise.all([\n resolveFolderScope(input.folderId),\n resolveFolderScope(input.targetParentFolderId),\n ]);\n if (!sourceScope || !targetScope) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!scopesEqual(sourceScope, targetScope)) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move folders between scopes\",\n });\n }\n // Moving the folder targets it (and the new parent's contents). Source-side\n // is mutates-self; target-side is regular WRITE on the new parent.\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n await requireFolderAccess(userId, input.targetParentFolderId, \"WRITE\");\n\n // Don't let a folder land under one of its own descendants.\n let cursor: string | null = input.targetParentFolderId;\n while (cursor) {\n if (cursor === input.folderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into its own descendant\",\n });\n }\n const node = await prisma.folder.findUnique({\n where: { id: cursor },\n select: { parentFolderId: true },\n });\n cursor = node?.parentFolderId ?? null;\n }\n\n await prisma.folder.update({\n where: { id: input.folderId },\n data: { parentFolderId: input.targetParentFolderId },\n });\n return { success: true };\n}\n\nexport async function getFolderUploadUrls(\n userId: string,\n input: { folderId: string; files: DirectFileData[] }\n) {\n await requireFolderAccess(userId, input.folderId, \"WRITE\");\n return createDirectUploadFiles(input.files, userId, input.folderId);\n}\n\nexport async function confirmFolderUpload(\n _userId: string,\n input: { fileId: string; uploadSuccess: boolean; errorMessage?: string }\n) {\n // Permission was already checked at upload-url issuance — the file record\n // exists with PENDING status, and the user holds the session id needed to\n // PUT bytes. Confirming flips status, so we just trust the call.\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n\n // Index file for RAG search if it belongs to a class folder\n if (input.uploadSuccess) {\n const file = await prisma.file.findUnique({\n where: { id: input.fileId },\n select: { folderId: true },\n });\n if (file?.folderId) {\n const classId = await resolveFileClassId(file.folderId);\n if (classId) {\n enqueueEmbedding(classId, 'FILE', input.fileId);\n }\n }\n }\n\n return { success: true };\n}\n\n/** Walk up folder tree to find classId, if any. */\nasync function resolveFileClassId(folderId: string): Promise<string | null> {\n let currentId: string | null = folderId;\n while (currentId) {\n const folder = await prisma.folder.findUnique({\n where: { id: currentId },\n select: { classId: true, parentFolderId: true },\n });\n if (!folder) return null;\n if (folder.classId) return folder.classId;\n currentId = folder.parentFolderId;\n }\n return null;\n}\n\n/** What scopes can this user pick from in cloud-storage UIs? */\nexport async function listUserScopes(userId: string) {\n const memberships = await prisma.schoolMembership.findMany({\n where: { userId, status: \"ACTIVE\" },\n select: {\n role: true,\n school: {\n select: { id: true, name: true, logoId: true },\n },\n },\n orderBy: { createdAt: \"asc\" },\n });\n\n const classes = await prisma.class.findMany({\n where: {\n OR: [\n { teachers: { some: { id: userId } } },\n { students: { some: { id: userId } } },\n ],\n },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n schoolId: true,\n },\n orderBy: { name: \"asc\" },\n });\n\n // Light counts for the \"Shared with me\" bucket so the UI can hide the entry\n // when there's nothing to show. Cheap to compute — just counts top-most\n // grants, not folder/file metadata.\n const sharedWithMe = await listSharedWithMe(userId);\n\n return {\n personal: { kind: \"PERSONAL\" as const, userId },\n schools: memberships.map((m) => ({\n id: m.school.id,\n name: m.school.name,\n logoId: m.school.logoId,\n role: m.role,\n })),\n classes,\n sharedWithMe: {\n kind: \"SHARED_WITH_ME\" as const,\n folderCount: sharedWithMe.folders.length,\n fileCount: sharedWithMe.files.length,\n },\n };\n}\n"],"names":[],"mappings":"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","debug_id":"0e6d72f8-42d4-50f6-907d-bbab13f2bf3f"}
@@ -5,10 +5,10 @@ export declare function createLabChat(userId: string, input: {
5
5
  context: string;
6
6
  }): Promise<{
7
7
  class: {
8
- name: string;
8
+ section: string;
9
9
  id: string;
10
+ name: string;
10
11
  subject: string;
11
- section: string;
12
12
  };
13
13
  conversation: {
14
14
  members: ({
@@ -16,8 +16,8 @@ export declare function createLabChat(userId: string, input: {
16
16
  id: string;
17
17
  username: string;
18
18
  profile: {
19
- displayName: string | null;
20
19
  profilePicture: string | null;
20
+ displayName: string | null;
21
21
  } | null;
22
22
  };
23
23
  } & {
@@ -32,10 +32,10 @@ export declare function createLabChat(userId: string, input: {
32
32
  })[];
33
33
  } & {
34
34
  type: import(".prisma/client").$Enums.ConversationType;
35
- name: string | null;
36
35
  id: string;
37
36
  createdAt: Date;
38
37
  updatedAt: Date;
38
+ name: string | null;
39
39
  displayInChat: boolean;
40
40
  };
41
41
  createdBy: {
@@ -51,17 +51,17 @@ export declare function createLabChat(userId: string, input: {
51
51
  createdAt: Date;
52
52
  updatedAt: Date;
53
53
  classId: string;
54
- conversationId: string;
55
54
  title: string;
55
+ conversationId: string;
56
56
  createdById: string;
57
57
  }>;
58
58
  export declare function getLabChat(userId: string, labChatId: string): Promise<{
59
59
  pendingGenerationMessageId: string | null;
60
60
  class: {
61
- name: string;
61
+ section: string;
62
62
  id: string;
63
+ name: string;
63
64
  subject: string;
64
- section: string;
65
65
  };
66
66
  conversation: {
67
67
  members: ({
@@ -69,8 +69,8 @@ export declare function getLabChat(userId: string, labChatId: string): Promise<{
69
69
  id: string;
70
70
  username: string;
71
71
  profile: {
72
- displayName: string | null;
73
72
  profilePicture: string | null;
73
+ displayName: string | null;
74
74
  } | null;
75
75
  };
76
76
  } & {
@@ -85,18 +85,18 @@ export declare function getLabChat(userId: string, labChatId: string): Promise<{
85
85
  })[];
86
86
  } & {
87
87
  type: import(".prisma/client").$Enums.ConversationType;
88
- name: string | null;
89
88
  id: string;
90
89
  createdAt: Date;
91
90
  updatedAt: Date;
91
+ name: string | null;
92
92
  displayInChat: boolean;
93
93
  };
94
94
  createdBy: {
95
95
  id: string;
96
96
  username: string;
97
97
  profile: {
98
- displayName: string | null;
99
98
  profilePicture: string | null;
99
+ displayName: string | null;
100
100
  } | null;
101
101
  };
102
102
  context: string;
@@ -104,8 +104,8 @@ export declare function getLabChat(userId: string, labChatId: string): Promise<{
104
104
  createdAt: Date;
105
105
  updatedAt: Date;
106
106
  classId: string;
107
- conversationId: string;
108
107
  title: string;
108
+ conversationId: string;
109
109
  createdById: string;
110
110
  }>;
111
111
  export declare function listLabChats(userId: string, classId: string): Promise<{
@@ -135,9 +135,9 @@ export declare function listLabChats(userId: string, classId: string): Promise<{
135
135
  meta: import("@prisma/client/runtime/library.js").JsonValue | null;
136
136
  id: string;
137
137
  createdAt: Date;
138
- conversationId: string;
139
138
  content: string;
140
139
  senderId: string;
140
+ conversationId: string;
141
141
  runJobId: string | null;
142
142
  };
143
143
  messageCount: number;
@@ -158,23 +158,23 @@ export declare function postToLabChat(userId: string, input: {
158
158
  id: string;
159
159
  username: string;
160
160
  profile: {
161
- displayName: string | null;
162
161
  profilePicture: string | null;
162
+ displayName: string | null;
163
163
  } | null;
164
164
  };
165
165
  attachments: {
166
166
  path: string;
167
167
  type: string;
168
- name: string;
169
168
  id: string;
169
+ name: string;
170
170
  size: number | null;
171
171
  uploadedAt: Date | null;
172
- uploadStatus: import(".prisma/client").$Enums.UploadStatus;
173
- thumbnailId: string | null;
174
172
  thumbnail: {
175
173
  path: string;
176
174
  id: string;
177
175
  } | null;
176
+ thumbnailId: string | null;
177
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
178
178
  }[];
179
179
  mentionedUserIds: string[];
180
180
  }>;
@@ -18,27 +18,14 @@ export declare function createSchoolDevelopmentProgramRecord(input: {
18
18
  }): Promise<{
19
19
  id: string;
20
20
  }>;
21
- /** Look up a school development program by ID. */
21
+ /** Public application status lookup (no contact PII). */
22
22
  export declare function searchSchoolDevelopmentPrograms(id: string): Promise<{
23
- type: string;
24
23
  status: string;
25
- name: string;
26
24
  id: string;
25
+ name: string;
27
26
  submittedAt: Date | null;
28
- website: string | null;
29
- address: string;
30
27
  city: string;
31
28
  country: string;
32
- numberOfStudents: number;
33
- numberOfTeachers: number;
34
- contactName: string | null;
35
- contactRole: string | null;
36
- contactEmail: string | null;
37
- contactPhone: string | null;
38
- eligibilityInformation: string | null;
39
- whyHelp: string | null;
40
- additionalInformation: string | null;
41
- reviewedAt: Date | null;
42
29
  } | null>;
43
30
  /** Create an early access request. */
44
31
  export declare function createEarlyAccessRequestRecord(input: {
@@ -1 +1 @@
1
- {"version":3,"file":"marketing.d.ts","sourceRoot":"/","sources":["services/marketing.ts"],"names":[],"mappings":"AAUA,4EAA4E;AAC5E,wBAAsB,oCAAoC,CAAC,KAAK,EAAE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;;GAeA;AAED,kDAAkD;AAClD,wBAAsB,+BAA+B,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;UAE/D;AAED,sCAAsC;AACtC,wBAAsB,8BAA8B,CAAC,KAAK,EAAE;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;CACzB;;GAGA"}
1
+ {"version":3,"file":"marketing.d.ts","sourceRoot":"/","sources":["services/marketing.ts"],"names":[],"mappings":"AAUA,4EAA4E;AAC5E,wBAAsB,oCAAoC,CAAC,KAAK,EAAE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;;GAeA;AAED,yDAAyD;AACzD,wBAAsB,+BAA+B,CAAC,EAAE,EAAE,MAAM;;;;;;;UAE/D;AAED,sCAAsC;AACtC,wBAAsB,8BAA8B,CAAC,KAAK,EAAE;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;CACzB;;GAGA"}
@@ -2,9 +2,9 @@
2
2
  * Marketing service – school development programs and early access requests.
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]="a1e245e4-bd37-50ba-b330-218c8f3789ea")}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]="d5fcb374-2047-58b8-8257-6cca05559642")}catch(e){}}();
6
6
  import { v4 } from "uuid";
7
- import { createSchoolDevelopmentProgram, findSchoolDevelopmentProgramById, createEarlyAccessRequest, } from "../models/marketing.js";
7
+ import { createSchoolDevelopmentProgram, findSchoolDevelopmentProgramStatusById, createEarlyAccessRequest, } from "../models/marketing.js";
8
8
  /** Create a school development program application. Generates unique ID. */
9
9
  export async function createSchoolDevelopmentProgramRecord(input) {
10
10
  const date = new Date();
@@ -19,9 +19,9 @@ export async function createSchoolDevelopmentProgramRecord(input) {
19
19
  });
20
20
  return { id: program.id };
21
21
  }
22
- /** Look up a school development program by ID. */
22
+ /** Public application status lookup (no contact PII). */
23
23
  export async function searchSchoolDevelopmentPrograms(id) {
24
- return findSchoolDevelopmentProgramById(id);
24
+ return findSchoolDevelopmentProgramStatusById(id);
25
25
  }
26
26
  /** Create an early access request. */
27
27
  export async function createEarlyAccessRequestRecord(input) {
@@ -29,4 +29,4 @@ export async function createEarlyAccessRequestRecord(input) {
29
29
  return { id: record.id };
30
30
  }
31
31
  //# sourceMappingURL=marketing.js.map
32
- //# debugId=a1e245e4-bd37-50ba-b330-218c8f3789ea
32
+ //# debugId=d5fcb374-2047-58b8-8257-6cca05559642
@@ -1 +1 @@
1
- {"version":3,"file":"marketing.js","sources":["services/marketing.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Marketing service – school development programs and early access requests.\n */\nimport { v4 } from \"uuid\";\nimport {\n createSchoolDevelopmentProgram,\n findSchoolDevelopmentProgramById,\n createEarlyAccessRequest,\n} from \"../models/marketing.js\";\n\n/** Create a school development program application. Generates unique ID. */\nexport async function createSchoolDevelopmentProgramRecord(input: {\n name: string;\n type: string;\n address: string;\n city: string;\n country: string;\n numberOfStudents: number;\n numberOfTeachers: number;\n website?: string;\n contactName?: string;\n contactRole?: string;\n contactEmail?: string;\n contactPhone?: string;\n eligibilityInformation?: string;\n whyHelp?: string;\n additionalInformation?: string;\n}) {\n const date = new Date();\n const id =\n input.name.slice(0, 3).toUpperCase() +\n \"-\" +\n date.getFullYear() +\n \"-\" +\n v4().slice(0, 4).toUpperCase();\n\n const program = await createSchoolDevelopmentProgram({\n id,\n ...input,\n });\n\n return { id: program.id };\n}\n\n/** Look up a school development program by ID. */\nexport async function searchSchoolDevelopmentPrograms(id: string) {\n return findSchoolDevelopmentProgramById(id);\n}\n\n/** Create an early access request. */\nexport async function createEarlyAccessRequestRecord(input: {\n email: string;\n institutionSize: string;\n}) {\n const record = await createEarlyAccessRequest(input);\n return { id: record.id };\n}\n"],"names":[],"mappings":"AAAA;;GAEG;;;AACH,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAEhC,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,oCAAoC,CAAC,KAgB1D;IACC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACxB,MAAM,EAAE,GACN,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE;QACpC,GAAG;QACH,IAAI,CAAC,WAAW,EAAE;QAClB,GAAG;QACH,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAEjC,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC;QACnD,EAAE;QACF,GAAG,KAAK;KACT,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;AAC5B,CAAC;AAED,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAAC,EAAU;IAC9D,OAAO,gCAAgC,CAAC,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,sCAAsC;AACtC,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,KAGpD;IACC,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC,KAAK,CAAC,CAAC;IACrD,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;AAC3B,CAAC","debug_id":"a1e245e4-bd37-50ba-b330-218c8f3789ea"}
1
+ {"version":3,"file":"marketing.js","sources":["services/marketing.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Marketing service – school development programs and early access requests.\n */\nimport { v4 } from \"uuid\";\nimport {\n createSchoolDevelopmentProgram,\n findSchoolDevelopmentProgramStatusById,\n createEarlyAccessRequest,\n} from \"../models/marketing.js\";\n\n/** Create a school development program application. Generates unique ID. */\nexport async function createSchoolDevelopmentProgramRecord(input: {\n name: string;\n type: string;\n address: string;\n city: string;\n country: string;\n numberOfStudents: number;\n numberOfTeachers: number;\n website?: string;\n contactName?: string;\n contactRole?: string;\n contactEmail?: string;\n contactPhone?: string;\n eligibilityInformation?: string;\n whyHelp?: string;\n additionalInformation?: string;\n}) {\n const date = new Date();\n const id =\n input.name.slice(0, 3).toUpperCase() +\n \"-\" +\n date.getFullYear() +\n \"-\" +\n v4().slice(0, 4).toUpperCase();\n\n const program = await createSchoolDevelopmentProgram({\n id,\n ...input,\n });\n\n return { id: program.id };\n}\n\n/** Public application status lookup (no contact PII). */\nexport async function searchSchoolDevelopmentPrograms(id: string) {\n return findSchoolDevelopmentProgramStatusById(id);\n}\n\n/** Create an early access request. */\nexport async function createEarlyAccessRequestRecord(input: {\n email: string;\n institutionSize: string;\n}) {\n const record = await createEarlyAccessRequest(input);\n return { id: record.id };\n}\n"],"names":[],"mappings":"AAAA;;GAEG;;;AACH,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EACL,8BAA8B,EAC9B,sCAAsC,EACtC,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAEhC,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,oCAAoC,CAAC,KAgB1D;IACC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACxB,MAAM,EAAE,GACN,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE;QACpC,GAAG;QACH,IAAI,CAAC,WAAW,EAAE;QAClB,GAAG;QACH,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAEjC,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC;QACnD,EAAE;QACF,GAAG,KAAK;KACT,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;AAC5B,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAAC,EAAU;IAC9D,OAAO,sCAAsC,CAAC,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,sCAAsC;AACtC,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,KAGpD;IACC,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC,KAAK,CAAC,CAAC;IACrD,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;AAC3B,CAAC","debug_id":"d5fcb374-2047-58b8-8257-6cca05559642"}
@@ -2,9 +2,9 @@
2
2
  * Message service – send, update, delete messages; mark as read; track mentions.
3
3
  * Broadcasts real-time updates via Pusher.
4
4
  */
5
- import type { Prisma } from "@prisma/client";
5
+ import type { AppTransactionClient } from "../lib/prisma.js";
6
6
  export declare function ensureMessageHasContentOrAttachments(content: string, fileIds?: string[]): void;
7
- export declare function claimMessageAttachments(tx: Prisma.TransactionClient, userId: string, conversationId: string, messageId: string, fileIds?: string[]): Promise<void>;
7
+ export declare function claimMessageAttachments(tx: AppTransactionClient, userId: string, conversationId: string, messageId: string, fileIds?: string[]): Promise<void>;
8
8
  export declare function getMessageUploadUrls(userId: string, input: {
9
9
  conversationId: string;
10
10
  files: {
@@ -38,24 +38,24 @@ export declare function listMessages(userId: string, input: {
38
38
  id: string;
39
39
  username: string;
40
40
  profile: {
41
- displayName: string | null;
42
41
  profilePicture: string | null;
42
+ displayName: string | null;
43
43
  } | null;
44
44
  };
45
45
  status: import(".prisma/client").$Enums.JobStatus | null;
46
46
  attachments: {
47
47
  path: string;
48
48
  type: string;
49
- name: string;
50
49
  id: string;
50
+ name: string;
51
51
  size: number | null;
52
52
  uploadedAt: Date | null;
53
- uploadStatus: import(".prisma/client").$Enums.UploadStatus;
54
- thumbnailId: string | null;
55
53
  thumbnail: {
56
54
  path: string;
57
55
  id: string;
58
56
  } | null;
57
+ thumbnailId: string | null;
58
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
59
59
  }[];
60
60
  meta: Record<string, unknown>;
61
61
  mentions: {
@@ -88,23 +88,23 @@ export declare function sendMessage(userId: string, input: {
88
88
  id: string;
89
89
  username: string;
90
90
  profile: {
91
- displayName: string | null;
92
91
  profilePicture: string | null;
92
+ displayName: string | null;
93
93
  } | null;
94
94
  };
95
95
  attachments: {
96
96
  path: string;
97
97
  type: string;
98
- name: string;
99
98
  id: string;
99
+ name: string;
100
100
  size: number | null;
101
101
  uploadedAt: Date | null;
102
- uploadStatus: import(".prisma/client").$Enums.UploadStatus;
103
- thumbnailId: string | null;
104
102
  thumbnail: {
105
103
  path: string;
106
104
  id: string;
107
105
  } | null;
106
+ thumbnailId: string | null;
107
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
108
108
  }[];
109
109
  mentionedUserIds: string[];
110
110
  }>;
@@ -123,8 +123,8 @@ export declare function updateMessage(userId: string, input: {
123
123
  id: string;
124
124
  username: string;
125
125
  profile: {
126
- displayName: string | null;
127
126
  profilePicture: string | null;
127
+ displayName: string | null;
128
128
  } | null;
129
129
  };
130
130
  mentionedUserIds: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"/","sources":["services/message.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAoB7C,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,EAAO,QAQvB;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,MAAM,CAAC,iBAAiB,EAC5B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAM,EAAO,iBA2BvB;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD,8DAmBF;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;;GAuCF;AAED,oEAAoE;AACpE,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiCyB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;;GAMlD;AAED,oFAAoF;AACpF,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkIF;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;;;;;;;;;;;;;;;;GAqHF;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;GAuDpE;AAUD,mFAAmF;AACnF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,SAAS,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC;CACf;;GA8HF;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;;GA+BtE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM;;GAgCvB;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM;;;GA8BvB"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"/","sources":["services/message.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAoB7D,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,EAAO,QAQvB;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAM,EAAO,iBA2BvB;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD,8DAmBF;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;;GAuCF;AAED,oEAAoE;AACpE,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiCyB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;;GAMlD;AAED,oFAAoF;AACpF,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkIF;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;;;;;;;;;;;;;;;;GAqHF;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;GAuDpE;AAUD,mFAAmF;AACnF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,SAAS,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC;CACf;;GA8HF;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;;GA+BtE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM;;GAgCvB;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM;;;GA8BvB"}
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a0453833-3ac8-5c02-9d64-8a457397f108")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="fbce11f4-499e-5d7a-90a5-2b01d5c0082f")}catch(e){}}();
3
3
  import { TRPCError } from "@trpc/server";
4
4
  import { prisma } from "../lib/prisma.js";
5
5
  import { chatChannel, pusher } from "../lib/pusher.js";
@@ -546,4 +546,4 @@ export async function getUnreadCount(userId, conversationId) {
546
546
  return { unreadCount, unreadMentionCount };
547
547
  }
548
548
  //# sourceMappingURL=message.js.map
549
- //# debugId=a0453833-3ac8-5c02-9d64-8a457397f108
549
+ //# debugId=fbce11f4-499e-5d7a-90a5-2b01d5c0082f