@goscribe/server 1.3.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/.env.example +12 -0
  2. package/.vscode/settings.json +3 -0
  3. package/REFACTOR_NOTES.md +60 -0
  4. package/TESTING_PROMPT.md +225 -0
  5. package/dist/context.d.ts +14 -1
  6. package/dist/context.js +23 -2
  7. package/dist/controllers/admin.controller.d.ts +715 -0
  8. package/dist/controllers/admin.controller.js +9 -0
  9. package/dist/controllers/annotations.controller.d.ts +439 -0
  10. package/dist/controllers/annotations.controller.js +9 -0
  11. package/dist/controllers/app-router.controller.d.ts +3011 -0
  12. package/dist/controllers/app-router.controller.js +38 -0
  13. package/dist/controllers/app-router.controller.test.d.ts +1 -0
  14. package/dist/controllers/app-router.controller.test.js +36 -0
  15. package/dist/controllers/auth.controller.d.ts +323 -0
  16. package/dist/controllers/auth.controller.js +9 -0
  17. package/dist/controllers/base.controller.d.ts +4 -0
  18. package/dist/controllers/base.controller.js +5 -0
  19. package/dist/controllers/chat.controller.d.ts +341 -0
  20. package/dist/controllers/chat.controller.js +9 -0
  21. package/dist/controllers/copilot.controller.d.ts +397 -0
  22. package/dist/controllers/copilot.controller.js +9 -0
  23. package/dist/controllers/flashcards.controller.d.ts +651 -0
  24. package/dist/controllers/flashcards.controller.js +9 -0
  25. package/dist/controllers/members.controller.d.ts +339 -0
  26. package/dist/controllers/members.controller.js +9 -0
  27. package/dist/controllers/notifications.controller.d.ts +199 -0
  28. package/dist/controllers/notifications.controller.js +9 -0
  29. package/dist/controllers/payment.controller.d.ts +181 -0
  30. package/dist/controllers/payment.controller.js +9 -0
  31. package/dist/controllers/podcast.controller.d.ts +575 -0
  32. package/dist/controllers/podcast.controller.js +9 -0
  33. package/dist/controllers/router-module.controller.d.ts +5 -0
  34. package/dist/controllers/router-module.controller.js +6 -0
  35. package/dist/controllers/studyguide.controller.d.ts +73 -0
  36. package/dist/controllers/studyguide.controller.js +9 -0
  37. package/dist/controllers/worksheets.controller.d.ts +829 -0
  38. package/dist/controllers/worksheets.controller.js +9 -0
  39. package/dist/controllers/workspace.controller.d.ts +1207 -0
  40. package/dist/controllers/workspace.controller.js +9 -0
  41. package/dist/lib/activity_human_description.test.js +16 -15
  42. package/dist/lib/activity_log_service.test.js +28 -23
  43. package/dist/lib/ai/config.d.ts +20 -0
  44. package/dist/lib/ai/config.js +31 -0
  45. package/dist/lib/ai/embedding-client.d.ts +8 -0
  46. package/dist/lib/ai/embedding-client.js +30 -0
  47. package/dist/lib/ai/index.d.ts +48 -0
  48. package/dist/lib/ai/index.js +29 -0
  49. package/dist/lib/ai/inference-backend/client.d.ts +28 -0
  50. package/dist/lib/ai/inference-backend/client.js +301 -0
  51. package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
  52. package/dist/lib/ai/inference-backend/mocks.js +133 -0
  53. package/dist/lib/ai/inference-backend/types.d.ts +44 -0
  54. package/dist/lib/ai/inference-backend/types.js +1 -0
  55. package/dist/lib/ai/json-parse.d.ts +2 -0
  56. package/dist/lib/ai/json-parse.js +34 -0
  57. package/dist/lib/ai/llm-client.d.ts +7 -0
  58. package/dist/lib/ai/llm-client.js +36 -0
  59. package/dist/lib/ai/mock.d.ts +2 -0
  60. package/dist/lib/ai/mock.js +10 -0
  61. package/dist/lib/ai/types.d.ts +9 -0
  62. package/dist/lib/ai/types.js +1 -0
  63. package/dist/lib/chunking.d.ts +19 -0
  64. package/dist/lib/chunking.js +47 -0
  65. package/dist/lib/curated-kb-seed.d.ts +12 -0
  66. package/dist/lib/curated-kb-seed.js +155 -0
  67. package/dist/lib/email.js +67 -108
  68. package/dist/lib/embeddings.d.ts +2 -0
  69. package/dist/lib/embeddings.js +1 -0
  70. package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
  71. package/dist/lib/ensure-curated-kb-catalog.js +53 -0
  72. package/dist/lib/env.d.ts +1 -5
  73. package/dist/lib/env.js +2 -7
  74. package/dist/lib/inference.d.ts +1 -8
  75. package/dist/lib/inference.js +1 -19
  76. package/dist/lib/kb-meta.d.ts +8 -0
  77. package/dist/lib/kb-meta.js +77 -0
  78. package/dist/lib/note-text.d.ts +1 -0
  79. package/dist/lib/note-text.js +47 -0
  80. package/dist/lib/notification-service.test.js +37 -36
  81. package/dist/lib/pdf.d.ts +11 -0
  82. package/dist/lib/pdf.js +11 -0
  83. package/dist/lib/usage_service.d.ts +2 -1
  84. package/dist/lib/usage_service.js +30 -12
  85. package/dist/lib/worksheet-generation.js +4 -4
  86. package/dist/lib/worksheet-generation.test.js +32 -17
  87. package/dist/lib/workspace-kb.d.ts +5 -0
  88. package/dist/lib/workspace-kb.js +7 -0
  89. package/dist/models/controller-context.model.d.ts +8 -0
  90. package/dist/models/controller-context.model.js +1 -0
  91. package/dist/repositories/artifact.repository.d.ts +60 -0
  92. package/dist/repositories/artifact.repository.js +40 -0
  93. package/dist/repositories/base.repository.d.ts +14 -0
  94. package/dist/repositories/base.repository.js +14 -0
  95. package/dist/repositories/invitation.repository.d.ts +94 -0
  96. package/dist/repositories/invitation.repository.js +44 -0
  97. package/dist/repositories/notification.repository.d.ts +72 -0
  98. package/dist/repositories/notification.repository.js +44 -0
  99. package/dist/repositories/router-module.repository.d.ts +10 -0
  100. package/dist/repositories/router-module.repository.js +14 -0
  101. package/dist/repositories/user.repository.d.ts +74 -0
  102. package/dist/repositories/user.repository.js +37 -0
  103. package/dist/repositories/workspace-member.repository.d.ts +31 -0
  104. package/dist/repositories/workspace-member.repository.js +31 -0
  105. package/dist/repositories/workspace.repository.d.ts +97 -0
  106. package/dist/repositories/workspace.repository.js +79 -0
  107. package/dist/routers/_app.d.ts +566 -111
  108. package/dist/routers/_app.js +4 -0
  109. package/dist/routers/admin.d.ts +0 -4
  110. package/dist/routers/admin.js +21 -549
  111. package/dist/routers/annotations.js +12 -170
  112. package/dist/routers/artifactVersions.d.ts +65 -0
  113. package/dist/routers/artifactVersions.js +14 -0
  114. package/dist/routers/auth.d.ts +0 -6
  115. package/dist/routers/auth.js +37 -422
  116. package/dist/routers/chat.js +15 -229
  117. package/dist/routers/copilot.d.ts +14 -13
  118. package/dist/routers/copilot.js +13 -532
  119. package/dist/routers/flashcards.d.ts +17 -6
  120. package/dist/routers/flashcards.js +23 -349
  121. package/dist/routers/knowledgeBase.d.ts +421 -0
  122. package/dist/routers/knowledgeBase.js +118 -0
  123. package/dist/routers/members.d.ts +0 -41
  124. package/dist/routers/members.js +22 -710
  125. package/dist/routers/notes.d.ts +94 -0
  126. package/dist/routers/notes.js +37 -0
  127. package/dist/routers/notifications.js +7 -109
  128. package/dist/routers/payment.d.ts +2 -12
  129. package/dist/routers/payment.js +11 -393
  130. package/dist/routers/podcast.d.ts +1 -1
  131. package/dist/routers/podcast.js +11 -784
  132. package/dist/routers/studyguide.js +3 -129
  133. package/dist/routers/worksheets.d.ts +29 -14
  134. package/dist/routers/worksheets.js +49 -628
  135. package/dist/routers/workspace.d.ts +27 -71
  136. package/dist/routers/workspace.js +29 -922
  137. package/dist/scripts/purge-deleted-users.js +2 -2
  138. package/dist/server.js +10 -3
  139. package/dist/services/activity/activity-human-description.service.d.ts +13 -0
  140. package/dist/services/activity/activity-human-description.service.js +221 -0
  141. package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
  142. package/dist/services/activity/activity-human-description.service.test.js +16 -0
  143. package/dist/services/activity/activity-log.service.d.ts +87 -0
  144. package/dist/services/activity/activity-log.service.js +276 -0
  145. package/dist/services/activity/activity-log.service.test.d.ts +1 -0
  146. package/dist/services/activity/activity-log.service.test.js +27 -0
  147. package/dist/services/activity-human-description.service.d.ts +13 -0
  148. package/dist/services/activity-human-description.service.js +221 -0
  149. package/dist/services/activity-human-description.service.test.d.ts +1 -0
  150. package/dist/services/activity-human-description.service.test.js +16 -0
  151. package/dist/services/activity-log.service.d.ts +87 -0
  152. package/dist/services/activity-log.service.js +276 -0
  153. package/dist/services/activity-log.service.test.d.ts +1 -0
  154. package/dist/services/activity-log.service.test.js +27 -0
  155. package/dist/services/admin/admin.service.d.ts +270 -0
  156. package/dist/services/admin/admin.service.js +476 -0
  157. package/dist/services/admin.service.d.ts +270 -0
  158. package/dist/services/admin.service.js +476 -0
  159. package/dist/services/ai/ai-session.service.d.ts +5 -0
  160. package/dist/services/ai/ai-session.service.js +4 -0
  161. package/dist/services/ai-session.service.d.ts +60 -0
  162. package/dist/services/ai-session.service.js +561 -0
  163. package/dist/services/annotation.service.d.ts +177 -0
  164. package/dist/services/annotation.service.js +154 -0
  165. package/dist/services/artifact-notification.service.d.ts +14 -0
  166. package/dist/services/artifact-notification.service.js +20 -0
  167. package/dist/services/artifact-version.service.d.ts +38 -0
  168. package/dist/services/artifact-version.service.js +129 -0
  169. package/dist/services/artifacts/annotation.service.d.ts +177 -0
  170. package/dist/services/artifacts/annotation.service.js +154 -0
  171. package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
  172. package/dist/services/artifacts/artifact-version.service.js +129 -0
  173. package/dist/services/artifacts/chat.service.d.ts +127 -0
  174. package/dist/services/artifacts/chat.service.js +182 -0
  175. package/dist/services/artifacts/study-guide.service.d.ts +18 -0
  176. package/dist/services/artifacts/study-guide.service.js +65 -0
  177. package/dist/services/auth/auth.service.d.ts +94 -0
  178. package/dist/services/auth/auth.service.js +368 -0
  179. package/dist/services/auth.service.d.ts +94 -0
  180. package/dist/services/auth.service.js +368 -0
  181. package/dist/services/base.service.d.ts +14 -0
  182. package/dist/services/base.service.js +14 -0
  183. package/dist/services/billing/payment.service.d.ts +44 -0
  184. package/dist/services/billing/payment.service.js +365 -0
  185. package/dist/services/billing/subscription.service.d.ts +37 -0
  186. package/dist/services/billing/subscription.service.js +654 -0
  187. package/dist/services/billing/usage.service.d.ts +47 -0
  188. package/dist/services/billing/usage.service.js +149 -0
  189. package/dist/services/chat.service.d.ts +127 -0
  190. package/dist/services/chat.service.js +182 -0
  191. package/dist/services/content/copilot.service.d.ts +113 -0
  192. package/dist/services/content/copilot.service.js +439 -0
  193. package/dist/services/content/flashcard-progress.service.d.ts +159 -0
  194. package/dist/services/content/flashcard-progress.service.js +432 -0
  195. package/dist/services/content/flashcard.service.d.ts +184 -0
  196. package/dist/services/content/flashcard.service.js +339 -0
  197. package/dist/services/content/media-analysis.service.d.ts +23 -0
  198. package/dist/services/content/media-analysis.service.js +404 -0
  199. package/dist/services/content/podcast.service.d.ts +267 -0
  200. package/dist/services/content/podcast.service.js +653 -0
  201. package/dist/services/content/worksheet-content.service.d.ts +37 -0
  202. package/dist/services/content/worksheet-content.service.js +84 -0
  203. package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
  204. package/dist/services/content/worksheet-content.service.test.js +69 -0
  205. package/dist/services/content/worksheet-generation.service.d.ts +91 -0
  206. package/dist/services/content/worksheet-generation.service.js +95 -0
  207. package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
  208. package/dist/services/content/worksheet-generation.service.test.js +20 -0
  209. package/dist/services/content/worksheet.service.d.ts +347 -0
  210. package/dist/services/content/worksheet.service.js +599 -0
  211. package/dist/services/copilot.service.d.ts +116 -0
  212. package/dist/services/copilot.service.js +447 -0
  213. package/dist/services/flashcard-progress.service.d.ts +2 -2
  214. package/dist/services/flashcard-progress.service.js +3 -2
  215. package/dist/services/flashcard.service.d.ts +140 -0
  216. package/dist/services/flashcard.service.js +325 -0
  217. package/dist/services/invitation.service.d.ts +66 -0
  218. package/dist/services/invitation.service.js +348 -0
  219. package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
  220. package/dist/services/knowledge/knowledge-base.service.js +544 -0
  221. package/dist/services/knowledge-base.service.d.ts +316 -0
  222. package/dist/services/knowledge-base.service.js +536 -0
  223. package/dist/services/media-analysis.service.d.ts +23 -0
  224. package/dist/services/media-analysis.service.js +384 -0
  225. package/dist/services/member.service.d.ts +36 -0
  226. package/dist/services/member.service.js +193 -0
  227. package/dist/services/members/invitation.service.d.ts +66 -0
  228. package/dist/services/members/invitation.service.js +348 -0
  229. package/dist/services/members/member.service.d.ts +36 -0
  230. package/dist/services/members/member.service.js +193 -0
  231. package/dist/services/note.service.d.ts +55 -0
  232. package/dist/services/note.service.js +111 -0
  233. package/dist/services/notification.service.d.ts +214 -0
  234. package/dist/services/notification.service.js +550 -0
  235. package/dist/services/notification.service.test.d.ts +1 -0
  236. package/dist/services/notification.service.test.js +87 -0
  237. package/dist/services/notifications/notification.service.d.ts +214 -0
  238. package/dist/services/notifications/notification.service.js +550 -0
  239. package/dist/services/notifications/notification.service.test.d.ts +1 -0
  240. package/dist/services/notifications/notification.service.test.js +87 -0
  241. package/dist/services/payment.service.d.ts +55 -0
  242. package/dist/services/payment.service.js +368 -0
  243. package/dist/services/podcast.service.d.ts +267 -0
  244. package/dist/services/podcast.service.js +654 -0
  245. package/dist/services/router-module.service.d.ts +7 -0
  246. package/dist/services/router-module.service.js +10 -0
  247. package/dist/services/study-guide.service.d.ts +18 -0
  248. package/dist/services/study-guide.service.js +65 -0
  249. package/dist/services/subscription.service.d.ts +37 -0
  250. package/dist/services/subscription.service.js +654 -0
  251. package/dist/services/usage-limit-policy.service.d.ts +12 -0
  252. package/dist/services/usage-limit-policy.service.js +22 -0
  253. package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
  254. package/dist/services/usage-limit-policy.service.test.js +46 -0
  255. package/dist/services/usage.service.d.ts +27 -0
  256. package/dist/services/usage.service.js +77 -0
  257. package/dist/services/worksheet-content.service.d.ts +42 -0
  258. package/dist/services/worksheet-content.service.js +84 -0
  259. package/dist/services/worksheet-content.service.test.d.ts +1 -0
  260. package/dist/services/worksheet-content.service.test.js +69 -0
  261. package/dist/services/worksheet-generation.service.d.ts +91 -0
  262. package/dist/services/worksheet-generation.service.js +95 -0
  263. package/dist/services/worksheet-generation.service.test.d.ts +1 -0
  264. package/dist/services/worksheet-generation.service.test.js +20 -0
  265. package/dist/services/worksheet.service.d.ts +385 -0
  266. package/dist/services/worksheet.service.js +596 -0
  267. package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
  268. package/dist/services/workspace/workspace-analytics.service.js +95 -0
  269. package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
  270. package/dist/services/workspace/workspace-kb.service.js +184 -0
  271. package/dist/services/workspace/workspace.service.d.ts +263 -0
  272. package/dist/services/workspace/workspace.service.js +401 -0
  273. package/dist/services/workspace-analytics.service.d.ts +24 -0
  274. package/dist/services/workspace-analytics.service.js +95 -0
  275. package/dist/services/workspace-kb.service.d.ts +40 -0
  276. package/dist/services/workspace-kb.service.js +184 -0
  277. package/dist/services/workspace-progress.service.d.ts +27 -0
  278. package/dist/services/workspace-progress.service.js +56 -0
  279. package/dist/services/workspace-progress.service.test.d.ts +1 -0
  280. package/dist/services/workspace-progress.service.test.js +49 -0
  281. package/dist/services/workspace.service.d.ts +307 -0
  282. package/dist/services/workspace.service.js +390 -0
  283. package/dist/trpc.d.ts +12 -4
  284. package/dist/trpc.js +7 -13
  285. package/package.json +5 -6
  286. package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
  287. package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
  288. package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
  289. package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
  290. package/prisma/schema.prisma +150 -48
  291. package/prisma/seed.mjs +67 -0
  292. package/scripts/debug/README.md +4 -0
  293. package/src/README.md +63 -0
  294. package/src/context.ts +33 -3
  295. package/src/lib/ai/config.ts +34 -0
  296. package/src/lib/ai/embedding-client.ts +47 -0
  297. package/src/lib/ai/index.ts +65 -0
  298. package/src/lib/ai/inference-backend/client.ts +479 -0
  299. package/src/lib/ai/inference-backend/mocks.ts +171 -0
  300. package/src/lib/ai/inference-backend/types.ts +50 -0
  301. package/src/lib/ai/json-parse.ts +35 -0
  302. package/src/lib/ai/llm-client.ts +54 -0
  303. package/src/lib/ai/mock.ts +12 -0
  304. package/src/lib/ai/types.ts +11 -0
  305. package/src/lib/chunking.ts +81 -0
  306. package/src/lib/curated-kb-seed.ts +164 -0
  307. package/src/lib/email.ts +77 -115
  308. package/src/lib/embeddings.ts +9 -0
  309. package/src/lib/ensure-curated-kb-catalog.ts +60 -0
  310. package/src/lib/env.ts +2 -7
  311. package/src/lib/inference.ts +1 -21
  312. package/src/lib/kb-meta.ts +81 -0
  313. package/src/lib/pdf.ts +23 -0
  314. package/src/lib/workspace-kb.ts +7 -0
  315. package/src/repositories/artifact.repository.ts +55 -0
  316. package/src/repositories/base.repository.ts +19 -0
  317. package/src/repositories/invitation.repository.ts +53 -0
  318. package/src/repositories/notification.repository.ts +53 -0
  319. package/src/repositories/user.repository.ts +44 -0
  320. package/src/repositories/workspace-member.repository.ts +38 -0
  321. package/src/repositories/workspace.repository.ts +89 -0
  322. package/src/routers/_app.ts +4 -0
  323. package/src/routers/admin.ts +124 -692
  324. package/src/routers/annotations.ts +25 -203
  325. package/src/routers/artifactVersions.ts +32 -0
  326. package/src/routers/auth.ts +82 -520
  327. package/src/routers/chat.ts +42 -245
  328. package/src/routers/copilot.ts +41 -666
  329. package/src/routers/flashcards.ts +108 -404
  330. package/src/routers/knowledgeBase.ts +216 -0
  331. package/src/routers/members.ts +60 -782
  332. package/src/routers/notifications.ts +15 -117
  333. package/src/routers/payment.ts +37 -446
  334. package/src/routers/podcast.ts +36 -898
  335. package/src/routers/studyguide.ts +5 -144
  336. package/src/routers/worksheets.ts +171 -735
  337. package/src/routers/workspace.ts +141 -1108
  338. package/src/scripts/purge-deleted-users.ts +2 -2
  339. package/src/server.ts +10 -3
  340. package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
  341. package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
  342. package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
  343. package/src/services/admin/admin.service.ts +612 -0
  344. package/src/services/ai/ai-session.service.ts +5 -0
  345. package/src/services/artifacts/annotation.service.ts +189 -0
  346. package/src/services/artifacts/artifact-version.service.ts +151 -0
  347. package/src/services/artifacts/chat.service.ts +197 -0
  348. package/src/services/artifacts/study-guide.service.ts +72 -0
  349. package/src/services/auth/auth.service.ts +473 -0
  350. package/src/services/base.service.ts +19 -0
  351. package/src/services/billing/payment.service.ts +433 -0
  352. package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
  353. package/src/services/billing/usage.service.ts +207 -0
  354. package/src/services/content/copilot.service.ts +587 -0
  355. package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +18 -12
  356. package/src/services/content/flashcard.service.ts +417 -0
  357. package/src/services/content/media-analysis.service.ts +561 -0
  358. package/src/services/content/podcast.service.ts +777 -0
  359. package/src/services/content/worksheet-content.service.test.ts +83 -0
  360. package/src/services/content/worksheet-content.service.ts +117 -0
  361. package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +3 -3
  362. package/src/services/content/worksheet.service.ts +751 -0
  363. package/src/services/knowledge/knowledge-base.service.ts +705 -0
  364. package/src/services/members/invitation.service.ts +427 -0
  365. package/src/services/members/member.service.ts +241 -0
  366. package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
  367. package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
  368. package/src/services/workspace/workspace-analytics.service.ts +107 -0
  369. package/src/services/workspace/workspace-kb.service.ts +273 -0
  370. package/src/services/workspace/workspace.service.ts +488 -0
  371. package/src/trpc.ts +7 -15
  372. package/src/lib/ai-session.ts +0 -704
  373. package/src/lib/usage_service.ts +0 -74
  374. package/src/lib/workspace-access.ts +0 -13
  375. /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
  376. /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
  377. /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
  378. /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
  379. /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
  380. /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
  381. /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
  382. /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
  383. /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
