@goscribe/server 1.3.4 → 1.6.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 (383) hide show
  1. package/.env.example +12 -0
  2. package/.vscode/settings.json +3 -0
  3. package/REFACTOR_NOTES.md +60 -0
  4. package/TESTING_PROMPT.md +225 -0
  5. package/dist/context.d.ts +14 -1
  6. package/dist/context.js +23 -2
  7. package/dist/controllers/admin.controller.d.ts +715 -0
  8. package/dist/controllers/admin.controller.js +9 -0
  9. package/dist/controllers/annotations.controller.d.ts +439 -0
  10. package/dist/controllers/annotations.controller.js +9 -0
  11. package/dist/controllers/app-router.controller.d.ts +3011 -0
  12. package/dist/controllers/app-router.controller.js +38 -0
  13. package/dist/controllers/app-router.controller.test.d.ts +1 -0
  14. package/dist/controllers/app-router.controller.test.js +36 -0
  15. package/dist/controllers/auth.controller.d.ts +323 -0
  16. package/dist/controllers/auth.controller.js +9 -0
  17. package/dist/controllers/base.controller.d.ts +4 -0
  18. package/dist/controllers/base.controller.js +5 -0
  19. package/dist/controllers/chat.controller.d.ts +341 -0
  20. package/dist/controllers/chat.controller.js +9 -0
  21. package/dist/controllers/copilot.controller.d.ts +397 -0
  22. package/dist/controllers/copilot.controller.js +9 -0
  23. package/dist/controllers/flashcards.controller.d.ts +651 -0
  24. package/dist/controllers/flashcards.controller.js +9 -0
  25. package/dist/controllers/members.controller.d.ts +339 -0
  26. package/dist/controllers/members.controller.js +9 -0
  27. package/dist/controllers/notifications.controller.d.ts +199 -0
  28. package/dist/controllers/notifications.controller.js +9 -0
  29. package/dist/controllers/payment.controller.d.ts +181 -0
  30. package/dist/controllers/payment.controller.js +9 -0
  31. package/dist/controllers/podcast.controller.d.ts +575 -0
  32. package/dist/controllers/podcast.controller.js +9 -0
  33. package/dist/controllers/router-module.controller.d.ts +5 -0
  34. package/dist/controllers/router-module.controller.js +6 -0
  35. package/dist/controllers/studyguide.controller.d.ts +73 -0
  36. package/dist/controllers/studyguide.controller.js +9 -0
  37. package/dist/controllers/worksheets.controller.d.ts +829 -0
  38. package/dist/controllers/worksheets.controller.js +9 -0
  39. package/dist/controllers/workspace.controller.d.ts +1207 -0
  40. package/dist/controllers/workspace.controller.js +9 -0
  41. package/dist/lib/activity_human_description.test.js +16 -15
  42. package/dist/lib/activity_log_service.test.js +28 -23
  43. package/dist/lib/ai/config.d.ts +20 -0
  44. package/dist/lib/ai/config.js +31 -0
  45. package/dist/lib/ai/embedding-client.d.ts +8 -0
  46. package/dist/lib/ai/embedding-client.js +30 -0
  47. package/dist/lib/ai/index.d.ts +48 -0
  48. package/dist/lib/ai/index.js +29 -0
  49. package/dist/lib/ai/inference-backend/client.d.ts +28 -0
  50. package/dist/lib/ai/inference-backend/client.js +301 -0
  51. package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
  52. package/dist/lib/ai/inference-backend/mocks.js +133 -0
  53. package/dist/lib/ai/inference-backend/types.d.ts +44 -0
  54. package/dist/lib/ai/inference-backend/types.js +1 -0
  55. package/dist/lib/ai/json-parse.d.ts +2 -0
  56. package/dist/lib/ai/json-parse.js +34 -0
  57. package/dist/lib/ai/llm-client.d.ts +7 -0
  58. package/dist/lib/ai/llm-client.js +36 -0
  59. package/dist/lib/ai/mock.d.ts +2 -0
  60. package/dist/lib/ai/mock.js +10 -0
  61. package/dist/lib/ai/types.d.ts +9 -0
  62. package/dist/lib/ai/types.js +1 -0
  63. package/dist/lib/chunking.d.ts +19 -0
  64. package/dist/lib/chunking.js +47 -0
  65. package/dist/lib/curated-kb-seed.d.ts +12 -0
  66. package/dist/lib/curated-kb-seed.js +155 -0
  67. package/dist/lib/email.js +67 -108
  68. package/dist/lib/embeddings.d.ts +2 -0
  69. package/dist/lib/embeddings.js +1 -0
  70. package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
  71. package/dist/lib/ensure-curated-kb-catalog.js +53 -0
  72. package/dist/lib/env.d.ts +1 -5
  73. package/dist/lib/env.js +2 -7
  74. package/dist/lib/inference.d.ts +1 -8
  75. package/dist/lib/inference.js +1 -19
  76. package/dist/lib/kb-meta.d.ts +8 -0
  77. package/dist/lib/kb-meta.js +77 -0
  78. package/dist/lib/note-text.d.ts +1 -0
  79. package/dist/lib/note-text.js +47 -0
  80. package/dist/lib/notification-service.test.js +37 -36
  81. package/dist/lib/pdf.d.ts +11 -0
  82. package/dist/lib/pdf.js +11 -0
  83. package/dist/lib/usage_service.d.ts +2 -1
  84. package/dist/lib/usage_service.js +30 -12
  85. package/dist/lib/worksheet-generation.js +4 -4
  86. package/dist/lib/worksheet-generation.test.js +32 -17
  87. package/dist/lib/workspace-kb.d.ts +5 -0
  88. package/dist/lib/workspace-kb.js +7 -0
  89. package/dist/models/controller-context.model.d.ts +8 -0
  90. package/dist/models/controller-context.model.js +1 -0
  91. package/dist/repositories/artifact.repository.d.ts +60 -0
  92. package/dist/repositories/artifact.repository.js +40 -0
  93. package/dist/repositories/base.repository.d.ts +14 -0
  94. package/dist/repositories/base.repository.js +14 -0
  95. package/dist/repositories/invitation.repository.d.ts +94 -0
  96. package/dist/repositories/invitation.repository.js +44 -0
  97. package/dist/repositories/notification.repository.d.ts +72 -0
  98. package/dist/repositories/notification.repository.js +44 -0
  99. package/dist/repositories/router-module.repository.d.ts +10 -0
  100. package/dist/repositories/router-module.repository.js +14 -0
  101. package/dist/repositories/user.repository.d.ts +74 -0
  102. package/dist/repositories/user.repository.js +37 -0
  103. package/dist/repositories/workspace-member.repository.d.ts +31 -0
  104. package/dist/repositories/workspace-member.repository.js +31 -0
  105. package/dist/repositories/workspace.repository.d.ts +97 -0
  106. package/dist/repositories/workspace.repository.js +79 -0
  107. package/dist/routers/_app.d.ts +566 -111
  108. package/dist/routers/_app.js +4 -0
  109. package/dist/routers/admin.d.ts +0 -4
  110. package/dist/routers/admin.js +21 -549
  111. package/dist/routers/annotations.js +12 -170
  112. package/dist/routers/artifactVersions.d.ts +65 -0
  113. package/dist/routers/artifactVersions.js +14 -0
  114. package/dist/routers/auth.d.ts +0 -6
  115. package/dist/routers/auth.js +37 -422
  116. package/dist/routers/chat.js +15 -229
  117. package/dist/routers/copilot.d.ts +14 -13
  118. package/dist/routers/copilot.js +13 -532
  119. package/dist/routers/flashcards.d.ts +17 -6
  120. package/dist/routers/flashcards.js +23 -349
  121. package/dist/routers/knowledgeBase.d.ts +421 -0
  122. package/dist/routers/knowledgeBase.js +118 -0
  123. package/dist/routers/members.d.ts +0 -41
  124. package/dist/routers/members.js +22 -710
  125. package/dist/routers/notes.d.ts +94 -0
  126. package/dist/routers/notes.js +37 -0
  127. package/dist/routers/notifications.js +7 -109
  128. package/dist/routers/payment.d.ts +2 -12
  129. package/dist/routers/payment.js +11 -393
  130. package/dist/routers/podcast.d.ts +1 -1
  131. package/dist/routers/podcast.js +11 -784
  132. package/dist/routers/studyguide.js +3 -129
  133. package/dist/routers/worksheets.d.ts +29 -14
  134. package/dist/routers/worksheets.js +49 -628
  135. package/dist/routers/workspace.d.ts +27 -71
  136. package/dist/routers/workspace.js +29 -922
  137. package/dist/scripts/purge-deleted-users.js +2 -2
  138. package/dist/server.js +10 -3
  139. package/dist/services/activity/activity-human-description.service.d.ts +13 -0
  140. package/dist/services/activity/activity-human-description.service.js +221 -0
  141. package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
  142. package/dist/services/activity/activity-human-description.service.test.js +16 -0
  143. package/dist/services/activity/activity-log.service.d.ts +87 -0
  144. package/dist/services/activity/activity-log.service.js +276 -0
  145. package/dist/services/activity/activity-log.service.test.d.ts +1 -0
  146. package/dist/services/activity/activity-log.service.test.js +27 -0
  147. package/dist/services/activity-human-description.service.d.ts +13 -0
  148. package/dist/services/activity-human-description.service.js +221 -0
  149. package/dist/services/activity-human-description.service.test.d.ts +1 -0
  150. package/dist/services/activity-human-description.service.test.js +16 -0
  151. package/dist/services/activity-log.service.d.ts +87 -0
  152. package/dist/services/activity-log.service.js +276 -0
  153. package/dist/services/activity-log.service.test.d.ts +1 -0
  154. package/dist/services/activity-log.service.test.js +27 -0
  155. package/dist/services/admin/admin.service.d.ts +270 -0
  156. package/dist/services/admin/admin.service.js +476 -0
  157. package/dist/services/admin.service.d.ts +270 -0
  158. package/dist/services/admin.service.js +476 -0
  159. package/dist/services/ai/ai-session.service.d.ts +5 -0
  160. package/dist/services/ai/ai-session.service.js +4 -0
  161. package/dist/services/ai-session.service.d.ts +60 -0
  162. package/dist/services/ai-session.service.js +561 -0
  163. package/dist/services/annotation.service.d.ts +177 -0
  164. package/dist/services/annotation.service.js +154 -0
  165. package/dist/services/artifact-notification.service.d.ts +14 -0
  166. package/dist/services/artifact-notification.service.js +20 -0
  167. package/dist/services/artifact-version.service.d.ts +38 -0
  168. package/dist/services/artifact-version.service.js +129 -0
  169. package/dist/services/artifacts/annotation.service.d.ts +177 -0
  170. package/dist/services/artifacts/annotation.service.js +154 -0
  171. package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
  172. package/dist/services/artifacts/artifact-version.service.js +129 -0
  173. package/dist/services/artifacts/chat.service.d.ts +127 -0
  174. package/dist/services/artifacts/chat.service.js +182 -0
  175. package/dist/services/artifacts/study-guide.service.d.ts +18 -0
  176. package/dist/services/artifacts/study-guide.service.js +65 -0
  177. package/dist/services/auth/auth.service.d.ts +94 -0
  178. package/dist/services/auth/auth.service.js +368 -0
  179. package/dist/services/auth.service.d.ts +94 -0
  180. package/dist/services/auth.service.js +368 -0
  181. package/dist/services/base.service.d.ts +14 -0
  182. package/dist/services/base.service.js +14 -0
  183. package/dist/services/billing/payment.service.d.ts +44 -0
  184. package/dist/services/billing/payment.service.js +365 -0
  185. package/dist/services/billing/subscription.service.d.ts +37 -0
  186. package/dist/services/billing/subscription.service.js +654 -0
  187. package/dist/services/billing/usage.service.d.ts +47 -0
  188. package/dist/services/billing/usage.service.js +149 -0
  189. package/dist/services/chat.service.d.ts +127 -0
  190. package/dist/services/chat.service.js +182 -0
  191. package/dist/services/content/copilot.service.d.ts +113 -0
  192. package/dist/services/content/copilot.service.js +439 -0
  193. package/dist/services/content/flashcard-progress.service.d.ts +159 -0
  194. package/dist/services/content/flashcard-progress.service.js +432 -0
  195. package/dist/services/content/flashcard.service.d.ts +184 -0
  196. package/dist/services/content/flashcard.service.js +339 -0
  197. package/dist/services/content/media-analysis.service.d.ts +23 -0
  198. package/dist/services/content/media-analysis.service.js +404 -0
  199. package/dist/services/content/podcast.service.d.ts +267 -0
  200. package/dist/services/content/podcast.service.js +653 -0
  201. package/dist/services/content/worksheet-content.service.d.ts +37 -0
  202. package/dist/services/content/worksheet-content.service.js +84 -0
  203. package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
  204. package/dist/services/content/worksheet-content.service.test.js +69 -0
  205. package/dist/services/content/worksheet-generation.service.d.ts +91 -0
  206. package/dist/services/content/worksheet-generation.service.js +95 -0
  207. package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
  208. package/dist/services/content/worksheet-generation.service.test.js +20 -0
  209. package/dist/services/content/worksheet.service.d.ts +347 -0
  210. package/dist/services/content/worksheet.service.js +599 -0
  211. package/dist/services/copilot.service.d.ts +116 -0
  212. package/dist/services/copilot.service.js +447 -0
  213. package/dist/services/flashcard-progress.service.d.ts +2 -2
  214. package/dist/services/flashcard-progress.service.js +3 -2
  215. package/dist/services/flashcard.service.d.ts +140 -0
  216. package/dist/services/flashcard.service.js +325 -0
  217. package/dist/services/invitation.service.d.ts +66 -0
  218. package/dist/services/invitation.service.js +348 -0
  219. package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
  220. package/dist/services/knowledge/knowledge-base.service.js +544 -0
  221. package/dist/services/knowledge-base.service.d.ts +316 -0
  222. package/dist/services/knowledge-base.service.js +536 -0
  223. package/dist/services/media-analysis.service.d.ts +23 -0
  224. package/dist/services/media-analysis.service.js +384 -0
  225. package/dist/services/member.service.d.ts +36 -0
  226. package/dist/services/member.service.js +193 -0
  227. package/dist/services/members/invitation.service.d.ts +66 -0
  228. package/dist/services/members/invitation.service.js +348 -0
  229. package/dist/services/members/member.service.d.ts +36 -0
  230. package/dist/services/members/member.service.js +193 -0
  231. package/dist/services/note.service.d.ts +55 -0
  232. package/dist/services/note.service.js +111 -0
  233. package/dist/services/notification.service.d.ts +214 -0
  234. package/dist/services/notification.service.js +550 -0
  235. package/dist/services/notification.service.test.d.ts +1 -0
  236. package/dist/services/notification.service.test.js +87 -0
  237. package/dist/services/notifications/notification.service.d.ts +214 -0
  238. package/dist/services/notifications/notification.service.js +550 -0
  239. package/dist/services/notifications/notification.service.test.d.ts +1 -0
  240. package/dist/services/notifications/notification.service.test.js +87 -0
  241. package/dist/services/payment.service.d.ts +55 -0
  242. package/dist/services/payment.service.js +368 -0
  243. package/dist/services/podcast.service.d.ts +267 -0
  244. package/dist/services/podcast.service.js +654 -0
  245. package/dist/services/router-module.service.d.ts +7 -0
  246. package/dist/services/router-module.service.js +10 -0
  247. package/dist/services/study-guide.service.d.ts +18 -0
  248. package/dist/services/study-guide.service.js +65 -0
  249. package/dist/services/subscription.service.d.ts +37 -0
  250. package/dist/services/subscription.service.js +654 -0
  251. package/dist/services/usage-limit-policy.service.d.ts +12 -0
  252. package/dist/services/usage-limit-policy.service.js +22 -0
  253. package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
  254. package/dist/services/usage-limit-policy.service.test.js +46 -0
  255. package/dist/services/usage.service.d.ts +27 -0
  256. package/dist/services/usage.service.js +77 -0
  257. package/dist/services/worksheet-content.service.d.ts +42 -0
  258. package/dist/services/worksheet-content.service.js +84 -0
  259. package/dist/services/worksheet-content.service.test.d.ts +1 -0
  260. package/dist/services/worksheet-content.service.test.js +69 -0
  261. package/dist/services/worksheet-generation.service.d.ts +91 -0
  262. package/dist/services/worksheet-generation.service.js +95 -0
  263. package/dist/services/worksheet-generation.service.test.d.ts +1 -0
  264. package/dist/services/worksheet-generation.service.test.js +20 -0
  265. package/dist/services/worksheet.service.d.ts +385 -0
  266. package/dist/services/worksheet.service.js +596 -0
  267. package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
  268. package/dist/services/workspace/workspace-analytics.service.js +95 -0
  269. package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
  270. package/dist/services/workspace/workspace-kb.service.js +184 -0
  271. package/dist/services/workspace/workspace.service.d.ts +263 -0
  272. package/dist/services/workspace/workspace.service.js +401 -0
  273. package/dist/services/workspace-analytics.service.d.ts +24 -0
  274. package/dist/services/workspace-analytics.service.js +95 -0
  275. package/dist/services/workspace-kb.service.d.ts +40 -0
  276. package/dist/services/workspace-kb.service.js +184 -0
  277. package/dist/services/workspace-progress.service.d.ts +27 -0
  278. package/dist/services/workspace-progress.service.js +56 -0
  279. package/dist/services/workspace-progress.service.test.d.ts +1 -0
  280. package/dist/services/workspace-progress.service.test.js +49 -0
  281. package/dist/services/workspace.service.d.ts +307 -0
  282. package/dist/services/workspace.service.js +390 -0
  283. package/dist/trpc.d.ts +12 -4
  284. package/dist/trpc.js +7 -13
  285. package/package.json +5 -6
  286. package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
  287. package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
  288. package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
  289. package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
  290. package/prisma/schema.prisma +150 -48
  291. package/prisma/seed.mjs +67 -0
  292. package/scripts/debug/README.md +4 -0
  293. package/src/README.md +63 -0
  294. package/src/context.ts +33 -3
  295. package/src/lib/ai/config.ts +34 -0
  296. package/src/lib/ai/embedding-client.ts +47 -0
  297. package/src/lib/ai/index.ts +65 -0
  298. package/src/lib/ai/inference-backend/client.ts +479 -0
  299. package/src/lib/ai/inference-backend/mocks.ts +171 -0
  300. package/src/lib/ai/inference-backend/types.ts +50 -0
  301. package/src/lib/ai/json-parse.ts +35 -0
  302. package/src/lib/ai/llm-client.ts +54 -0
  303. package/src/lib/ai/mock.ts +12 -0
  304. package/src/lib/ai/types.ts +11 -0
  305. package/src/lib/chunking.ts +81 -0
  306. package/src/lib/curated-kb-seed.ts +164 -0
  307. package/src/lib/email.ts +77 -115
  308. package/src/lib/embeddings.ts +9 -0
  309. package/src/lib/ensure-curated-kb-catalog.ts +60 -0
  310. package/src/lib/env.ts +2 -7
  311. package/src/lib/inference.ts +1 -21
  312. package/src/lib/kb-meta.ts +81 -0
  313. package/src/lib/pdf.ts +23 -0
  314. package/src/lib/workspace-kb.ts +7 -0
  315. package/src/repositories/artifact.repository.ts +55 -0
  316. package/src/repositories/base.repository.ts +19 -0
  317. package/src/repositories/invitation.repository.ts +53 -0
  318. package/src/repositories/notification.repository.ts +53 -0
  319. package/src/repositories/user.repository.ts +44 -0
  320. package/src/repositories/workspace-member.repository.ts +38 -0
  321. package/src/repositories/workspace.repository.ts +89 -0
  322. package/src/routers/_app.ts +4 -0
  323. package/src/routers/admin.ts +124 -692
  324. package/src/routers/annotations.ts +25 -203
  325. package/src/routers/artifactVersions.ts +32 -0
  326. package/src/routers/auth.ts +82 -520
  327. package/src/routers/chat.ts +42 -245
  328. package/src/routers/copilot.ts +41 -666
  329. package/src/routers/flashcards.ts +108 -404
  330. package/src/routers/knowledgeBase.ts +216 -0
  331. package/src/routers/members.ts +60 -782
  332. package/src/routers/notifications.ts +15 -117
  333. package/src/routers/payment.ts +37 -446
  334. package/src/routers/podcast.ts +36 -898
  335. package/src/routers/studyguide.ts +5 -144
  336. package/src/routers/worksheets.ts +171 -735
  337. package/src/routers/workspace.ts +141 -1108
  338. package/src/scripts/purge-deleted-users.ts +2 -2
  339. package/src/server.ts +10 -3
  340. package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
  341. package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
  342. package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
  343. package/src/services/admin/admin.service.ts +612 -0
  344. package/src/services/ai/ai-session.service.ts +5 -0
  345. package/src/services/artifacts/annotation.service.ts +189 -0
  346. package/src/services/artifacts/artifact-version.service.ts +151 -0
  347. package/src/services/artifacts/chat.service.ts +197 -0
  348. package/src/services/artifacts/study-guide.service.ts +72 -0
  349. package/src/services/auth/auth.service.ts +473 -0
  350. package/src/services/base.service.ts +19 -0
  351. package/src/services/billing/payment.service.ts +433 -0
  352. package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
  353. package/src/services/billing/usage.service.ts +207 -0
  354. package/src/services/content/copilot.service.ts +587 -0
  355. package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +18 -12
  356. package/src/services/content/flashcard.service.ts +417 -0
  357. package/src/services/content/media-analysis.service.ts +561 -0
  358. package/src/services/content/podcast.service.ts +777 -0
  359. package/src/services/content/worksheet-content.service.test.ts +83 -0
  360. package/src/services/content/worksheet-content.service.ts +117 -0
  361. package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +3 -3
  362. package/src/services/content/worksheet.service.ts +751 -0
  363. package/src/services/knowledge/knowledge-base.service.ts +705 -0
  364. package/src/services/members/invitation.service.ts +427 -0
  365. package/src/services/members/member.service.ts +241 -0
  366. package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
  367. package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
  368. package/src/services/workspace/workspace-analytics.service.ts +107 -0
  369. package/src/services/workspace/workspace-kb.service.ts +273 -0
  370. package/src/services/workspace/workspace.service.ts +488 -0
  371. package/src/trpc.ts +7 -15
  372. package/src/lib/ai-session.ts +0 -704
  373. package/src/lib/usage_service.ts +0 -74
  374. package/src/lib/workspace-access.ts +0 -13
  375. /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
  376. /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
  377. /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
  378. /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
  379. /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
  380. /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
  381. /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
  382. /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
  383. /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
