@goscribe/server 1.3.3 → 1.5.0

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