@@ -1,15 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import { router, publicProcedure, authedProcedure } from '../trpc.js';
3
- import PusherService from '../lib/pusher.js';
4
- import { logger } from '../lib/logger.js';
5
- import bcrypt from 'bcryptjs';
6
3
  import { serialize } from 'cookie';
7
- import crypto from 'node:crypto';
8
4
  import { TRPCError } from '@trpc/server';
9
- import { supabaseClient } from '../lib/storage.js';
10
- import { sendVerificationEmail, sendAccountDeletionScheduledEmail, sendAccountRestoredEmail, sendPasswordResetEmail, } from '../lib/email.js';
11
- import { createStripeCustomer } from '../lib/stripe.js';
12
- import { notifyAdminsAccountDeletionScheduled, notifyAdminsOnSignup, } from '../lib/notification-service.js';
5
+ import PusherService from '../lib/pusher.js';
6
+ import { logger } from '../lib/logger.js';
7
+ import { AuthService } from '../services/auth/auth.service.js';
8
+ /**
9
+ * Router owns HTTP-level concerns (cookie set/clear). The service is
10
+ * HTTP-free and returns pure data; the router wires it into `ctx.res`.
11
+ */
13
12
  function getAuthCookieConfig(isProduction) {
14
13
  return {
15
14
  httpOnly: true,
@@ -17,129 +16,27 @@ function getAuthCookieConfig(isProduction) {
17
16
  sameSite: (isProduction ? 'none' : 'lax'),
18
17
  path: '/',
19
18
  maxAge: 60 * 60 * 24 * 30,
20
- // Use parent domain in production so scribe.study and api.scribe.study share auth state.
21
19
  domain: isProduction ? '.scribe.study' : undefined,
22
20
  };
23
21
  }
24
- // Helper to create custom auth token
25
22
  const passwordFieldSchema = z
26
23
  .string()
27
24
  .min(8, 'Password must be at least 8 characters')
28
25
  .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
29
26
  .regex(/[0-9]/, 'Password must contain at least one number')
30
27
  .regex(/[^a-zA-Z0-9]/, 'Password must contain at least one special character');
31
- function hashPasswordResetToken(rawToken) {
32
- return crypto.createHash('sha256').update(rawToken, 'utf8').digest('hex');
33
- }
34
- /** Use until `npx prisma generate` runs after adding PasswordResetToken to the schema. */
35
- function passwordResetDb(ctx) {
36
- return ctx.db.passwordResetToken;
37
- }
38
- function createCustomAuthToken(userId) {
39
- const secret = process.env.AUTH_SECRET;
40
- if (!secret) {
41
- throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: "AUTH_SECRET is not set" });
42
- }
43
- const base64UserId = Buffer.from(userId, 'utf8').toString('base64url');
44
- const hmac = crypto.createHmac('sha256', secret);
45
- hmac.update(base64UserId);
46
- const signature = hmac.digest('hex');
47
- return `${base64UserId}.${signature}`;
48
- }
49
28
  export const auth = router({
50
- updateProfile: publicProcedure
51
- .input(z.object({
52
- name: z.string().min(1),
53
- }))
54
- .mutation(async ({ ctx, input }) => {
55
- const { name } = input;
56
- await ctx.db.user.update({
57
- where: {
58
- id: ctx.session.user.id,
59
- },
60
- data: {
61
- name: name,
62
- }
63
- });
64
- return {
65
- success: true,
66
- message: 'Profile updated successfully',
67
- };
68
- }),
29
+ updateProfile: authedProcedure
30
+ .input(z.object({ name: z.string().min(1) }))
31
+ .mutation(({ ctx, input }) => new AuthService(ctx.db).updateProfile(ctx.session.user.id, input)),
69
32
  changePassword: authedProcedure
70
33
  .input(z.object({
71
- currentPassword: z.string().min(1, "Current password is required"),
34
+ currentPassword: z.string().min(1, 'Current password is required'),
72
35
  newPassword: passwordFieldSchema,
73
36
  }))
74
- .mutation(async ({ ctx, input }) => {
75
- const user = await ctx.db.user.findUnique({
76
- where: { id: ctx.session.user.id },
77
- select: { id: true, passwordHash: true },
78
- });
79
- if (!user) {
80
- throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
81
- }
82
- if (!user.passwordHash) {
83
- throw new TRPCError({
84
- code: 'BAD_REQUEST',
85
- message: 'Password change is unavailable for this account.',
86
- });
87
- }
88
- const validCurrentPassword = await bcrypt.compare(input.currentPassword, user.passwordHash);
89
- if (!validCurrentPassword) {
90
- throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Current password is incorrect' });
91
- }
92
- const isSamePassword = await bcrypt.compare(input.newPassword, user.passwordHash);
93
- if (isSamePassword) {
94
- throw new TRPCError({
95
- code: 'BAD_REQUEST',
96
- message: 'New password must be different from current password',
97
- });
98
- }
99
- const newHash = await bcrypt.hash(input.newPassword, 10);
100
- await ctx.db.user.update({
101
- where: { id: user.id },
102
- data: { passwordHash: newHash },
103
- });
104
- return { success: true, message: 'Password changed successfully' };
105
- }),
106
- uploadProfilePicture: authedProcedure
107
- .mutation(async ({ ctx }) => {
108
- const userId = ctx.session.user.id;
109
- logger.info(`Generating upload URL for user ${userId}`, 'AUTH');
110
- const objectKey = `profile_picture_${userId}`;
111
- const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
112
- .from('media')
113
- .createSignedUploadUrl(objectKey, { upsert: true });
114
- if (signedUrlError) {
115
- logger.error(`Failed to generate upload URL: ${signedUrlError.message}`, 'AUTH');
116
- throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: `Failed to generate upload URL: ${signedUrlError.message}` });
117
- }
118
- const fileAsset = await ctx.db.fileAsset.create({
119
- data: {
120
- userId: userId,
121
- name: 'Profile Picture',
122
- mimeType: 'image/jpeg',
123
- size: 0,
124
- bucket: 'media',
125
- objectKey: objectKey,
126
- },
127
- });
128
- await ctx.db.user.update({
129
- where: { id: userId },
130
- data: {
131
- fileAssetId: fileAsset.id,
132
- },
133
- });
134
- logger.info(`Profile picture asset created and linked for user ${userId}`, 'AUTH');
135
- return {
136
- success: true,
137
- message: 'Profile picture uploaded successfully',
138
- signedUrl: signedUrlData.signedUrl,
139
- };
140
- }),
141
- confirmProfileUpdate: authedProcedure
142
- .mutation(async ({ ctx }) => {
37
+ .mutation(({ ctx, input }) => new AuthService(ctx.db).changePassword(ctx.session.user.id, input)),
38
+ uploadProfilePicture: authedProcedure.mutation(({ ctx }) => new AuthService(ctx.db).uploadProfilePicture(ctx.session.user.id)),
39
+ confirmProfileUpdate: authedProcedure.mutation(async ({ ctx }) => {
143
40
  logger.info(`Confirming profile update for user ${ctx.session.user.id}`, 'AUTH');
144
41
  await PusherService.emitProfileUpdate(ctx.session.user.id);
145
42
  return { success: true };
@@ -150,333 +47,51 @@ export const auth = router({
150
47
  email: z.string().email(),
151
48
  password: passwordFieldSchema,
152
49
  }))
153
- .mutation(async ({ ctx, input }) => {
154
- const existing = await ctx.db.user.findUnique({
155
- where: { email: input.email },
156
- });
157
- if (existing) {
158
- throw new TRPCError({ code: 'CONFLICT', message: "Email already registered" });
159
- }
160
- const hash = await bcrypt.hash(input.password, 10);
161
- // Get default "User" role
162
- const userRole = await ctx.db.role.findUnique({
163
- where: { name: 'User' },
164
- });
165
- const user = await ctx.db.user.create({
166
- data: {
167
- name: input.name,
168
- email: input.email,
169
- passwordHash: hash,
170
- roleId: userRole?.id,
171
- // emailVerified is null -- user must verify
172
- },
173
- });
174
- await notifyAdminsOnSignup(ctx.db, {
175
- id: user.id,
176
- name: user.name,
177
- email: user.email,
178
- });
179
- // Create verification token (24h expiry)
180
- const token = crypto.randomUUID();
181
- await ctx.db.verificationToken.create({
182
- data: {
183
- identifier: input.email,
184
- token,
185
- expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
186
- },
187
- });
188
- // Send verification email (non-blocking)
189
- sendVerificationEmail(input.email, token, input.name).catch(() => { });
190
- // Create Stripe Customer (non-blocking for registration, but we want it)
191
- createStripeCustomer(input.email, input.name).then(async (stripeCustomerId) => {
192
- if (stripeCustomerId) {
193
- await ctx.db.user.update({
194
- where: { id: user.id },
195
- data: { stripe_customer_id: stripeCustomerId }
196
- }).catch((err) => logger.error(`Failed to update user with stripe_customer_id: ${err.message}`, 'AUTH'));
197
- }
198
- });
199
- return { id: user.id, email: user.email, name: user.name };
200
- }),
201
- // Verify email with token from the email link
50
+ .mutation(({ ctx, input }) => new AuthService(ctx.db).signup(input)),
202
51
  verifyEmail: publicProcedure
203
- .input(z.object({
204
- token: z.string(),
205
- }))
206
- .mutation(async ({ ctx, input }) => {
207
- const record = await ctx.db.verificationToken.findUnique({
208
- where: { token: input.token },
209
- });
210
- if (!record) {
211
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Invalid or expired verification link' });
212
- }
213
- if (record.expires < new Date()) {
214
- // Clean up expired token
215
- await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
216
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'Verification link has expired. Please request a new one.' });
217
- }
218
- // Mark user as verified
219
- await ctx.db.user.update({
220
- where: { email: record.identifier },
221
- data: { emailVerified: new Date() },
222
- });
223
- // Delete used token
224
- await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
225
- return { success: true, message: 'Email verified successfully' };
226
- }),
227
- // Resend verification email (for logged-in users who haven't verified)
228
- resendVerification: publicProcedure
229
- .mutation(async ({ ctx }) => {
230
- if (!ctx.session?.user?.id) {
231
- throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not logged in' });
232
- }
233
- const user = await ctx.db.user.findUnique({
234
- where: { id: ctx.session.user.id },
235
- });
236
- if (!user || !user.email) {
237
- throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
238
- }
239
- if (user.emailVerified) {
240
- return { success: true, message: 'Email is already verified' };
241
- }
242
- // Delete any existing tokens for this email
243
- await ctx.db.verificationToken.deleteMany({
244
- where: { identifier: user.email },
245
- });
246
- // Create new token
247
- const token = crypto.randomUUID();
248
- await ctx.db.verificationToken.create({
249
- data: {
250
- identifier: user.email,
251
- token,
252
- expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
253
- },
254
- });
255
- const sent = await sendVerificationEmail(user.email, token, user.name);
256
- if (!sent) {
257
- throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to send email. Please try again.' });
258
- }
259
- return { success: true, message: 'Verification email sent' };
260
- }),
52
+ .input(z.object({ token: z.string() }))
53
+ .mutation(({ ctx, input }) => new AuthService(ctx.db).verifyEmail(input.token)),
54
+ resendVerification: publicProcedure.mutation(({ ctx }) => new AuthService(ctx.db).resendVerification(ctx.session?.user?.id)),
261
55
  login: publicProcedure
262
56
  .input(z.object({
263
57
  email: z.string().email(),
264
58
  password: z.string().min(6),
265
59
  }))
266
60
  .mutation(async ({ ctx, input }) => {
267
- const user = await ctx.db.user.findUnique({
268
- where: { email: input.email },
269
- });
270
- if (!user) {
271
- throw new TRPCError({ code: 'UNAUTHORIZED', message: "Invalid credentials" });
272
- }
273
- if (user.deletedAt) {
274
- throw new TRPCError({ code: 'UNAUTHORIZED', message: "Account scheduled for deletion. Please check your email for a restore link." });
275
- }
276
- const valid = await bcrypt.compare(input.password, user.passwordHash);
277
- if (!valid) {
278
- throw new TRPCError({ code: 'UNAUTHORIZED', message: "Invalid credentials" });
279
- }
280
- // Create custom auth token
281
- const authToken = createCustomAuthToken(user.id);
282
- const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
283
- const cookieValue = serialize("auth_token", authToken, getAuthCookieConfig(isProduction));
284
- ctx.res.setHeader("Set-Cookie", cookieValue);
285
- return {
286
- id: user.id,
287
- email: user.email,
288
- name: user.name,
289
- token: authToken
290
- };
61
+ const result = await new AuthService(ctx.db).login(input);
62
+ const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER);
63
+ const cookieValue = serialize('auth_token', result.token, getAuthCookieConfig(isProduction));
64
+ ctx.res.setHeader('Set-Cookie', cookieValue);
65
+ return result;
291
66
  }),