@@ -1,807 +1,85 @@
1
1
  import { z } from 'zod';
2
- import { TRPCError } from '@trpc/server';
3
2
  import { router, publicProcedure, authedProcedure } from '../trpc.js';
4
- import { logger } from '../lib/logger.js';
5
- import { sendInvitationEmail } from '../lib/email.js';
6
- import PusherService from '../lib/pusher.js';
7
- import {
8
- notifyInviteAccepted,
9
- notifyInviteRecipient,
10
- notifyWorkspaceMembershipRemoved,
11
- notifyWorkspaceRoleChanged,
12
- } from '../lib/notification-service.js';
3
+ import { MemberService } from '../services/members/member.service.js';
4
+ import { InvitationService } from '../services/members/invitation.service.js';
13
5
 
14
- /**
15
- * Members router for workspace member management
16
- *
17
- * Features:
18
- * - Get workspace members
19
- * - Invite new members via email
20
- * - Accept invitations via UUID
21
- * - Change member roles
22
- * - Remove members
23
- * - Get current user's role
24
- */
25
6
  export const members = router({
26
- /**
27
- * Get all members of a workspace
28
- */
29
7
  getMembers: authedProcedure
30
- .input(z.object({
31
- workspaceId: z.string(),
32
- }))
33
- .query(async ({ ctx, input }) => {
34
- // Check if user has access to this workspace
35
- const workspace = await ctx.db.workspace.findFirst({
36
- where: {
37
- id: input.workspaceId,
38
- OR: [
39
- { ownerId: ctx.session.user.id },
40
- { members: { some: { userId: ctx.session.user.id } } }
41
- ]
42
- },
43
- include: {
44
- owner: {
45
- select: {
46
- id: true,
47
- name: true,
48
- email: true,
49
- profilePicture: {
50
- select: {
51
- id: true,
52
- name: true,
53
- url: true,
54
- }
55
- }
56
- }
57
- },
58
- members: {
59
- include: {
60
- user: {
61
- select: {
62
- id: true,
63
- name: true,
64
- email: true,
65
- profilePicture: {
66
- select: {
67
- id: true,
68
- name: true,
69
- url: true,
70
- }
71
- }
72
- }
73
- }
74
- }
75
- }
76
- }
77
- });
8
+ .input(z.object({ workspaceId: z.string() }))
9
+ .query(({ ctx, input }) =>
10
+ new MemberService(ctx.db).getMembers(ctx.session.user.id, input.workspaceId),
11
+ ),
78
12
 
79
- if (!workspace) {
80
- throw new TRPCError({
81
- code: 'NOT_FOUND',
82
- message: 'Workspace not found or access denied'
83
- });
84
- }
85
-
86
- const workspaceMembers = [
87
- {
88
- id: workspace.owner.id,
89
- name: workspace.owner.name || 'Unknown',
90
- email: workspace.owner.email || '',
91
- role: 'owner' as const,
92
- joinedAt: workspace.createdAt,
93
- },
94
- ...workspace.members.map(membership => ({
95
- id: membership.user.id,
96
- name: membership.user.name || 'Unknown',
97
- email: membership.user.email || '',
98
- role: membership.role as 'admin' | 'member',
99
- joinedAt: membership.joinedAt,
100
- }))
101
- ];
102
-
103
- logger.info(`👥 Fetched ${workspaceMembers.length} members for workspace ${input.workspaceId}`, 'WORKSPACE', {
104
- memberEmails: workspaceMembers.map(m => m.email)
105
- });
106
-
107
- return workspaceMembers;
108
- }),
109
-
110
- /**
111
- * Get current user's role in a workspace
112
- */
113
13
  getCurrentUserRole: authedProcedure
114
- .input(z.object({
115
- workspaceId: z.string(),
116
- }))
117
- .query(async ({ ctx, input }) => {
118
- const workspace = await ctx.db.workspace.findFirst({
119
- where: { id: input.workspaceId },
120
- select: {
121
- ownerId: true,
122
- members: {
123
- where: { userId: ctx.session.user.id },
124
- select: { role: true }
125
- }
126
- }
127
- });
128
-
129
- if (!workspace) {
130
- throw new TRPCError({
131
- code: 'NOT_FOUND',
132
- message: 'Workspace not found'
133
- });
134
- }
135
-
136
- if (workspace.ownerId === ctx.session.user.id) {
137
- return 'owner';
138
- }
139
-
140
- if (workspace.members.length > 0) {
141
- return workspace.members[0].role as 'admin' | 'member';
142
- }
143
-
144
- throw new TRPCError({
145
- code: 'FORBIDDEN',
146
- message: 'Access denied to this workspace'
147
- });
148
- }),
14
+ .input(z.object({ workspaceId: z.string() }))
15
+ .query(({ ctx, input }) =>
16
+ new MemberService(ctx.db).getCurrentUserRole(ctx.session.user.id, input.workspaceId),
17
+ ),
149
18
 
150
- /**
151
- * Invite a new member to the workspace
152
- */
153
19
  inviteMember: authedProcedure
154
- .input(z.object({
155
- workspaceId: z.string(),
156
- email: z.string().email(),
157
- role: z.enum(['admin', 'member']).default('member'),
158
- }))
159
- .mutation(async ({ ctx, input }) => {
160
- // Check if user is owner or admin of the workspace
161
- const workspace = await ctx.db.workspace.findFirst({
162
- where: {
163
- id: input.workspaceId,
164
- ownerId: ctx.session.user.id // Only owners can invite for now
165
- }
166
- });
167
-
168
- if (!workspace) {
169
- throw new TRPCError({
170
- code: 'NOT_FOUND',
171
- message: 'Workspace not found or insufficient permissions'
172
- });
173
- }
174
-
175
- // Check if user is already a member
176
- const existingMember = await ctx.db.user.findFirst({
177
- where: {
178
- email: input.email,
179
- OR: [
180
- { id: workspace.ownerId },
181
- { workspaceMemberships: { some: { workspaceId: input.workspaceId } } }
182
- ]
183
- }
184
- });
185
-
186
- if (existingMember) {
187
- throw new TRPCError({
188
- code: 'BAD_REQUEST',
189
- message: 'User is already a member of this workspace'
190
- });
191
- }
192
-
193
- // Check if there's already a pending invitation
194
- const existingInvitation = await ctx.db.workspaceInvitation.findFirst({
195
- where: {
196
- workspaceId: input.workspaceId,
197
- email: input.email,
198
- acceptedAt: null,
199
- expiresAt: { gt: new Date() }
200
- }
201
- });
202
-
203
- if (existingInvitation) {
204
- throw new TRPCError({
205
- code: 'BAD_REQUEST',
206
- message: 'Invitation already sent to this email'
207
- });
208
- }
209
-
210
- // Create invitation
211
- const invitation = await ctx.db.workspaceInvitation.create({
212
- data: {
213
- workspaceId: input.workspaceId,
214
- email: input.email,
215
- role: input.role,
216
- invitedById: ctx.session.user.id,
217
- },
218
- include: {
219
- workspace: {
220
- select: {
221
- title: true,
222
- owner: {
223
- select: {
224
- name: true,
225
- email: true,
226
- }
227
- }
228
- }
229
- }
230
- }
231
- });
232
-
233
- const invitedExistingUser = await ctx.db.user.findUnique({
234
- where: { email: input.email },
235
- select: { id: true, name: true },
236
- });
237
-
238
- if (invitedExistingUser) {
239
- await notifyInviteRecipient(ctx.db, {
240
- invitedUserId: invitedExistingUser.id,
241
- inviterUserId: ctx.session.user.id,
242
- workspaceId: input.workspaceId,
243
- workspaceTitle: invitation.workspace.title,
244
- invitationId: invitation.id,
245
- invitationToken: invitation.token,
246
- inviterName: invitation.workspace.owner.name || invitation.workspace.owner.email,
247
- });
248
- }
20
+ .input(
21
+ z.object({
22
+ workspaceId: z.string(),
23
+ email: z.string().email(),
24
+ role: z.enum(['admin', 'member']).default('member'),
25
+ }),
26
+ )
27
+ .mutation(({ ctx, input }) =>
28
+ new InvitationService(ctx.db).inviteMember(ctx.session.user.id, input),
29
+ ),
249
30
 
250
- logger.info(`🎫 Invitation created for ${input.email} to workspace ${input.workspaceId} with role ${input.role}`, 'WORKSPACE', {
251
- invitationId: invitation.id,
252
- workspaceId: input.workspaceId,
253
- email: input.email,
254
- role: input.role,
255
- invitedBy: ctx.session.user.id
256
- });
257
-
258
- // Send email notification
259
- await sendInvitationEmail({
260
- email: invitation.email,
261
- token: invitation.token,
262
- role: invitation.role,
263
- workspaceTitle: invitation.workspace.title,
264
- invitedByName: invitation.workspace.owner.name || invitation.workspace.owner.email || 'Someone',
265
- });
266
-
267
- return {
268
- invitationId: invitation.id,
269
- token: invitation.token,
270
- email: invitation.email,
271
- role: invitation.role,
272
- expiresAt: invitation.expiresAt,
273
- workspaceTitle: invitation.workspace.title,
274
- invitedByName: invitation.workspace.owner.name || invitation.workspace.owner.email,
275
- };
276
- }),
277
31
  getInvitations: authedProcedure
278
- .input(z.object({
279
- workspaceId: z.string(),
280
- }))
281
- .query(async ({ ctx, input }) => {
282
- const invitations = await ctx.db.workspaceInvitation.findMany({
283
- where: { workspaceId: input.workspaceId },
284
- });
285
- return invitations;
286
- }),
287
- /**
288
- * Accept an invitation (public endpoint)
289
- */
290
- acceptInvite: publicProcedure
291
- .input(z.object({
292
- token: z.string(),
293
- }))
294
- .mutation(async ({ ctx, input }) => {
295
- // Find the invitation
296
- const invitation = await ctx.db.workspaceInvitation.findFirst({
297
- where: {
298
- token: input.token,
299
- acceptedAt: null,
300
- expiresAt: { gt: new Date() }
301
- },
302
- include: {
303
- workspace: {
304
- select: {
305
- id: true,
306
- title: true,
307
- ownerId: true,
308
- owner: {
309
- select: {
310
- name: true,
311
- email: true,
312
- }
313
- }
314
- }
315
- }
316
- }
317
- });
318
-
319
- if (!invitation) {
320
- throw new TRPCError({
321
- code: 'NOT_FOUND',
322
- message: 'Invalid or expired invitation'
323
- });
324
- }
325
-
326
- // Check if user is authenticated
327
- if (!ctx.session?.user) {
328
- throw new TRPCError({
329
- code: 'UNAUTHORIZED',
330
- message: 'Please log in to accept this invitation'
331
- });
332
- }
333
-
334
- const user = await ctx.db.user.findFirst({ where: { id: ctx.session.user.id } });
335
- if (!user || !user.email) throw new TRPCError({ code: 'NOT_FOUND' });
336
-
337
- logger.info(`🔍 Verification check for ${user.email} accepting invite for ${invitation.email}`, 'WORKSPACE');
338
-
339
- // Check if the email matches the user's email (case-insensitive)
340
- if (user.email.toLowerCase() !== invitation.email.toLowerCase()) {
341
- logger.warn(`❌ Invitation email mismatch: user ${user.email} vs invite ${invitation.email}`, 'WORKSPACE');
342
- throw new TRPCError({
343
- code: 'BAD_REQUEST',
344
- message: 'This invitation was sent to a different email address'
345
- });
346
- }
347
-
348
- // Check if user is already a member
349
- const isAlreadyMember = await ctx.db.workspace.findFirst({
350
- where: {
351
- id: invitation.workspaceId,
352
- OR: [
353
- { ownerId: ctx.session.user.id },
354
- { members: { some: { userId: ctx.session.user.id } } }
355
- ]
356
- }
357
- });
358
-
359
- if (isAlreadyMember) {
360
- logger.info(`ℹ️ User ${ctx.session.user.id} is already a member of workspace ${invitation.workspaceId}. Marking invite as accepted.`, 'WORKSPACE');
361
- // Mark invitation as accepted even if already a member
362
- await ctx.db.workspaceInvitation.update({
363
- where: { id: invitation.id },
364
- data: { acceptedAt: new Date() }
365
- });
366
-
367
- return {
368
- workspaceId: invitation.workspaceId,
369
- workspaceTitle: invitation.workspace.title,
370
- role: invitation.role,
371
- ownerName: invitation.workspace.owner.name || invitation.workspace.owner.email,
372
- message: 'You are already a member of this workspace'
373
- };
374
- }
375
-
376
- // Add user to workspace with proper role.
377
- // This can race if accept is triggered twice (e.g. redirects/retries),
378
- // so treat duplicate membership as a successful, idempotent accept.
379
- let memberAdded = false;
380
- try {
381
- await ctx.db.workspaceMember.create({
382
- data: {
383
- workspaceId: invitation.workspaceId,
384
- userId: ctx.session.user.id,
385
- role: invitation.role,
386
- }
387
- });
388
- memberAdded = true;
389
- } catch (error: any) {
390
- if (error?.code !== 'P2002') {
391
- throw error;
392
- }
393
- logger.info(`ℹ️ Duplicate invite accept handled for user ${ctx.session.user.id} in workspace ${invitation.workspaceId}`, 'WORKSPACE');
394
- }
395
-
396
- // Mark invitation as accepted
397
- await ctx.db.workspaceInvitation.update({
398
- where: { id: invitation.id },
399
- data: { acceptedAt: new Date() }
400
- });
401
-
402
- if (memberAdded) {
403
- await notifyInviteAccepted(ctx.db, {
404
- recipientUserIds: [invitation.invitedById, invitation.workspace.ownerId],
405
- actorUserId: ctx.session.user.id,
406
- workspaceId: invitation.workspaceId,
407
- workspaceTitle: invitation.workspace.title,
408
- memberName: user.name || user.email,
409
- invitationId: invitation.id,
410
- });
411
- }
412
-
413
- logger.info(`✅ Invitation accepted by ${ctx.session.user.id} (${user.email}) for workspace ${invitation.workspaceId}`, 'WORKSPACE', {
414
- invitationId: invitation.id,
415
- workspaceId: invitation.workspaceId,
416
- userId: ctx.session.user.id,
417
- email: invitation.email
418
- });
32
+ .input(z.object({ workspaceId: z.string() }))
33
+ .query(({ ctx, input }) => new InvitationService(ctx.db).getInvitations(input.workspaceId)),
419
34
 
420
- // Try to emit a Pusher event if possible
421
- try {
422
- if (memberAdded) {
423
- await PusherService.emitMemberJoined(invitation.workspaceId, {
424
- id: ctx.session.user.id,
425
- name: user.name || 'Member',
426
- email: user.email,
427
- role: invitation.role,
428
- });
429
- }
430
- } catch (e) {
431
- logger.error('Failed to emit member joined event', e);
432
- }
433
-
434
- return {
435
- workspaceId: invitation.workspaceId,
436
- workspaceTitle: invitation.workspace.title,
437
- role: invitation.role,
438
- ownerName: invitation.workspace.owner.name || invitation.workspace.owner.email,
439
- ...(memberAdded ? {} : { message: 'You are already a member of this workspace' }),
440
- };
441
- }),
35
+ acceptInvite: publicProcedure
36
+ .input(z.object({ token: z.string() }))
37
+ .mutation(({ ctx, input }) =>
38
+ new InvitationService(ctx.db).acceptInvite(ctx.session?.user?.id, input.token),
39
+ ),
442
40
 
443
- /**
444
- * Change a member's role (owner only)
445
- */
446
41
  changeMemberRole: authedProcedure
447
- .input(z.object({
448
- workspaceId: z.string(),
449
- memberId: z.string(),
450
- role: z.enum(['admin', 'member']),
451
- }))
452
- .mutation(async ({ ctx, input }) => {
453
- // Check if user is owner of the workspace
454
- const workspace = await ctx.db.workspace.findFirst({
455
- where: {
456
- id: input.workspaceId,
457
- ownerId: ctx.session.user.id
458
- },
459
- select: { id: true, title: true, ownerId: true },
460
- });
461
-
462
- if (!workspace) {
463
- throw new TRPCError({
464
- code: 'NOT_FOUND',
465
- message: 'Workspace not found or insufficient permissions'
466
- });
467
- }
468
-
469
- // Check if member exists and is not the owner
470
- if (input.memberId === workspace.ownerId) {
471
- throw new TRPCError({
472
- code: 'BAD_REQUEST',
473
- message: 'Cannot change owner role'
474
- });
475
- }
476
-
477
- const member = await ctx.db.workspaceMember.findFirst({
478
- where: {
479
- workspaceId: input.workspaceId,
480
- userId: input.memberId
481
- }
482
- });
483
-
484
- if (!member) {
485
- throw new TRPCError({
486
- code: 'NOT_FOUND',
487
- message: 'Member not found in this workspace'
488
- });
489
- }
490
-
491
- // Update the member's role
492
- const updatedMember = await ctx.db.workspaceMember.update({
493
- where: { id: member.id },
494
- data: { role: input.role },
495
- include: {
496
- user: {
497
- select: {
498
- id: true,
499
- name: true,
500
- email: true,
501
- }
502
- }
503
- }
504
- });
505
-
506
- logger.info(`🔄 Member role changed for ${input.memberId} from ${member.role} to ${input.role} in workspace ${input.workspaceId}`, 'WORKSPACE', {
507
- workspaceId: input.workspaceId,
508
- memberId: input.memberId,
509
- oldRole: member.role,
510
- newRole: input.role,
511
- changedBy: ctx.session.user.id
512
- });
42
+ .input(
43
+ z.object({
44
+ workspaceId: z.string(),
45
+ memberId: z.string(),
46
+ role: z.enum(['admin', 'member']),
47
+ }),
48
+ )
49
+ .mutation(({ ctx, input }) =>
50
+ new MemberService(ctx.db).changeMemberRole(ctx.session.user.id, input),
51
+ ),
513
52
 
514
- const actor = await ctx.db.user.findUnique({
515
- where: { id: ctx.session.user.id },
516
- select: { name: true, email: true },
517
- });
518
- await notifyWorkspaceRoleChanged(ctx.db, {
519
- memberUserId: input.memberId,
520
- workspaceId: input.workspaceId,
521
- workspaceTitle: workspace.title,
522
- newRole: input.role,
523
- oldRole: member.role,
524
- actorUserId: ctx.session.user.id,
525
- actorName: actor?.name || actor?.email || 'A workspace admin',
526
- }).catch(() => {});
527
-
528
- return {
529
- memberId: input.memberId,
530
- role: input.role,
531
- memberName: updatedMember.user.name || updatedMember.user.email,
532
- message: 'Role changed successfully'
533
- };
534
- }),
535
-
536
- /**
537
- * Remove a member from the workspace (owner only)
538
- */
539
53
  removeMember: authedProcedure
540
- .input(z.object({
541
- workspaceId: z.string(),
542
- memberId: z.string(),
543
- }))
544
- .mutation(async ({ ctx, input }) => {
545
- // Check if user is owner of the workspace
546
- const workspace = await ctx.db.workspace.findFirst({
547
- where: {
548
- id: input.workspaceId,
549
- ownerId: ctx.session.user.id
550
- },
551
- select: { id: true, title: true, ownerId: true },
552
- });
553
-
554
- if (!workspace) {
555
- throw new TRPCError({
556
- code: 'NOT_FOUND',
557
- message: 'Workspace not found or insufficient permissions'
558
- });
559
- }
560
-
561
- // Check if trying to remove the owner
562
- if (input.memberId === workspace.ownerId) {
563
- throw new TRPCError({
564
- code: 'BAD_REQUEST',
565
- message: 'Cannot remove workspace owner'
566
- });
567
- }
568
-
569
- // Check if member exists
570
- const member = await ctx.db.workspaceMember.findFirst({
571
- where: {
572
- workspaceId: input.workspaceId,
573
- userId: input.memberId
574
- },
575
- include: {
576
- user: {
577
- select: {
578
- name: true,
579
- email: true,
580
- }
581
- }
582
- }
583
- });
584
-
585
- if (!member) {
586
- throw new TRPCError({
587
- code: 'NOT_FOUND',
588
- message: 'Member not found in this workspace'
589
- });
590
- }
591
-
592
- const actor = await ctx.db.user.findUnique({
593
- where: { id: ctx.session.user.id },
594
- select: { name: true, email: true },
595
- });
596
- await notifyWorkspaceMembershipRemoved(ctx.db, {
597
- memberUserId: input.memberId,
598
- workspaceId: input.workspaceId,
599
- workspaceTitle: workspace.title,
600
- actorUserId: ctx.session.user.id,
601
- actorName: actor?.name || actor?.email || 'A workspace admin',
602
- }).catch(() => {});
603
-
604
- // Remove member from workspace
605
- await ctx.db.workspaceMember.delete({
606
- where: { id: member.id }
607
- });
54
+ .input(z.object({ workspaceId: z.string(), memberId: z.string() }))
55
+ .mutation(({ ctx, input }) =>
56
+ new MemberService(ctx.db).removeMember(ctx.session.user.id, input),
57
+ ),
608
58
 
609
- logger.info(`🗑️ Member ${input.memberId} removed from workspace ${input.workspaceId}`, 'WORKSPACE', {
610
- workspaceId: input.workspaceId,
611
- memberId: input.memberId,
612
- removedBy: ctx.session.user.id
613
- });
614
-
615
- return {
616
- memberId: input.memberId,
617
- message: 'Member removed successfully'
618
- };
619
- }),
620
-
621
- /**
622
- * Get pending invitations for a workspace (owner only)
623
- */
624
59
  getPendingInvitations: authedProcedure
625
- .input(z.object({
626
- workspaceId: z.string(),
627
- }))
628
- .query(async ({ ctx, input }) => {
629
- // Check if user is owner of the workspace
630
- const workspace = await ctx.db.workspace.findFirst({
631
- where: {
632
- id: input.workspaceId,
633
- ownerId: ctx.session.user.id
634
- }
635
- });
636
-
637
- if (!workspace) {
638
- throw new TRPCError({
639
- code: 'NOT_FOUND',
640
- message: 'Workspace not found or insufficient permissions'
641
- });
642
- }
643
-
644
- const invitations = await ctx.db.workspaceInvitation.findMany({
645
- where: {
646
- workspaceId: input.workspaceId,
647
- acceptedAt: null,
648
- expiresAt: { gt: new Date() }
649
- },
650
- include: {
651
- invitedBy: {
652
- select: {
653
- name: true,
654
- email: true,
655
- }
656
- }
657
- },
658
- orderBy: { createdAt: 'desc' }
659
- });
60
+ .input(z.object({ workspaceId: z.string() }))
61
+ .query(({ ctx, input }) =>
62
+ new InvitationService(ctx.db).getPendingInvitations(ctx.session.user.id, input.workspaceId),
63
+ ),
660
64
 
661
- return invitations.map(invitation => ({
662
- id: invitation.id,
663
- email: invitation.email,
664
- role: invitation.role,
665
- token: invitation.token,
666
- expiresAt: invitation.expiresAt,
667
- createdAt: invitation.createdAt,
668
- invitedByName: invitation.invitedBy.name || invitation.invitedBy.email,
669
- }));
670
- }),
671
-
672
- /**
673
- * Cancel a pending invitation (owner only)
674
- */
675
65
  cancelInvitation: authedProcedure
676
- .input(z.object({
677
- invitationId: z.string(),
678
- }))
679
- .mutation(async ({ ctx, input }) => {
680
- // Check if user is owner of the workspace
681
- const invitation = await ctx.db.workspaceInvitation.findFirst({
682
- where: {
683
- id: input.invitationId,
684
- acceptedAt: null,
685
- workspace: {
686
- ownerId: ctx.session.user.id
687
- }
688
- }
689
- });
690
-
691
- if (!invitation) {
692
- throw new TRPCError({
693
- code: 'NOT_FOUND',
694
- message: 'Invitation not found or insufficient permissions'
695
- });
696
- }
697
-
698
- // Delete the invitation
699
- await ctx.db.workspaceInvitation.delete({
700
- where: { id: input.invitationId }
701
- });
702
-
703
- logger.info(`❌ Invitation cancelled for ${invitation.email} to workspace ${invitation.workspaceId}`, 'WORKSPACE', {
704
- invitationId: input.invitationId,
705
- workspaceId: invitation.workspaceId,
706
- email: invitation.email,
707
- cancelledBy: ctx.session.user.id
708
- });
66
+ .input(z.object({ invitationId: z.string() }))
67
+ .mutation(({ ctx, input }) =>
68
+ new InvitationService(ctx.db).cancelInvitation(ctx.session.user.id, input.invitationId),
69
+ ),
709
70
 
710
- return {
711
- invitationId: input.invitationId,
712
- message: 'Invitation cancelled successfully'
713
- };
714
- }),
715
-
716
- /**
717
- * Resend a pending invitation (owner only)
718
- */
719
71
  resendInvitation: authedProcedure
720
- .input(z.object({
721
- invitationId: z.string(),
722
- }))
723
- .mutation(async ({ ctx, input }) => {
724
- // Check if user is owner of the workspace and invitation is pending
725
- const invitation = await ctx.db.workspaceInvitation.findFirst({
726
- where: {
727
- id: input.invitationId,
728
- acceptedAt: null,
729
- workspace: {
730
- ownerId: ctx.session.user.id
731
- }
732
- },
733
- include: {
734
- workspace: {
735
- select: {
736
- title: true,
737
- owner: {
738
- select: {
739
- name: true,
740
- email: true,
741
- }
742
- }
743
- }
744
- }
745
- }
746
- });
747
-
748
- if (!invitation) {
749
- throw new TRPCError({
750
- code: 'NOT_FOUND',
751
- message: 'Invitation not found or insufficient permissions'
752
- });
753
- }
754
-
755
- // Check if expired and update expiry if needed
756
- if (invitation.expiresAt < new Date()) {
757
- const newExpiry = new Date();
758
- newExpiry.setDate(newExpiry.getDate() + 7);
759
- await ctx.db.workspaceInvitation.update({
760
- where: { id: invitation.id },
761
- data: { expiresAt: newExpiry }
762
- });
763
- invitation.expiresAt = newExpiry;
764
- }
765
-
766
- // Send email notification
767
- await sendInvitationEmail({
768
- email: invitation.email,
769
- token: invitation.token,
770
- role: invitation.role,
771
- workspaceTitle: invitation.workspace.title,
772
- invitedByName: invitation.workspace.owner.name || invitation.workspace.owner.email || 'Someone',
773
- });
72
+ .input(z.object({ invitationId: z.string() }))
73
+ .mutation(({ ctx, input }) =>
74
+ new InvitationService(ctx.db).resendInvitation(ctx.session.user.id, input.invitationId),
75
+ ),
774
76
 
775
- logger.info(`📧 Invitation resent to ${invitation.email} for workspace ${invitation.workspaceId}`, 'WORKSPACE', {
776
- invitationId: invitation.id,
777
- workspaceId: invitation.workspaceId,
778
- email: invitation.email,
779
- resentBy: ctx.session.user.id
780
- });
781
-
782
- return {
783
- invitationId: invitation.id,
784
- message: 'Invitation email resent successfully'
785
- };
786
- }),
787
-
788
- /**
789
- * DEBUG ONLY: Get all invitations for a workspace
790
- */
791
77
  getAllInvitationsDebug: authedProcedure
792
- .input(z.object({
793
- workspaceId: z.string(),
794
- }))
795
- .query(async ({ ctx, input }) => {
796
- // Check if user is owner
797
- const workspace = await ctx.db.workspace.findFirst({
798
- where: { id: input.workspaceId, ownerId: ctx.session.user.id }
799
- });
800
- if (!workspace) throw new TRPCError({ code: 'UNAUTHORIZED' });
801
-
802
- return ctx.db.workspaceInvitation.findMany({
803
- where: { workspaceId: input.workspaceId },
804
- orderBy: { createdAt: 'desc' }
805
- });
806
- }),
78
+ .input(z.object({ workspaceId: z.string() }))
79
+ .query(({ ctx, input }) =>
80
+ new InvitationService(ctx.db).getAllInvitationsDebug(
81
+ ctx.session.user.id,
82
+ input.workspaceId,
83
+ ),
84
+ ),
807
85
  });