@goscribe/server 1.3.4 → 1.5.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 (378) 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/controllers/admin.controller.d.ts +715 -0
  6. package/dist/controllers/admin.controller.js +9 -0
  7. package/dist/controllers/annotations.controller.d.ts +439 -0
  8. package/dist/controllers/annotations.controller.js +9 -0
  9. package/dist/controllers/app-router.controller.d.ts +3011 -0
  10. package/dist/controllers/app-router.controller.js +38 -0
  11. package/dist/controllers/app-router.controller.test.d.ts +1 -0
  12. package/dist/controllers/app-router.controller.test.js +36 -0
  13. package/dist/controllers/auth.controller.d.ts +323 -0
  14. package/dist/controllers/auth.controller.js +9 -0
  15. package/dist/controllers/base.controller.d.ts +4 -0
  16. package/dist/controllers/base.controller.js +5 -0
  17. package/dist/controllers/chat.controller.d.ts +341 -0
  18. package/dist/controllers/chat.controller.js +9 -0
  19. package/dist/controllers/copilot.controller.d.ts +397 -0
  20. package/dist/controllers/copilot.controller.js +9 -0
  21. package/dist/controllers/flashcards.controller.d.ts +651 -0
  22. package/dist/controllers/flashcards.controller.js +9 -0
  23. package/dist/controllers/members.controller.d.ts +339 -0
  24. package/dist/controllers/members.controller.js +9 -0
  25. package/dist/controllers/notifications.controller.d.ts +199 -0
  26. package/dist/controllers/notifications.controller.js +9 -0
  27. package/dist/controllers/payment.controller.d.ts +181 -0
  28. package/dist/controllers/payment.controller.js +9 -0
  29. package/dist/controllers/podcast.controller.d.ts +575 -0
  30. package/dist/controllers/podcast.controller.js +9 -0
  31. package/dist/controllers/router-module.controller.d.ts +5 -0
  32. package/dist/controllers/router-module.controller.js +6 -0
  33. package/dist/controllers/studyguide.controller.d.ts +73 -0
  34. package/dist/controllers/studyguide.controller.js +9 -0
  35. package/dist/controllers/worksheets.controller.d.ts +829 -0
  36. package/dist/controllers/worksheets.controller.js +9 -0
  37. package/dist/controllers/workspace.controller.d.ts +1207 -0
  38. package/dist/controllers/workspace.controller.js +9 -0
  39. package/dist/lib/activity_human_description.test.js +16 -15
  40. package/dist/lib/activity_log_service.test.js +28 -23
  41. package/dist/lib/ai/config.d.ts +20 -0
  42. package/dist/lib/ai/config.js +31 -0
  43. package/dist/lib/ai/embedding-client.d.ts +8 -0
  44. package/dist/lib/ai/embedding-client.js +30 -0
  45. package/dist/lib/ai/index.d.ts +47 -0
  46. package/dist/lib/ai/index.js +28 -0
  47. package/dist/lib/ai/inference-backend/client.d.ts +28 -0
  48. package/dist/lib/ai/inference-backend/client.js +301 -0
  49. package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
  50. package/dist/lib/ai/inference-backend/mocks.js +133 -0
  51. package/dist/lib/ai/inference-backend/types.d.ts +44 -0
  52. package/dist/lib/ai/inference-backend/types.js +1 -0
  53. package/dist/lib/ai/json-parse.d.ts +2 -0
  54. package/dist/lib/ai/json-parse.js +34 -0
  55. package/dist/lib/ai/llm-client.d.ts +6 -0
  56. package/dist/lib/ai/llm-client.js +19 -0
  57. package/dist/lib/ai/mock.d.ts +2 -0
  58. package/dist/lib/ai/mock.js +10 -0
  59. package/dist/lib/ai/types.d.ts +9 -0
  60. package/dist/lib/ai/types.js +1 -0
  61. package/dist/lib/chunking.d.ts +19 -0
  62. package/dist/lib/chunking.js +47 -0
  63. package/dist/lib/curated-kb-seed.d.ts +12 -0
  64. package/dist/lib/curated-kb-seed.js +155 -0
  65. package/dist/lib/email.js +67 -108
  66. package/dist/lib/embeddings.d.ts +2 -0
  67. package/dist/lib/embeddings.js +1 -0
  68. package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
  69. package/dist/lib/ensure-curated-kb-catalog.js +53 -0
  70. package/dist/lib/env.d.ts +1 -5
  71. package/dist/lib/env.js +2 -7
  72. package/dist/lib/inference.d.ts +1 -8
  73. package/dist/lib/inference.js +1 -19
  74. package/dist/lib/kb-meta.d.ts +8 -0
  75. package/dist/lib/kb-meta.js +77 -0
  76. package/dist/lib/note-text.d.ts +1 -0
  77. package/dist/lib/note-text.js +47 -0
  78. package/dist/lib/notification-service.test.js +37 -36
  79. package/dist/lib/pdf.d.ts +11 -0
  80. package/dist/lib/pdf.js +11 -0
  81. package/dist/lib/usage_service.d.ts +2 -1
  82. package/dist/lib/usage_service.js +30 -12
  83. package/dist/lib/worksheet-generation.js +4 -4
  84. package/dist/lib/worksheet-generation.test.js +32 -17
  85. package/dist/lib/workspace-kb.d.ts +5 -0
  86. package/dist/lib/workspace-kb.js +7 -0
  87. package/dist/models/controller-context.model.d.ts +8 -0
  88. package/dist/models/controller-context.model.js +1 -0
  89. package/dist/repositories/artifact.repository.d.ts +60 -0
  90. package/dist/repositories/artifact.repository.js +40 -0
  91. package/dist/repositories/base.repository.d.ts +14 -0
  92. package/dist/repositories/base.repository.js +14 -0
  93. package/dist/repositories/invitation.repository.d.ts +94 -0
  94. package/dist/repositories/invitation.repository.js +44 -0
  95. package/dist/repositories/notification.repository.d.ts +72 -0
  96. package/dist/repositories/notification.repository.js +44 -0
  97. package/dist/repositories/router-module.repository.d.ts +10 -0
  98. package/dist/repositories/router-module.repository.js +14 -0
  99. package/dist/repositories/user.repository.d.ts +74 -0
  100. package/dist/repositories/user.repository.js +37 -0
  101. package/dist/repositories/workspace-member.repository.d.ts +31 -0
  102. package/dist/repositories/workspace-member.repository.js +31 -0
  103. package/dist/repositories/workspace.repository.d.ts +97 -0
  104. package/dist/repositories/workspace.repository.js +79 -0
  105. package/dist/routers/_app.d.ts +528 -33
  106. package/dist/routers/_app.js +4 -0
  107. package/dist/routers/admin.d.ts +0 -4
  108. package/dist/routers/admin.js +21 -549
  109. package/dist/routers/annotations.js +12 -170
  110. package/dist/routers/artifactVersions.d.ts +65 -0
  111. package/dist/routers/artifactVersions.js +14 -0
  112. package/dist/routers/auth.d.ts +0 -6
  113. package/dist/routers/auth.js +36 -421
  114. package/dist/routers/chat.js +15 -229
  115. package/dist/routers/copilot.d.ts +14 -13
  116. package/dist/routers/copilot.js +13 -532
  117. package/dist/routers/flashcards.d.ts +5 -5
  118. package/dist/routers/flashcards.js +23 -349
  119. package/dist/routers/knowledgeBase.d.ts +421 -0
  120. package/dist/routers/knowledgeBase.js +118 -0
  121. package/dist/routers/members.d.ts +0 -41
  122. package/dist/routers/members.js +22 -710
  123. package/dist/routers/notes.d.ts +94 -0
  124. package/dist/routers/notes.js +37 -0
  125. package/dist/routers/notifications.js +7 -109
  126. package/dist/routers/payment.d.ts +3 -2
  127. package/dist/routers/payment.js +11 -393
  128. package/dist/routers/podcast.d.ts +1 -1
  129. package/dist/routers/podcast.js +11 -784
  130. package/dist/routers/studyguide.js +3 -129
  131. package/dist/routers/worksheets.d.ts +29 -14
  132. package/dist/routers/worksheets.js +49 -628
  133. package/dist/routers/workspace.d.ts +0 -4
  134. package/dist/routers/workspace.js +28 -922
  135. package/dist/scripts/purge-deleted-users.js +2 -2
  136. package/dist/server.js +10 -3
  137. package/dist/services/activity/activity-human-description.service.d.ts +13 -0
  138. package/dist/services/activity/activity-human-description.service.js +221 -0
  139. package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
  140. package/dist/services/activity/activity-human-description.service.test.js +16 -0
  141. package/dist/services/activity/activity-log.service.d.ts +87 -0
  142. package/dist/services/activity/activity-log.service.js +276 -0
  143. package/dist/services/activity/activity-log.service.test.d.ts +1 -0
  144. package/dist/services/activity/activity-log.service.test.js +27 -0
  145. package/dist/services/activity-human-description.service.d.ts +13 -0
  146. package/dist/services/activity-human-description.service.js +221 -0
  147. package/dist/services/activity-human-description.service.test.d.ts +1 -0
  148. package/dist/services/activity-human-description.service.test.js +16 -0
  149. package/dist/services/activity-log.service.d.ts +87 -0
  150. package/dist/services/activity-log.service.js +276 -0
  151. package/dist/services/activity-log.service.test.d.ts +1 -0
  152. package/dist/services/activity-log.service.test.js +27 -0
  153. package/dist/services/admin/admin.service.d.ts +270 -0
  154. package/dist/services/admin/admin.service.js +476 -0
  155. package/dist/services/admin.service.d.ts +270 -0
  156. package/dist/services/admin.service.js +476 -0
  157. package/dist/services/ai/ai-session.service.d.ts +5 -0
  158. package/dist/services/ai/ai-session.service.js +4 -0
  159. package/dist/services/ai-session.service.d.ts +60 -0
  160. package/dist/services/ai-session.service.js +561 -0
  161. package/dist/services/annotation.service.d.ts +177 -0
  162. package/dist/services/annotation.service.js +154 -0
  163. package/dist/services/artifact-notification.service.d.ts +14 -0
  164. package/dist/services/artifact-notification.service.js +20 -0
  165. package/dist/services/artifact-version.service.d.ts +38 -0
  166. package/dist/services/artifact-version.service.js +129 -0
  167. package/dist/services/artifacts/annotation.service.d.ts +177 -0
  168. package/dist/services/artifacts/annotation.service.js +154 -0
  169. package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
  170. package/dist/services/artifacts/artifact-version.service.js +129 -0
  171. package/dist/services/artifacts/chat.service.d.ts +127 -0
  172. package/dist/services/artifacts/chat.service.js +182 -0
  173. package/dist/services/artifacts/study-guide.service.d.ts +18 -0
  174. package/dist/services/artifacts/study-guide.service.js +65 -0
  175. package/dist/services/auth/auth.service.d.ts +94 -0
  176. package/dist/services/auth/auth.service.js +368 -0
  177. package/dist/services/auth.service.d.ts +94 -0
  178. package/dist/services/auth.service.js +368 -0
  179. package/dist/services/base.service.d.ts +14 -0
  180. package/dist/services/base.service.js +14 -0
  181. package/dist/services/billing/payment.service.d.ts +55 -0
  182. package/dist/services/billing/payment.service.js +368 -0
  183. package/dist/services/billing/subscription.service.d.ts +37 -0
  184. package/dist/services/billing/subscription.service.js +654 -0
  185. package/dist/services/billing/usage.service.d.ts +27 -0
  186. package/dist/services/billing/usage.service.js +77 -0
  187. package/dist/services/chat.service.d.ts +127 -0
  188. package/dist/services/chat.service.js +182 -0
  189. package/dist/services/content/copilot.service.d.ts +113 -0
  190. package/dist/services/content/copilot.service.js +453 -0
  191. package/dist/services/content/flashcard-progress.service.d.ts +159 -0
  192. package/dist/services/content/flashcard-progress.service.js +432 -0
  193. package/dist/services/content/flashcard.service.d.ts +140 -0
  194. package/dist/services/content/flashcard.service.js +326 -0
  195. package/dist/services/content/media-analysis.service.d.ts +23 -0
  196. package/dist/services/content/media-analysis.service.js +404 -0
  197. package/dist/services/content/podcast.service.d.ts +267 -0
  198. package/dist/services/content/podcast.service.js +653 -0
  199. package/dist/services/content/worksheet-content.service.d.ts +37 -0
  200. package/dist/services/content/worksheet-content.service.js +84 -0
  201. package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
  202. package/dist/services/content/worksheet-content.service.test.js +69 -0
  203. package/dist/services/content/worksheet-generation.service.d.ts +91 -0
  204. package/dist/services/content/worksheet-generation.service.js +95 -0
  205. package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
  206. package/dist/services/content/worksheet-generation.service.test.js +20 -0
  207. package/dist/services/content/worksheet.service.d.ts +347 -0
  208. package/dist/services/content/worksheet.service.js +599 -0
  209. package/dist/services/copilot.service.d.ts +116 -0
  210. package/dist/services/copilot.service.js +447 -0
  211. package/dist/services/flashcard-progress.service.d.ts +2 -2
  212. package/dist/services/flashcard-progress.service.js +3 -2
  213. package/dist/services/flashcard.service.d.ts +140 -0
  214. package/dist/services/flashcard.service.js +325 -0
  215. package/dist/services/invitation.service.d.ts +66 -0
  216. package/dist/services/invitation.service.js +348 -0
  217. package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
  218. package/dist/services/knowledge/knowledge-base.service.js +544 -0
  219. package/dist/services/knowledge-base.service.d.ts +316 -0
  220. package/dist/services/knowledge-base.service.js +536 -0
  221. package/dist/services/media-analysis.service.d.ts +23 -0
  222. package/dist/services/media-analysis.service.js +384 -0
  223. package/dist/services/member.service.d.ts +36 -0
  224. package/dist/services/member.service.js +193 -0
  225. package/dist/services/members/invitation.service.d.ts +66 -0
  226. package/dist/services/members/invitation.service.js +348 -0
  227. package/dist/services/members/member.service.d.ts +36 -0
  228. package/dist/services/members/member.service.js +193 -0
  229. package/dist/services/note.service.d.ts +55 -0
  230. package/dist/services/note.service.js +111 -0
  231. package/dist/services/notification.service.d.ts +214 -0
  232. package/dist/services/notification.service.js +550 -0
  233. package/dist/services/notification.service.test.d.ts +1 -0
  234. package/dist/services/notification.service.test.js +87 -0
  235. package/dist/services/notifications/notification.service.d.ts +214 -0
  236. package/dist/services/notifications/notification.service.js +550 -0
  237. package/dist/services/notifications/notification.service.test.d.ts +1 -0
  238. package/dist/services/notifications/notification.service.test.js +87 -0
  239. package/dist/services/payment.service.d.ts +55 -0
  240. package/dist/services/payment.service.js +368 -0
  241. package/dist/services/podcast.service.d.ts +267 -0
  242. package/dist/services/podcast.service.js +654 -0
  243. package/dist/services/router-module.service.d.ts +7 -0
  244. package/dist/services/router-module.service.js +10 -0
  245. package/dist/services/study-guide.service.d.ts +18 -0
  246. package/dist/services/study-guide.service.js +65 -0
  247. package/dist/services/subscription.service.d.ts +37 -0
  248. package/dist/services/subscription.service.js +654 -0
  249. package/dist/services/usage-limit-policy.service.d.ts +12 -0
  250. package/dist/services/usage-limit-policy.service.js +22 -0
  251. package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
  252. package/dist/services/usage-limit-policy.service.test.js +46 -0
  253. package/dist/services/usage.service.d.ts +27 -0
  254. package/dist/services/usage.service.js +77 -0
  255. package/dist/services/worksheet-content.service.d.ts +42 -0
  256. package/dist/services/worksheet-content.service.js +84 -0
  257. package/dist/services/worksheet-content.service.test.d.ts +1 -0
  258. package/dist/services/worksheet-content.service.test.js +69 -0
  259. package/dist/services/worksheet-generation.service.d.ts +91 -0
  260. package/dist/services/worksheet-generation.service.js +95 -0
  261. package/dist/services/worksheet-generation.service.test.d.ts +1 -0
  262. package/dist/services/worksheet-generation.service.test.js +20 -0
  263. package/dist/services/worksheet.service.d.ts +385 -0
  264. package/dist/services/worksheet.service.js +596 -0
  265. package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
  266. package/dist/services/workspace/workspace-analytics.service.js +95 -0
  267. package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
  268. package/dist/services/workspace/workspace-kb.service.js +184 -0
  269. package/dist/services/workspace/workspace.service.d.ts +307 -0
  270. package/dist/services/workspace/workspace.service.js +394 -0
  271. package/dist/services/workspace-analytics.service.d.ts +24 -0
  272. package/dist/services/workspace-analytics.service.js +95 -0
  273. package/dist/services/workspace-kb.service.d.ts +40 -0
  274. package/dist/services/workspace-kb.service.js +184 -0
  275. package/dist/services/workspace-progress.service.d.ts +27 -0
  276. package/dist/services/workspace-progress.service.js +56 -0
  277. package/dist/services/workspace-progress.service.test.d.ts +1 -0
  278. package/dist/services/workspace-progress.service.test.js +49 -0
  279. package/dist/services/workspace.service.d.ts +307 -0
  280. package/dist/services/workspace.service.js +390 -0
  281. package/dist/trpc.js +2 -2
  282. package/package.json +5 -6
  283. package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
  284. package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
  285. package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
  286. package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
  287. package/prisma/schema.prisma +150 -48
  288. package/prisma/seed.mjs +67 -0
  289. package/scripts/debug/README.md +4 -0
  290. package/src/README.md +63 -0
  291. package/src/lib/ai/config.ts +34 -0
  292. package/src/lib/ai/embedding-client.ts +47 -0
  293. package/src/lib/ai/index.ts +62 -0
  294. package/src/lib/ai/inference-backend/client.ts +479 -0
  295. package/src/lib/ai/inference-backend/mocks.ts +171 -0
  296. package/src/lib/ai/inference-backend/types.ts +50 -0
  297. package/src/lib/ai/json-parse.ts +35 -0
  298. package/src/lib/ai/llm-client.ts +31 -0
  299. package/src/lib/ai/mock.ts +12 -0
  300. package/src/lib/ai/types.ts +11 -0
  301. package/src/lib/chunking.ts +81 -0
  302. package/src/lib/curated-kb-seed.ts +164 -0
  303. package/src/lib/email.ts +77 -115
  304. package/src/lib/embeddings.ts +9 -0
  305. package/src/lib/ensure-curated-kb-catalog.ts +60 -0
  306. package/src/lib/env.ts +2 -7
  307. package/src/lib/inference.ts +1 -21
  308. package/src/lib/kb-meta.ts +81 -0
  309. package/src/lib/pdf.ts +23 -0
  310. package/src/lib/workspace-kb.ts +7 -0
  311. package/src/repositories/artifact.repository.ts +55 -0
  312. package/src/repositories/base.repository.ts +19 -0
  313. package/src/repositories/invitation.repository.ts +53 -0
  314. package/src/repositories/notification.repository.ts +53 -0
  315. package/src/repositories/user.repository.ts +44 -0
  316. package/src/repositories/workspace-member.repository.ts +38 -0
  317. package/src/repositories/workspace.repository.ts +89 -0
  318. package/src/routers/_app.ts +4 -0
  319. package/src/routers/admin.ts +124 -692
  320. package/src/routers/annotations.ts +25 -203
  321. package/src/routers/artifactVersions.ts +32 -0
  322. package/src/routers/auth.ts +81 -519
  323. package/src/routers/chat.ts +42 -245
  324. package/src/routers/copilot.ts +41 -666
  325. package/src/routers/flashcards.ts +108 -404
  326. package/src/routers/knowledgeBase.ts +216 -0
  327. package/src/routers/members.ts +60 -782
  328. package/src/routers/notifications.ts +15 -117
  329. package/src/routers/payment.ts +37 -446
  330. package/src/routers/podcast.ts +36 -898
  331. package/src/routers/studyguide.ts +5 -144
  332. package/src/routers/worksheets.ts +171 -735
  333. package/src/routers/workspace.ts +138 -1109
  334. package/src/scripts/purge-deleted-users.ts +2 -2
  335. package/src/server.ts +10 -3
  336. package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
  337. package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
  338. package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
  339. package/src/services/admin/admin.service.ts +612 -0
  340. package/src/services/ai/ai-session.service.ts +5 -0
  341. package/src/services/artifacts/annotation.service.ts +189 -0
  342. package/src/services/artifacts/artifact-version.service.ts +151 -0
  343. package/src/services/artifacts/chat.service.ts +197 -0
  344. package/src/services/artifacts/study-guide.service.ts +72 -0
  345. package/src/services/auth/auth.service.ts +473 -0
  346. package/src/services/base.service.ts +19 -0
  347. package/src/services/billing/payment.service.ts +436 -0
  348. package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
  349. package/src/{lib/usage_service.ts → services/billing/usage.service.ts} +32 -12
  350. package/src/services/content/copilot.service.ts +596 -0
  351. package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +6 -3
  352. package/src/services/content/flashcard.service.ts +394 -0
  353. package/src/services/content/media-analysis.service.ts +556 -0
  354. package/src/services/content/podcast.service.ts +777 -0
  355. package/src/services/content/worksheet-content.service.test.ts +83 -0
  356. package/src/services/content/worksheet-content.service.ts +117 -0
  357. package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +1 -1
  358. package/src/services/content/worksheet.service.ts +751 -0
  359. package/src/services/knowledge/knowledge-base.service.ts +705 -0
  360. package/src/services/members/invitation.service.ts +427 -0
  361. package/src/services/members/member.service.ts +241 -0
  362. package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
  363. package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
  364. package/src/services/workspace/workspace-analytics.service.ts +107 -0
  365. package/src/services/workspace/workspace-kb.service.ts +273 -0
  366. package/src/services/workspace/workspace.service.ts +481 -0
  367. package/src/trpc.ts +2 -2
  368. package/src/lib/ai-session.ts +0 -704
  369. package/src/lib/workspace-access.ts +0 -13
  370. /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
  371. /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
  372. /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
  373. /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
  374. /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
  375. /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
  376. /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
  377. /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
  378. /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