292
- /**
293
- * Request a password reset email. Always returns the same message (no email enumeration).
294
- */
295
67
  requestPasswordReset: publicProcedure
296
68
  .input(z.object({ email: z.string().email() }))
297
- .mutation(async ({ ctx, input }) => {
298
- const email = input.email.trim().toLowerCase();
299
- const generic = {
300
- success: true,
301
- message: 'If an account exists for this email, we sent password reset instructions.',
302
- };
303
- const user = await ctx.db.user.findUnique({
304
- where: { email },
305
- select: {
306
- id: true,
307
- email: true,
308
- name: true,
309
- passwordHash: true,
310
- deletedAt: true,
311
- },
312
- });
313
- if (!user?.passwordHash || user.deletedAt || !user.email) {
314
- return generic;
315
- }
316
- await passwordResetDb(ctx).deleteMany({
317
- where: { userId: user.id, usedAt: null },
318
- });
319
- const rawToken = crypto.randomBytes(32).toString('hex');
320
- const tokenHash = hashPasswordResetToken(rawToken);
321
- const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
322
- await passwordResetDb(ctx).create({
323
- data: {
324
- userId: user.id,
325
- tokenHash,
326
- expiresAt,
327
- },
328
- });
329
- sendPasswordResetEmail(user.email, rawToken, user.name).catch(() => { });
330
- return generic;
331
- }),
332
- /**
333
- * Complete password reset using the token from the email link.
334
- */
69
+ .mutation(({ ctx, input }) => new AuthService(ctx.db).requestPasswordReset(input)),
335
70
  resetPassword: publicProcedure
