@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
@@ -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
+ }