@@ -0,0 +1,384 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { BaseService } from './base.service.js';
3
+ import { ArtifactType } from '../lib/constants.js';
4
+ import { supabaseClient } from '../lib/storage.js';
5
+ import PusherService from '../lib/pusher.js';
6
+ import { aiSessionService } from './ai-session.service.js';
7
+ import { getUserUsage, getUserPlanLimits } from './usage.service.js';
8
+ import { notifyArtifactFailed, notifyArtifactReady } from './notification.service.js';
9
+ const PIPELINE_STEPS = ['fileUpload', 'fileAnalysis', 'studyGuide', 'flashcards'];
10
+ const PROGRESS_FILENAME_MAX_CHARS = 20;
11
+ function truncateProgressFilename(value, maxChars = PROGRESS_FILENAME_MAX_CHARS) {
12
+ if (value.length <= maxChars)
13
+ return value;
14
+ return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
15
+ }
16
+ function buildCombinedFilenameLabel(fileNames) {
17
+ return truncateProgressFilename(fileNames.join(', '));
18
+ }
19
+ function buildProgressSteps(currentStep, currentStatus, config, overrides) {
20
+ const stepIndex = PIPELINE_STEPS.indexOf(currentStep);
21
+ const steps = {};
22
+ for (let i = 0; i < PIPELINE_STEPS.length; i++) {
23
+ const step = PIPELINE_STEPS[i];
24
+ let status;
25
+ if (overrides?.[step]) {
26
+ status = overrides[step];
27
+ }
28
+ else if (i < stepIndex) {
29
+ status = 'completed';
30
+ }
31
+ else if (i === stepIndex) {
32
+ status = currentStatus;
33
+ }
34
+ else {
35
+ if (step === 'studyGuide' && !config.generateStudyGuide) {
36
+ status = 'skipped';
37
+ }
38
+ else if (step === 'flashcards' && !config.generateFlashcards) {
39
+ status = 'skipped';
40
+ }
41
+ else {
42
+ status = 'pending';
43
+ }
44
+ }
45
+ steps[step] = { order: i + 1, status };
46
+ }
47
+ return steps;
48
+ }
49
+ function buildProgress(status, filename, fileType, currentStep, currentStepStatus, config, extra) {
50
+ return {
51
+ status,
52
+ filename,
53
+ fileType,
54
+ startedAt: new Date().toISOString(),
55
+ steps: buildProgressSteps(currentStep, currentStepStatus, config, extra),
56
+ ...extra,
57
+ };
58
+ }
59
+ export class MediaAnalysisService extends BaseService {
60
+ constructor(db) {
61
+ super(db);
62
+ }
63
+ async updateAnalysisProgress(workspaceId, progress) {
64
+ await this.db.workspace.update({
65
+ where: { id: workspaceId },
66
+ data: { analysisProgress: progress },
67
+ });
68
+ await PusherService.emitAnalysisProgress(workspaceId, progress);
69
+ }
70
+ async uploadAndAnalyzeMedia(input, userId) {
71
+ const workspace = await this.db.workspace.findFirst({
72
+ where: { id: input.workspaceId, ownerId: userId },
73
+ });
74
+ if (!workspace) {
75
+ this.logger.error('Workspace not found', { workspaceId: input.workspaceId, userId });
76
+ throw new TRPCError({ code: 'NOT_FOUND' });
77
+ }
78
+ if (workspace.fileBeingAnalyzed) {
79
+ throw new TRPCError({
80
+ code: 'CONFLICT',
81
+ message: 'File analysis is already in progress for this workspace. Please wait for it to complete.',
82
+ });
83
+ }
84
+ const files = await this.db.fileAsset.findMany({
85
+ where: {
86
+ id: { in: input.files.map((file) => file.id) },
87
+ workspaceId: input.workspaceId,
88
+ userId,
89
+ },
90
+ });
91
+ if (files.length === 0) {
92
+ throw new TRPCError({
93
+ code: 'NOT_FOUND',
94
+ message: 'No files found with the provided IDs',
95
+ });
96
+ }
97
+ for (const file of files) {
98
+ if (!file.bucket || !file.objectKey) {
99
+ throw new TRPCError({
100
+ code: 'BAD_REQUEST',
101
+ message: `File ${file.id} does not have bucket or objectKey set`,
102
+ });
103
+ }
104
+ }
105
+ const primaryFile = files[0];
106
+ const fileType = primaryFile.mimeType.startsWith('image/') ? 'image' : 'pdf';
107
+ const combinedFilenameLabel = buildCombinedFilenameLabel(files.map((file) => file.name));
108
+ try {
109
+ await this.db.workspace.update({
110
+ where: { id: input.workspaceId },
111
+ data: { fileBeingAnalyzed: true },
112
+ });
113
+ const genConfig = {
114
+ generateStudyGuide: input.generateStudyGuide,
115
+ generateFlashcards: input.generateFlashcards,
116
+ };
117
+ PusherService.emitAnalysisProgress(input.workspaceId, buildProgress('starting', primaryFile.name, fileType, 'fileUpload', 'pending', genConfig));
118
+ try {
119
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('starting', primaryFile.name, fileType, 'fileUpload', 'pending', genConfig));
120
+ }
121
+ catch (error) {
122
+ this.logger.error('Failed to update analysis progress:', error);
123
+ await this.db.workspace.update({
124
+ where: { id: input.workspaceId },
125
+ data: { fileBeingAnalyzed: false },
126
+ });
127
+ await PusherService.emitError(input.workspaceId, `Failed to update analysis progress: ${error}`, 'file_analysis');
128
+ throw error;
129
+ }
130
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('uploading', primaryFile.name, fileType, 'fileUpload', 'in_progress', genConfig));
131
+ for (const [fileIndex, file] of files.entries()) {
132
+ if (!file.bucket || !file.objectKey) {
133
+ continue;
134
+ }
135
+ const currentFileType = file.mimeType.startsWith('image/') ? 'image' : 'pdf';
136
+ const currentFileLabel = truncateProgressFilename(file.name);
137
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('uploading', currentFileLabel, currentFileType, 'fileUpload', 'in_progress', genConfig, {
138
+ currentFileIndex: fileIndex + 1,
139
+ totalFiles: files.length,
140
+ }));
141
+ const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
142
+ .from(file.bucket)
143
+ .createSignedUrl(file.objectKey, 24 * 60 * 60);
144
+ if (signedUrlError) {
145
+ await this.db.workspace.update({
146
+ where: { id: input.workspaceId },
147
+ data: { fileBeingAnalyzed: false },
148
+ });
149
+ throw new TRPCError({
150
+ code: 'INTERNAL_SERVER_ERROR',
151
+ message: `Failed to upload file`,
152
+ });
153
+ }
154
+ const fileUrl = signedUrlData.signedUrl;
155
+ const maxPages = currentFileType === 'pdf' && file.size && file.size > 50 ? 50 : undefined;
156
+ const processResult = await aiSessionService.processFile(input.workspaceId, userId, fileUrl, currentFileType, maxPages);
157
+ if (processResult.status === 'error') {
158
+ this.logger.error(`Failed to process file ${file.name}:`, processResult.error);
159
+ }
160
+ else {
161
+ this.logger.info(`Successfully processed file ${file.name}: ${processResult.pageCount} pages`);
162
+ await this.db.fileAsset.update({
163
+ where: { id: file.id },
164
+ data: {
165
+ aiTranscription: {
166
+ comprehensiveDescription: processResult.comprehensiveDescription,
167
+ textContent: processResult.textContent,
168
+ imageDescriptions: processResult.imageDescriptions,
169
+ },
170
+ },
171
+ });
172
+ }
173
+ }
174
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('analyzing', combinedFilenameLabel, fileType, 'fileAnalysis', 'in_progress', genConfig));
175
+ try {
176
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('generating_artifacts', combinedFilenameLabel, fileType, 'studyGuide', 'pending', genConfig));
177
+ }
178
+ catch (error) {
179
+ this.logger.error('Failed to analyze files:', error);
180
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('error', combinedFilenameLabel, fileType, 'fileAnalysis', 'error', genConfig, {
181
+ error: `Failed to analyze ${fileType}: ${error}`,
182
+ studyGuide: 'skipped',
183
+ flashcards: 'skipped',
184
+ }));
185
+ await this.db.workspace.update({
186
+ where: { id: input.workspaceId },
187
+ data: { fileBeingAnalyzed: false },
188
+ });
189
+ throw error;
190
+ }
191
+ const results = {
192
+ filename: primaryFile.name,
193
+ artifacts: {
194
+ studyGuide: null,
195
+ flashcards: null,
196
+ worksheet: null,
197
+ },
198
+ };
199
+ try {
200
+ await aiSessionService.initSession(input.workspaceId, userId);
201
+ }
202
+ catch (initError) {
203
+ this.logger.error('Failed to init AI session (continuing with workspace context):', initError);
204
+ }
205
+ const [usage, limits] = await Promise.all([
206
+ getUserUsage(userId),
207
+ getUserPlanLimits(userId),
208
+ ]);
209
+ if (input.generateStudyGuide) {
210
+ if (limits && usage.studyGuides >= limits.maxStudyGuides) {
211
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('skipped', combinedFilenameLabel, fileType, 'studyGuide', 'skipped', genConfig));
212
+ await PusherService.emitError(input.workspaceId, 'Study guide skipped: Limit reached.', 'study_guide');
213
+ await notifyArtifactFailed(this.db, {
214
+ userId,
215
+ workspaceId: input.workspaceId,
216
+ artifactType: ArtifactType.STUDY_GUIDE,
217
+ message: 'Study guide was skipped because your plan limit was reached.',
218
+ }).catch(() => { });
219
+ }
220
+ else {
221
+ try {
222
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('generating_study_guide', combinedFilenameLabel, fileType, 'studyGuide', 'in_progress', genConfig));
223
+ const content = await aiSessionService.generateStudyGuide(input.workspaceId, userId);
224
+ let artifact = await this.db.artifact.findFirst({
225
+ where: { workspaceId: input.workspaceId, type: ArtifactType.STUDY_GUIDE },
226
+ });
227
+ if (!artifact) {
228
+ artifact = await this.db.artifact.create({
229
+ data: {
230
+ workspaceId: input.workspaceId,
231
+ type: ArtifactType.STUDY_GUIDE,
232
+ title: files.length === 1
233
+ ? `Study Guide - ${primaryFile.name}`
234
+ : `Study Guide - ${files.length} files`,
235
+ createdById: userId,
236
+ },
237
+ });
238
+ }
239
+ const lastVersion = await this.db.artifactVersion.findFirst({
240
+ where: { artifact: { workspaceId: input.workspaceId, type: ArtifactType.STUDY_GUIDE } },
241
+ orderBy: { version: 'desc' },
242
+ });
243
+ await this.db.artifactVersion.create({
244
+ data: {
245
+ artifactId: artifact.id,
246
+ version: lastVersion ? lastVersion.version + 1 : 1,
247
+ content: content,
248
+ createdById: userId,
249
+ },
250
+ });
251
+ results.artifacts.studyGuide = artifact;
252
+ await PusherService.emitStudyGuideComplete(input.workspaceId, artifact);
253
+ await notifyArtifactReady(this.db, {
254
+ userId,
255
+ workspaceId: input.workspaceId,
256
+ artifactId: artifact.id,
257
+ artifactType: ArtifactType.STUDY_GUIDE,
258
+ title: artifact.title,
259
+ }).catch(() => { });
260
+ }
261
+ catch (sgError) {
262
+ this.logger.error('Study guide generation failed after retries:', sgError);
263
+ await PusherService.emitError(input.workspaceId, 'Study guide generation failed. Please try regenerating later.', 'study_guide');
264
+ await notifyArtifactFailed(this.db, {
265
+ userId,
266
+ workspaceId: input.workspaceId,
267
+ artifactType: ArtifactType.STUDY_GUIDE,
268
+ message: 'Study guide generation failed. Please try regenerating later.',
269
+ }).catch(() => { });
270
+ }
271
+ }
272
+ }
273
+ if (input.generateFlashcards) {
274
+ if (limits && usage.flashcards >= limits.maxFlashcards) {
275
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('skipped', combinedFilenameLabel, fileType, 'flashcards', 'skipped', genConfig));
276
+ await PusherService.emitError(input.workspaceId, 'Flashcards skipped: Limit reached.', 'flashcards');
277
+ await notifyArtifactFailed(this.db, {
278
+ userId,
279
+ workspaceId: input.workspaceId,
280
+ artifactType: ArtifactType.FLASHCARD_SET,
281
+ message: 'Flashcards were skipped because your plan limit was reached.',
282
+ }).catch(() => { });
283
+ }
284
+ else {
285
+ try {
286
+ const sgStatus = input.generateStudyGuide
287
+ ? results.artifacts.studyGuide
288
+ ? 'completed'
289
+ : 'error'
290
+ : 'skipped';
291
+ await this.updateAnalysisProgress(input.workspaceId, buildProgress('generating_flashcards', combinedFilenameLabel, fileType, 'flashcards', 'in_progress', genConfig, {
292
+ studyGuide: sgStatus,
293
+ }));
294
+ const content = await aiSessionService.generateFlashcardQuestions(input.workspaceId, userId, 10, 'medium');
295
+ const artifact = await this.db.artifact.create({
296
+ data: {
297
+ workspaceId: input.workspaceId,
298
+ type: ArtifactType.FLASHCARD_SET,
299
+ title: files.length === 1
300
+ ? `Flashcards - ${primaryFile.name}`
301
+ : `Flashcards - ${files.length} files`,
302
+ createdById: userId,
303
+ },
304
+ });
305
+ try {
306
+ const parsed = typeof content === 'string' ? JSON.parse(content) : content;
307
+ const flashcardData = Array.isArray(parsed) ? parsed : (parsed.flashcards || []);
308
+ for (let i = 0; i < Math.min(flashcardData.length, 10); i++) {
309
+ const card = flashcardData[i];
310
+ const front = card.term || card.front || card.question || card.prompt || `Question ${i + 1}`;
311
+ const back = card.definition || card.back || card.answer || card.solution || `Answer ${i + 1}`;
312
+ await this.db.flashcard.create({
313
+ data: {
314
+ artifactId: artifact.id,
315
+ front: front,
316
+ back: back,
317
+ order: i,
318
+ tags: ['ai-generated', 'medium'],
319
+ },
320
+ });
321
+ }
322
+ }
323
+ catch (parseError) {
324
+ console.error('Failed to parse flashcard JSON or create cards in workspace router:', parseError);
325
+ const lines = content.split('\n').filter((line) => line.trim());
326
+ for (let i = 0; i < Math.min(lines.length, 10); i++) {
327
+ const line = lines[i];
328
+ if (line.includes(' - ')) {
329
+ const [front, back] = line.split(' - ');
330
+ await this.db.flashcard.create({
331
+ data: {
332
+ artifactId: artifact.id,
333
+ front: front.trim(),
334
+ back: back.trim(),
335
+ order: i,
336
+ tags: ['ai-generated', 'medium'],
337
+ },
338
+ });
339
+ }
340
+ }
341
+ }
342
+ results.artifacts.flashcards = artifact;
343
+ await PusherService.emitFlashcardComplete(input.workspaceId, artifact);
344
+ await notifyArtifactReady(this.db, {
345
+ userId,
346
+ workspaceId: input.workspaceId,
347
+ artifactId: artifact.id,
348
+ artifactType: ArtifactType.FLASHCARD_SET,
349
+ title: artifact.title,
350
+ }).catch(() => { });
351
+ }
352
+ catch (fcError) {
353
+ this.logger.error('Flashcard generation failed after retries:', fcError);
354
+ await PusherService.emitError(input.workspaceId, 'Flashcard generation failed. Please try regenerating later.', 'flashcards');
355
+ await notifyArtifactFailed(this.db, {
356
+ userId,
357
+ workspaceId: input.workspaceId,
358
+ artifactType: ArtifactType.FLASHCARD_SET,
359
+ message: 'Flashcard generation failed. Please try regenerating later.',
360
+ }).catch(() => { });
361
+ }
362
+ }
363
+ }
364
+ await this.db.workspace.update({
365
+ where: { id: input.workspaceId },
366
+ data: { fileBeingAnalyzed: false },
367
+ });
368
+ await this.updateAnalysisProgress(input.workspaceId, {
369
+ ...buildProgress('completed', combinedFilenameLabel, fileType, 'flashcards', 'completed', genConfig),
370
+ completedAt: new Date().toISOString(),
371
+ });
372
+ return results;
373
+ }
374
+ catch (error) {
375
+ this.logger.error('Failed to update analysis progress:', error);
376
+ await this.db.workspace.update({
377
+ where: { id: input.workspaceId },
378
+ data: { fileBeingAnalyzed: false },
379
+ });
380
+ await PusherService.emitError(input.workspaceId, `Failed to update analysis progress: ${error}`, 'file_analysis');
381
+ throw error;
382
+ }
383
+ }
384
+ }
@@ -0,0 +1,36 @@
1
+ import type { PrismaClient } from '@prisma/client';
2
+ import { BaseService } from './base.service.js';
3
+ export declare class MemberService extends BaseService {
4
+ constructor(db: PrismaClient);
5
+ getMembers(userId: string, workspaceId: string): Promise<({
6
+ id: string;
7
+ name: string;
8
+ email: string;
9
+ role: "admin" | "member";
10
+ joinedAt: Date;
11
+ } | {
12
+ id: string;
13
+ name: string;
14
+ email: string;
15
+ role: "owner";
16
+ joinedAt: Date;
17
+ })[]>;
18
+ getCurrentUserRole(userId: string, workspaceId: string): Promise<"admin" | "member" | "owner">;
19
+ changeMemberRole(userId: string, input: {
20
+ workspaceId: string;
21
+ memberId: string;
22
+ role: 'admin' | 'member';
23
+ }): Promise<{
24
+ memberId: string;
25
+ role: "admin" | "member";
26
+ memberName: string | null;
27
+ message: string;
28
+ }>;
29
+ removeMember(userId: string, input: {
30
+ workspaceId: string;
31
+ memberId: string;
32
+ }): Promise<{
33
+ memberId: string;
34
+ message: string;
35
+ }>;
36
+ }
@@ -0,0 +1,193 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { BaseService } from './base.service.js';
3
+ import { notifyWorkspaceMembershipRemoved, notifyWorkspaceRoleChanged, } from './notification.service.js';
4
+ export class MemberService extends BaseService {
5
+ constructor(db) {
6
+ super(db);
7
+ }
8
+ async getMembers(userId, workspaceId) {
9
+ const workspace = await this.db.workspace.findFirst({
10
+ where: {
11
+ id: workspaceId,
12
+ OR: [{ ownerId: userId }, { members: { some: { userId } } }],
13
+ },
14
+ include: {
15
+ owner: {
16
+ select: {
17
+ id: true,
18
+ name: true,
19
+ email: true,
20
+ profilePicture: { select: { id: true, name: true, url: true } },
21
+ },
22
+ },
23
+ members: {
24
+ include: {
25
+ user: {
26
+ select: {
27
+ id: true,
28
+ name: true,
29
+ email: true,
30
+ profilePicture: { select: { id: true, name: true, url: true } },
31
+ },
32
+ },
33
+ },
34
+ },
35
+ },
36
+ });
37
+ if (!workspace) {
38
+ throw new TRPCError({
39
+ code: 'NOT_FOUND',
40
+ message: 'Workspace not found or access denied',
41
+ });
42
+ }
43
+ const workspaceMembers = [
44
+ {
45
+ id: workspace.owner.id,
46
+ name: workspace.owner.name || 'Unknown',
47
+ email: workspace.owner.email || '',
48
+ role: 'owner',
49
+ joinedAt: workspace.createdAt,
50
+ },
51
+ ...workspace.members.map((membership) => ({
52
+ id: membership.user.id,
53
+ name: membership.user.name || 'Unknown',
54
+ email: membership.user.email || '',
55
+ role: membership.role,
56
+ joinedAt: membership.joinedAt,
57
+ })),
58
+ ];
59
+ this.logger.info(`👥 Fetched ${workspaceMembers.length} members for workspace ${workspaceId}`, 'WORKSPACE', { memberEmails: workspaceMembers.map((m) => m.email) });
60
+ return workspaceMembers;
61
+ }
62
+ async getCurrentUserRole(userId, workspaceId) {
63
+ const workspace = await this.db.workspace.findFirst({
64
+ where: { id: workspaceId },
65
+ select: {
66
+ ownerId: true,
67
+ members: { where: { userId }, select: { role: true } },
68
+ },
69
+ });
70
+ if (!workspace) {
71
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' });
72
+ }
73
+ if (workspace.ownerId === userId) {
74
+ return 'owner';
75
+ }
76
+ if (workspace.members.length > 0) {
77
+ return workspace.members[0].role;
78
+ }
79
+ throw new TRPCError({
80
+ code: 'FORBIDDEN',
81
+ message: 'Access denied to this workspace',
82
+ });
83
+ }
84
+ async changeMemberRole(userId, input) {
85
+ const workspace = await this.db.workspace.findFirst({
86
+ where: { id: input.workspaceId, ownerId: userId },
87
+ select: { id: true, title: true, ownerId: true },
88
+ });
89
+ if (!workspace) {
90
+ throw new TRPCError({
91
+ code: 'NOT_FOUND',
92
+ message: 'Workspace not found or insufficient permissions',
93
+ });
94
+ }
95
+ if (input.memberId === workspace.ownerId) {
96
+ throw new TRPCError({
97
+ code: 'BAD_REQUEST',
98
+ message: 'Cannot change owner role',
99
+ });
100
+ }
101
+ const member = await this.db.workspaceMember.findFirst({
102
+ where: { workspaceId: input.workspaceId, userId: input.memberId },
103
+ });
104
+ if (!member) {
105
+ throw new TRPCError({
106
+ code: 'NOT_FOUND',
107
+ message: 'Member not found in this workspace',
108
+ });
109
+ }
110
+ const updatedMember = await this.db.workspaceMember.update({
111
+ where: { id: member.id },
112
+ data: { role: input.role },
113
+ include: {
114
+ user: { select: { id: true, name: true, email: true } },
115
+ },
116
+ });
117
+ this.logger.info(`🔄 Member role changed for ${input.memberId} from ${member.role} to ${input.role} in workspace ${input.workspaceId}`, 'WORKSPACE', {
118
+ workspaceId: input.workspaceId,
119
+ memberId: input.memberId,
120
+ oldRole: member.role,
121
+ newRole: input.role,
122
+ changedBy: userId,
123
+ });
124
+ const actor = await this.db.user.findUnique({
125
+ where: { id: userId },
126
+ select: { name: true, email: true },
127
+ });
128
+ await notifyWorkspaceRoleChanged(this.db, {
129
+ memberUserId: input.memberId,
130
+ workspaceId: input.workspaceId,
131
+ workspaceTitle: workspace.title,
132
+ newRole: input.role,
133
+ oldRole: member.role,
134
+ actorUserId: userId,
135
+ actorName: actor?.name || actor?.email || 'A workspace admin',
136
+ }).catch(() => { });
137
+ return {
138
+ memberId: input.memberId,
139
+ role: input.role,
140
+ memberName: updatedMember.user.name || updatedMember.user.email,
141
+ message: 'Role changed successfully',
142
+ };
143
+ }
144
+ async removeMember(userId, input) {
145
+ const workspace = await this.db.workspace.findFirst({
146
+ where: { id: input.workspaceId, ownerId: userId },
147
+ select: { id: true, title: true, ownerId: true },
148
+ });
149
+ if (!workspace) {
150
+ throw new TRPCError({
151
+ code: 'NOT_FOUND',
152
+ message: 'Workspace not found or insufficient permissions',
153
+ });
154
+ }
155
+ if (input.memberId === workspace.ownerId) {
156
+ throw new TRPCError({
157
+ code: 'BAD_REQUEST',
158
+ message: 'Cannot remove workspace owner',
159
+ });
160
+ }
161
+ const member = await this.db.workspaceMember.findFirst({
162
+ where: { workspaceId: input.workspaceId, userId: input.memberId },
163
+ include: { user: { select: { name: true, email: true } } },
164
+ });
165
+ if (!member) {
166
+ throw new TRPCError({
167
+ code: 'NOT_FOUND',
168
+ message: 'Member not found in this workspace',
169
+ });
170
+ }
171
+ const actor = await this.db.user.findUnique({
172
+ where: { id: userId },
173
+ select: { name: true, email: true },
174
+ });
175
+ await notifyWorkspaceMembershipRemoved(this.db, {
176
+ memberUserId: input.memberId,
177
+ workspaceId: input.workspaceId,
178
+ workspaceTitle: workspace.title,
179
+ actorUserId: userId,
180
+ actorName: actor?.name || actor?.email || 'A workspace admin',
181
+ }).catch(() => { });
182
+ await this.db.workspaceMember.delete({ where: { id: member.id } });
183
+ this.logger.info(`🗑️ Member ${input.memberId} removed from workspace ${input.workspaceId}`, 'WORKSPACE', {
184
+ workspaceId: input.workspaceId,
185
+ memberId: input.memberId,
186
+ removedBy: userId,
187
+ });
188
+ return {
189
+ memberId: input.memberId,
190
+ message: 'Member removed successfully',
191
+ };
192
+ }
193
+ }
@@ -0,0 +1,66 @@
1
+ import type { PrismaClient } from '@prisma/client';
2
+ import { BaseService } from '../base.service.js';
3
+ export declare class InvitationService extends BaseService {
4
+ constructor(db: PrismaClient);
5
+ inviteMember(userId: string, input: {
6
+ workspaceId: string;
7
+ email: string;
8
+ role: 'admin' | 'member';
9
+ }): Promise<{
10
+ invitationId: string;
11
+ token: string;
12
+ email: string;
13
+ role: string;
14
+ expiresAt: Date;
15
+ workspaceTitle: string;
16
+ invitedByName: string | null;
17
+ }>;
18
+ getInvitations(workspaceId: string): Promise<{
19
+ id: string;
20
+ email: string;
21
+ createdAt: Date;
22
+ updatedAt: Date;
23
+ workspaceId: string;
24
+ role: string;
25
+ token: string;
26
+ invitedById: string;
27
+ acceptedAt: Date | null;
28
+ expiresAt: Date;
29
+ }[]>;
30
+ acceptInvite(sessionUserId: string | undefined, token: string): Promise<{
31
+ message?: string | undefined;
32
+ workspaceId: string;
33
+ workspaceTitle: string;
34
+ role: string;
35
+ ownerName: string | null;
36
+ }>;
37
+ getPendingInvitations(userId: string, workspaceId: string): Promise<{
38
+ id: string;
39
+ email: string;
40
+ role: string;
41
+ token: string;
42
+ expiresAt: Date;
43
+ createdAt: Date;
44
+ invitedByName: string | null;
45
+ }[]>;
46
+ cancelInvitation(userId: string, invitationId: string): Promise<{
47
+ invitationId: string;
48
+ message: string;
49
+ }>;
50
+ resendInvitation(userId: string, invitationId: string): Promise<{
51
+ invitationId: string;
52
+ message: string;
53
+ }>;
54
+ getAllInvitationsDebug(userId: string, workspaceId: string): Promise<{
55
+ id: string;
56
+ email: string;
57
+ createdAt: Date;
58
+ updatedAt: Date;
59
+ workspaceId: string;
60
+ role: string;
61
+ token: string;
62
+ invitedById: string;
63
+ acceptedAt: Date | null;
64
+ expiresAt: Date;
65
+ }[]>;
66
+ }