336
71
  .input(z.object({
337
72
  token: z.string().min(1),
338
73
  newPassword: passwordFieldSchema,
339
74
  }))
340
- .mutation(async ({ ctx, input }) => {
341
- const tokenHash = hashPasswordResetToken(input.token);
342
- const record = await passwordResetDb(ctx).findUnique({
343
- where: { tokenHash },
344
- });
345
- if (!record || record.usedAt || record.expiresAt < new Date()) {
346
- throw new TRPCError({
347
- code: 'BAD_REQUEST',
348
- message: 'Invalid or expired reset link. Please request a new one.',
349
- });
350
- }
351
- const newHash = await bcrypt.hash(input.newPassword, 10);
352
- await ctx.db.user.update({
353
- where: { id: record.userId },
354
- data: { passwordHash: newHash },
355
- });
356
- await passwordResetDb(ctx).update({
357
- where: { id: record.id },
358
- data: { usedAt: new Date() },
359
- });
360
- await passwordResetDb(ctx).deleteMany({
361
- where: { userId: record.userId, id: { not: record.id } },
362
- });
363
- return { success: true, message: 'Password updated. You can sign in now.' };
364
- }),
365
- getSession: publicProcedure.query(async ({ ctx }) => {
366
- // Just return the current session from context
367
- if (!ctx.session) {
368
- throw new TRPCError({ code: 'UNAUTHORIZED', message: "No session found" });
369
- }
370
- const userId = ctx.session.user.id;
371
- const user = await ctx.db.user.findUnique({
372
- where: { id: userId },
373
- include: {
374
- profilePicture: true,
375
- role: true,
376
- }
377
- });
378
- if (!user) {
379
- throw new TRPCError({ code: 'NOT_FOUND', message: "User not found" });
380
- }
381
- const profilePictureUrl = user.profilePicture?.objectKey
382
- ? `/profile-picture/${user.profilePicture.objectKey}?t=${new Date(user.updatedAt).getTime()}`
383
- : null;
384
- logger.info(`Session fetched for user ${userId}, profilePicture: ${profilePictureUrl}`, 'AUTH');
385
- return {
386
- user: {
387
- id: user.id,
388
- email: user.email,
389
- name: user.name,
390
- emailVerified: !!user.emailVerified,
391
- profilePicture: profilePictureUrl,
392
- role: user.role,
393
- }
394
- };
395
- }),
396
- requestAccountDeletion: authedProcedure
397
- .mutation(async ({ ctx }) => {
398
- const user = await ctx.db.user.findUnique({
399
- where: { id: ctx.session.user.id },
400
- });
401
- if (!user) {
402
- throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
403
- }
404
- // Mark user as deleted
405
- await ctx.db.user.update({
406
- where: { id: user.id },
407
- data: { deletedAt: new Date() },
408
- });
409
- await notifyAdminsAccountDeletionScheduled(ctx.db, {
410
- id: user.id,
411
- name: user.name,
412
- email: user.email,
413
- }).catch(() => { });
414
- // Clear existing restore tokens
415
- await ctx.db.verificationToken.deleteMany({
416
- where: { identifier: `restore-${user.email}` },
417
- });
418
- // Create restore token (30 days expiry)
419
- const token = crypto.randomUUID();
420
- await ctx.db.verificationToken.create({
421
- data: {
422
- identifier: `restore-${user.email}`,
423
- token,
424
- expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
425
- },
426
- });
427
- // Send email
428
- if (user.email) {
429
- sendAccountDeletionScheduledEmail(user.email, token).catch(() => { });
430
- }
431
- // Log out user by clearing cookie
432
- const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
75
+ .mutation(({ ctx, input }) => new AuthService(ctx.db).resetPassword(input)),
76
+ getSession: publicProcedure.query(({ ctx }) => new AuthService(ctx.db).getSession(ctx.session?.user?.id)),
77
+ requestAccountDeletion: authedProcedure.mutation(async ({ ctx }) => {
78
+ const result = await new AuthService(ctx.db).requestAccountDeletion(ctx.session.user.id);
79
+ const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER);
433
80
  const clearCookieConfig = getAuthCookieConfig(isProduction);
434
- ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
435
- ...clearCookieConfig,
436
- maxAge: 0,
437
- }));
438
- return { success: true, message: 'Account scheduled for deletion' };
81
+ ctx.res.setHeader('Set-Cookie', serialize('auth_token', '', { ...clearCookieConfig, maxAge: 0 }));
82
+ return result;
439
83
  }),
440
84
  restoreAccount: publicProcedure
441
- .input(z.object({
442
- token: z.string(),
443
- }))
444
- .mutation(async ({ ctx, input }) => {
445
- const record = await ctx.db.verificationToken.findUnique({
446
- where: { token: input.token },
447
- });
448
- if (!record || !record.identifier.startsWith('restore-')) {
449
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Invalid or expired restore link' });
450
- }
451
- if (record.expires < new Date()) {
452
- await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
453
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'Restore link has expired.' });
454
- }
455
- const email = record.identifier.replace('restore-', '');
456
- // Mark user as restored
457
- await ctx.db.user.update({
458
- where: { email },
459
- data: { deletedAt: null },
460
- });
461
- // Delete used token
462
- await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
463
- // Send confirmation email
464
- sendAccountRestoredEmail(email).catch(() => { });
465
- return { success: true, message: 'Account restored successfully' };
466
- }),
85
+ .input(z.object({ token: z.string() }))
86
+ .mutation(({ ctx, input }) => new AuthService(ctx.db).restoreAccount(input.token)),
467
87
  logout: publicProcedure.mutation(async ({ ctx }) => {
468
- const token = ctx.cookies["auth_token"];
88
+ const token = ctx.cookies['auth_token'];
469
89
  if (!token) {
470
- throw new TRPCError({ code: 'UNAUTHORIZED', message: "No token found" });
90
+ throw new TRPCError({ code: 'UNAUTHORIZED', message: 'No token found' });
471
91
  }
472
- // We don't need to delete from db.session because we use a stateless
473
- // custom HMAC auth system (auth_token cookie).
474
- const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
92
+ const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER);
475
93
  const clearCookieConfig = getAuthCookieConfig(isProduction);
476
- ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
477
- ...clearCookieConfig,
478
- maxAge: 0, // Expire immediately
479
- }));
94
+ ctx.res.setHeader('Set-Cookie', serialize('auth_token', '', { ...clearCookieConfig, maxAge: 0 }));
480
95
  return { success: true };
481
96
  }),
482
97
  });