@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.
- package/.env.example +12 -0
- package/.vscode/settings.json +3 -0
- package/REFACTOR_NOTES.md +60 -0
- package/TESTING_PROMPT.md +225 -0
- package/dist/context.d.ts +14 -1
- package/dist/context.js +23 -2
- package/dist/controllers/admin.controller.d.ts +715 -0
- package/dist/controllers/admin.controller.js +9 -0
- package/dist/controllers/annotations.controller.d.ts +439 -0
- package/dist/controllers/annotations.controller.js +9 -0
- package/dist/controllers/app-router.controller.d.ts +3011 -0
- package/dist/controllers/app-router.controller.js +38 -0
- package/dist/controllers/app-router.controller.test.d.ts +1 -0
- package/dist/controllers/app-router.controller.test.js +36 -0
- package/dist/controllers/auth.controller.d.ts +323 -0
- package/dist/controllers/auth.controller.js +9 -0
- package/dist/controllers/base.controller.d.ts +4 -0
- package/dist/controllers/base.controller.js +5 -0
- package/dist/controllers/chat.controller.d.ts +341 -0
- package/dist/controllers/chat.controller.js +9 -0
- package/dist/controllers/copilot.controller.d.ts +397 -0
- package/dist/controllers/copilot.controller.js +9 -0
- package/dist/controllers/flashcards.controller.d.ts +651 -0
- package/dist/controllers/flashcards.controller.js +9 -0
- package/dist/controllers/members.controller.d.ts +339 -0
- package/dist/controllers/members.controller.js +9 -0
- package/dist/controllers/notifications.controller.d.ts +199 -0
- package/dist/controllers/notifications.controller.js +9 -0
- package/dist/controllers/payment.controller.d.ts +181 -0
- package/dist/controllers/payment.controller.js +9 -0
- package/dist/controllers/podcast.controller.d.ts +575 -0
- package/dist/controllers/podcast.controller.js +9 -0
- package/dist/controllers/router-module.controller.d.ts +5 -0
- package/dist/controllers/router-module.controller.js +6 -0
- package/dist/controllers/studyguide.controller.d.ts +73 -0
- package/dist/controllers/studyguide.controller.js +9 -0
- package/dist/controllers/worksheets.controller.d.ts +829 -0
- package/dist/controllers/worksheets.controller.js +9 -0
- package/dist/controllers/workspace.controller.d.ts +1207 -0
- package/dist/controllers/workspace.controller.js +9 -0
- package/dist/lib/activity_human_description.test.js +16 -15
- package/dist/lib/activity_log_service.test.js +28 -23
- package/dist/lib/ai/config.d.ts +20 -0
- package/dist/lib/ai/config.js +31 -0
- package/dist/lib/ai/embedding-client.d.ts +8 -0
- package/dist/lib/ai/embedding-client.js +30 -0
- package/dist/lib/ai/index.d.ts +48 -0
- package/dist/lib/ai/index.js +29 -0
- package/dist/lib/ai/inference-backend/client.d.ts +28 -0
- package/dist/lib/ai/inference-backend/client.js +301 -0
- package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
- package/dist/lib/ai/inference-backend/mocks.js +133 -0
- package/dist/lib/ai/inference-backend/types.d.ts +44 -0
- package/dist/lib/ai/inference-backend/types.js +1 -0
- package/dist/lib/ai/json-parse.d.ts +2 -0
- package/dist/lib/ai/json-parse.js +34 -0
- package/dist/lib/ai/llm-client.d.ts +7 -0
- package/dist/lib/ai/llm-client.js +36 -0
- package/dist/lib/ai/mock.d.ts +2 -0
- package/dist/lib/ai/mock.js +10 -0
- package/dist/lib/ai/types.d.ts +9 -0
- package/dist/lib/ai/types.js +1 -0
- package/dist/lib/chunking.d.ts +19 -0
- package/dist/lib/chunking.js +47 -0
- package/dist/lib/curated-kb-seed.d.ts +12 -0
- package/dist/lib/curated-kb-seed.js +155 -0
- package/dist/lib/email.js +67 -108
- package/dist/lib/embeddings.d.ts +2 -0
- package/dist/lib/embeddings.js +1 -0
- package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
- package/dist/lib/ensure-curated-kb-catalog.js +53 -0
- package/dist/lib/env.d.ts +1 -5
- package/dist/lib/env.js +2 -7
- package/dist/lib/inference.d.ts +1 -8
- package/dist/lib/inference.js +1 -19
- package/dist/lib/kb-meta.d.ts +8 -0
- package/dist/lib/kb-meta.js +77 -0
- package/dist/lib/note-text.d.ts +1 -0
- package/dist/lib/note-text.js +47 -0
- package/dist/lib/notification-service.test.js +37 -36
- package/dist/lib/pdf.d.ts +11 -0
- package/dist/lib/pdf.js +11 -0
- package/dist/lib/usage_service.d.ts +2 -1
- package/dist/lib/usage_service.js +30 -12
- package/dist/lib/worksheet-generation.js +4 -4
- package/dist/lib/worksheet-generation.test.js +32 -17
- package/dist/lib/workspace-kb.d.ts +5 -0
- package/dist/lib/workspace-kb.js +7 -0
- package/dist/models/controller-context.model.d.ts +8 -0
- package/dist/models/controller-context.model.js +1 -0
- package/dist/repositories/artifact.repository.d.ts +60 -0
- package/dist/repositories/artifact.repository.js +40 -0
- package/dist/repositories/base.repository.d.ts +14 -0
- package/dist/repositories/base.repository.js +14 -0
- package/dist/repositories/invitation.repository.d.ts +94 -0
- package/dist/repositories/invitation.repository.js +44 -0
- package/dist/repositories/notification.repository.d.ts +72 -0
- package/dist/repositories/notification.repository.js +44 -0
- package/dist/repositories/router-module.repository.d.ts +10 -0
- package/dist/repositories/router-module.repository.js +14 -0
- package/dist/repositories/user.repository.d.ts +74 -0
- package/dist/repositories/user.repository.js +37 -0
- package/dist/repositories/workspace-member.repository.d.ts +31 -0
- package/dist/repositories/workspace-member.repository.js +31 -0
- package/dist/repositories/workspace.repository.d.ts +97 -0
- package/dist/repositories/workspace.repository.js +79 -0
- package/dist/routers/_app.d.ts +566 -111
- package/dist/routers/_app.js +4 -0
- package/dist/routers/admin.d.ts +0 -4
- package/dist/routers/admin.js +21 -549
- package/dist/routers/annotations.js +12 -170
- package/dist/routers/artifactVersions.d.ts +65 -0
- package/dist/routers/artifactVersions.js +14 -0
- package/dist/routers/auth.d.ts +0 -6
- package/dist/routers/auth.js +37 -422
- package/dist/routers/chat.js +15 -229
- package/dist/routers/copilot.d.ts +14 -13
- package/dist/routers/copilot.js +13 -532
- package/dist/routers/flashcards.d.ts +17 -6
- package/dist/routers/flashcards.js +23 -349
- package/dist/routers/knowledgeBase.d.ts +421 -0
- package/dist/routers/knowledgeBase.js +118 -0
- package/dist/routers/members.d.ts +0 -41
- package/dist/routers/members.js +22 -710
- package/dist/routers/notes.d.ts +94 -0
- package/dist/routers/notes.js +37 -0
- package/dist/routers/notifications.js +7 -109
- package/dist/routers/payment.d.ts +2 -12
- package/dist/routers/payment.js +11 -393
- package/dist/routers/podcast.d.ts +1 -1
- package/dist/routers/podcast.js +11 -784
- package/dist/routers/studyguide.js +3 -129
- package/dist/routers/worksheets.d.ts +29 -14
- package/dist/routers/worksheets.js +49 -628
- package/dist/routers/workspace.d.ts +27 -71
- package/dist/routers/workspace.js +29 -922
- package/dist/scripts/purge-deleted-users.js +2 -2
- package/dist/server.js +10 -3
- package/dist/services/activity/activity-human-description.service.d.ts +13 -0
- package/dist/services/activity/activity-human-description.service.js +221 -0
- package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
- package/dist/services/activity/activity-human-description.service.test.js +16 -0
- package/dist/services/activity/activity-log.service.d.ts +87 -0
- package/dist/services/activity/activity-log.service.js +276 -0
- package/dist/services/activity/activity-log.service.test.d.ts +1 -0
- package/dist/services/activity/activity-log.service.test.js +27 -0
- package/dist/services/activity-human-description.service.d.ts +13 -0
- package/dist/services/activity-human-description.service.js +221 -0
- package/dist/services/activity-human-description.service.test.d.ts +1 -0
- package/dist/services/activity-human-description.service.test.js +16 -0
- package/dist/services/activity-log.service.d.ts +87 -0
- package/dist/services/activity-log.service.js +276 -0
- package/dist/services/activity-log.service.test.d.ts +1 -0
- package/dist/services/activity-log.service.test.js +27 -0
- package/dist/services/admin/admin.service.d.ts +270 -0
- package/dist/services/admin/admin.service.js +476 -0
- package/dist/services/admin.service.d.ts +270 -0
- package/dist/services/admin.service.js +476 -0
- package/dist/services/ai/ai-session.service.d.ts +5 -0
- package/dist/services/ai/ai-session.service.js +4 -0
- package/dist/services/ai-session.service.d.ts +60 -0
- package/dist/services/ai-session.service.js +561 -0
- package/dist/services/annotation.service.d.ts +177 -0
- package/dist/services/annotation.service.js +154 -0
- package/dist/services/artifact-notification.service.d.ts +14 -0
- package/dist/services/artifact-notification.service.js +20 -0
- package/dist/services/artifact-version.service.d.ts +38 -0
- package/dist/services/artifact-version.service.js +129 -0
- package/dist/services/artifacts/annotation.service.d.ts +177 -0
- package/dist/services/artifacts/annotation.service.js +154 -0
- package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
- package/dist/services/artifacts/artifact-version.service.js +129 -0
- package/dist/services/artifacts/chat.service.d.ts +127 -0
- package/dist/services/artifacts/chat.service.js +182 -0
- package/dist/services/artifacts/study-guide.service.d.ts +18 -0
- package/dist/services/artifacts/study-guide.service.js +65 -0
- package/dist/services/auth/auth.service.d.ts +94 -0
- package/dist/services/auth/auth.service.js +368 -0
- package/dist/services/auth.service.d.ts +94 -0
- package/dist/services/auth.service.js +368 -0
- package/dist/services/base.service.d.ts +14 -0
- package/dist/services/base.service.js +14 -0
- package/dist/services/billing/payment.service.d.ts +44 -0
- package/dist/services/billing/payment.service.js +365 -0
- package/dist/services/billing/subscription.service.d.ts +37 -0
- package/dist/services/billing/subscription.service.js +654 -0
- package/dist/services/billing/usage.service.d.ts +47 -0
- package/dist/services/billing/usage.service.js +149 -0
- package/dist/services/chat.service.d.ts +127 -0
- package/dist/services/chat.service.js +182 -0
- package/dist/services/content/copilot.service.d.ts +113 -0
- package/dist/services/content/copilot.service.js +439 -0
- package/dist/services/content/flashcard-progress.service.d.ts +159 -0
- package/dist/services/content/flashcard-progress.service.js +432 -0
- package/dist/services/content/flashcard.service.d.ts +184 -0
- package/dist/services/content/flashcard.service.js +339 -0
- package/dist/services/content/media-analysis.service.d.ts +23 -0
- package/dist/services/content/media-analysis.service.js +404 -0
- package/dist/services/content/podcast.service.d.ts +267 -0
- package/dist/services/content/podcast.service.js +653 -0
- package/dist/services/content/worksheet-content.service.d.ts +37 -0
- package/dist/services/content/worksheet-content.service.js +84 -0
- package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
- package/dist/services/content/worksheet-content.service.test.js +69 -0
- package/dist/services/content/worksheet-generation.service.d.ts +91 -0
- package/dist/services/content/worksheet-generation.service.js +95 -0
- package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
- package/dist/services/content/worksheet-generation.service.test.js +20 -0
- package/dist/services/content/worksheet.service.d.ts +347 -0
- package/dist/services/content/worksheet.service.js +599 -0
- package/dist/services/copilot.service.d.ts +116 -0
- package/dist/services/copilot.service.js +447 -0
- package/dist/services/flashcard-progress.service.d.ts +2 -2
- package/dist/services/flashcard-progress.service.js +3 -2
- package/dist/services/flashcard.service.d.ts +140 -0
- package/dist/services/flashcard.service.js +325 -0
- package/dist/services/invitation.service.d.ts +66 -0
- package/dist/services/invitation.service.js +348 -0
- package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
- package/dist/services/knowledge/knowledge-base.service.js +544 -0
- package/dist/services/knowledge-base.service.d.ts +316 -0
- package/dist/services/knowledge-base.service.js +536 -0
- package/dist/services/media-analysis.service.d.ts +23 -0
- package/dist/services/media-analysis.service.js +384 -0
- package/dist/services/member.service.d.ts +36 -0
- package/dist/services/member.service.js +193 -0
- package/dist/services/members/invitation.service.d.ts +66 -0
- package/dist/services/members/invitation.service.js +348 -0
- package/dist/services/members/member.service.d.ts +36 -0
- package/dist/services/members/member.service.js +193 -0
- package/dist/services/note.service.d.ts +55 -0
- package/dist/services/note.service.js +111 -0
- package/dist/services/notification.service.d.ts +214 -0
- package/dist/services/notification.service.js +550 -0
- package/dist/services/notification.service.test.d.ts +1 -0
- package/dist/services/notification.service.test.js +87 -0
- package/dist/services/notifications/notification.service.d.ts +214 -0
- package/dist/services/notifications/notification.service.js +550 -0
- package/dist/services/notifications/notification.service.test.d.ts +1 -0
- package/dist/services/notifications/notification.service.test.js +87 -0
- package/dist/services/payment.service.d.ts +55 -0
- package/dist/services/payment.service.js +368 -0
- package/dist/services/podcast.service.d.ts +267 -0
- package/dist/services/podcast.service.js +654 -0
- package/dist/services/router-module.service.d.ts +7 -0
- package/dist/services/router-module.service.js +10 -0
- package/dist/services/study-guide.service.d.ts +18 -0
- package/dist/services/study-guide.service.js +65 -0
- package/dist/services/subscription.service.d.ts +37 -0
- package/dist/services/subscription.service.js +654 -0
- package/dist/services/usage-limit-policy.service.d.ts +12 -0
- package/dist/services/usage-limit-policy.service.js +22 -0
- package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
- package/dist/services/usage-limit-policy.service.test.js +46 -0
- package/dist/services/usage.service.d.ts +27 -0
- package/dist/services/usage.service.js +77 -0
- package/dist/services/worksheet-content.service.d.ts +42 -0
- package/dist/services/worksheet-content.service.js +84 -0
- package/dist/services/worksheet-content.service.test.d.ts +1 -0
- package/dist/services/worksheet-content.service.test.js +69 -0
- package/dist/services/worksheet-generation.service.d.ts +91 -0
- package/dist/services/worksheet-generation.service.js +95 -0
- package/dist/services/worksheet-generation.service.test.d.ts +1 -0
- package/dist/services/worksheet-generation.service.test.js +20 -0
- package/dist/services/worksheet.service.d.ts +385 -0
- package/dist/services/worksheet.service.js +596 -0
- package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
- package/dist/services/workspace/workspace-analytics.service.js +95 -0
- package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
- package/dist/services/workspace/workspace-kb.service.js +184 -0
- package/dist/services/workspace/workspace.service.d.ts +263 -0
- package/dist/services/workspace/workspace.service.js +401 -0
- package/dist/services/workspace-analytics.service.d.ts +24 -0
- package/dist/services/workspace-analytics.service.js +95 -0
- package/dist/services/workspace-kb.service.d.ts +40 -0
- package/dist/services/workspace-kb.service.js +184 -0
- package/dist/services/workspace-progress.service.d.ts +27 -0
- package/dist/services/workspace-progress.service.js +56 -0
- package/dist/services/workspace-progress.service.test.d.ts +1 -0
- package/dist/services/workspace-progress.service.test.js +49 -0
- package/dist/services/workspace.service.d.ts +307 -0
- package/dist/services/workspace.service.js +390 -0
- package/dist/trpc.d.ts +12 -4
- package/dist/trpc.js +7 -13
- package/package.json +5 -6
- package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
- package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
- package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
- package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
- package/prisma/schema.prisma +150 -48
- package/prisma/seed.mjs +67 -0
- package/scripts/debug/README.md +4 -0
- package/src/README.md +63 -0
- package/src/context.ts +33 -3
- package/src/lib/ai/config.ts +34 -0
- package/src/lib/ai/embedding-client.ts +47 -0
- package/src/lib/ai/index.ts +65 -0
- package/src/lib/ai/inference-backend/client.ts +479 -0
- package/src/lib/ai/inference-backend/mocks.ts +171 -0
- package/src/lib/ai/inference-backend/types.ts +50 -0
- package/src/lib/ai/json-parse.ts +35 -0
- package/src/lib/ai/llm-client.ts +54 -0
- package/src/lib/ai/mock.ts +12 -0
- package/src/lib/ai/types.ts +11 -0
- package/src/lib/chunking.ts +81 -0
- package/src/lib/curated-kb-seed.ts +164 -0
- package/src/lib/email.ts +77 -115
- package/src/lib/embeddings.ts +9 -0
- package/src/lib/ensure-curated-kb-catalog.ts +60 -0
- package/src/lib/env.ts +2 -7
- package/src/lib/inference.ts +1 -21
- package/src/lib/kb-meta.ts +81 -0
- package/src/lib/pdf.ts +23 -0
- package/src/lib/workspace-kb.ts +7 -0
- package/src/repositories/artifact.repository.ts +55 -0
- package/src/repositories/base.repository.ts +19 -0
- package/src/repositories/invitation.repository.ts +53 -0
- package/src/repositories/notification.repository.ts +53 -0
- package/src/repositories/user.repository.ts +44 -0
- package/src/repositories/workspace-member.repository.ts +38 -0
- package/src/repositories/workspace.repository.ts +89 -0
- package/src/routers/_app.ts +4 -0
- package/src/routers/admin.ts +124 -692
- package/src/routers/annotations.ts +25 -203
- package/src/routers/artifactVersions.ts +32 -0
- package/src/routers/auth.ts +82 -520
- package/src/routers/chat.ts +42 -245
- package/src/routers/copilot.ts +41 -666
- package/src/routers/flashcards.ts +108 -404
- package/src/routers/knowledgeBase.ts +216 -0
- package/src/routers/members.ts +60 -782
- package/src/routers/notifications.ts +15 -117
- package/src/routers/payment.ts +37 -446
- package/src/routers/podcast.ts +36 -898
- package/src/routers/studyguide.ts +5 -144
- package/src/routers/worksheets.ts +171 -735
- package/src/routers/workspace.ts +141 -1108
- package/src/scripts/purge-deleted-users.ts +2 -2
- package/src/server.ts +10 -3
- package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
- package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
- package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
- package/src/services/admin/admin.service.ts +612 -0
- package/src/services/ai/ai-session.service.ts +5 -0
- package/src/services/artifacts/annotation.service.ts +189 -0
- package/src/services/artifacts/artifact-version.service.ts +151 -0
- package/src/services/artifacts/chat.service.ts +197 -0
- package/src/services/artifacts/study-guide.service.ts +72 -0
- package/src/services/auth/auth.service.ts +473 -0
- package/src/services/base.service.ts +19 -0
- package/src/services/billing/payment.service.ts +433 -0
- package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
- package/src/services/billing/usage.service.ts +207 -0
- package/src/services/content/copilot.service.ts +587 -0
- package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +18 -12
- package/src/services/content/flashcard.service.ts +417 -0
- package/src/services/content/media-analysis.service.ts +561 -0
- package/src/services/content/podcast.service.ts +777 -0
- package/src/services/content/worksheet-content.service.test.ts +83 -0
- package/src/services/content/worksheet-content.service.ts +117 -0
- package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +3 -3
- package/src/services/content/worksheet.service.ts +751 -0
- package/src/services/knowledge/knowledge-base.service.ts +705 -0
- package/src/services/members/invitation.service.ts +427 -0
- package/src/services/members/member.service.ts +241 -0
- package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
- package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
- package/src/services/workspace/workspace-analytics.service.ts +107 -0
- package/src/services/workspace/workspace-kb.service.ts +273 -0
- package/src/services/workspace/workspace.service.ts +488 -0
- package/src/trpc.ts +7 -15
- package/src/lib/ai-session.ts +0 -704
- package/src/lib/usage_service.ts +0 -74
- package/src/lib/workspace-access.ts +0 -13
- /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
- /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
- /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
- /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
- /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
- /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
- /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
- /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
- /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { BaseService } from '../base.service.js';
|
|
5
|
+
import { supabaseClient } from '../../lib/storage.js';
|
|
6
|
+
import { sendVerificationEmail, sendAccountDeletionScheduledEmail, sendAccountRestoredEmail, sendPasswordResetEmail, } from '../../lib/email.js';
|
|
7
|
+
import { createStripeCustomer } from '../../lib/stripe.js';
|
|
8
|
+
import { notifyAdminsAccountDeletionScheduled, notifyAdminsOnSignup, } from '../notifications/notification.service.js';
|
|
9
|
+
export function hashPasswordResetToken(rawToken) {
|
|
10
|
+
return crypto.createHash('sha256').update(rawToken, 'utf8').digest('hex');
|
|
11
|
+
}
|
|
12
|
+
export function createCustomAuthToken(userId) {
|
|
13
|
+
const secret = process.env.AUTH_SECRET;
|
|
14
|
+
if (!secret) {
|
|
15
|
+
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'AUTH_SECRET is not set' });
|
|
16
|
+
}
|
|
17
|
+
const base64UserId = Buffer.from(userId, 'utf8').toString('base64url');
|
|
18
|
+
const hmac = crypto.createHmac('sha256', secret);
|
|
19
|
+
hmac.update(base64UserId);
|
|
20
|
+
const signature = hmac.digest('hex');
|
|
21
|
+
return `${base64UserId}.${signature}`;
|
|
22
|
+
}
|
|
23
|
+
function passwordResetDb(db) {
|
|
24
|
+
return db.passwordResetToken;
|
|
25
|
+
}
|
|
26
|
+
export class AuthService extends BaseService {
|
|
27
|
+
constructor(db) {
|
|
28
|
+
super(db);
|
|
29
|
+
}
|
|
30
|
+
async updateProfile(userId, input) {
|
|
31
|
+
await this.db.user.update({
|
|
32
|
+
where: { id: userId },
|
|
33
|
+
data: { name: input.name },
|
|
34
|
+
});
|
|
35
|
+
return { success: true, message: 'Profile updated successfully' };
|
|
36
|
+
}
|
|
37
|
+
async changePassword(userId, input) {
|
|
38
|
+
const user = await this.db.user.findUnique({
|
|
39
|
+
where: { id: userId },
|
|
40
|
+
select: { id: true, passwordHash: true },
|
|
41
|
+
});
|
|
42
|
+
if (!user) {
|
|
43
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
44
|
+
}
|
|
45
|
+
if (!user.passwordHash) {
|
|
46
|
+
throw new TRPCError({
|
|
47
|
+
code: 'BAD_REQUEST',
|
|
48
|
+
message: 'Password change is unavailable for this account.',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const validCurrentPassword = await bcrypt.compare(input.currentPassword, user.passwordHash);
|
|
52
|
+
if (!validCurrentPassword) {
|
|
53
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Current password is incorrect' });
|
|
54
|
+
}
|
|
55
|
+
const isSamePassword = await bcrypt.compare(input.newPassword, user.passwordHash);
|
|
56
|
+
if (isSamePassword) {
|
|
57
|
+
throw new TRPCError({
|
|
58
|
+
code: 'BAD_REQUEST',
|
|
59
|
+
message: 'New password must be different from current password',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const newHash = await bcrypt.hash(input.newPassword, 10);
|
|
63
|
+
await this.db.user.update({
|
|
64
|
+
where: { id: user.id },
|
|
65
|
+
data: { passwordHash: newHash },
|
|
66
|
+
});
|
|
67
|
+
return { success: true, message: 'Password changed successfully' };
|
|
68
|
+
}
|
|
69
|
+
async uploadProfilePicture(userId) {
|
|
70
|
+
this.logger.info(`Generating upload URL for user ${userId}`, 'AUTH');
|
|
71
|
+
const objectKey = `profile_picture_${userId}`;
|
|
72
|
+
const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
|
|
73
|
+
.from('media')
|
|
74
|
+
.createSignedUploadUrl(objectKey, { upsert: true });
|
|
75
|
+
if (signedUrlError) {
|
|
76
|
+
this.logger.error(`Failed to generate upload URL: ${signedUrlError.message}`, 'AUTH');
|
|
77
|
+
throw new TRPCError({
|
|
78
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
79
|
+
message: `Failed to generate upload URL: ${signedUrlError.message}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const fileAsset = await this.db.fileAsset.create({
|
|
83
|
+
data: {
|
|
84
|
+
userId,
|
|
85
|
+
name: 'Profile Picture',
|
|
86
|
+
mimeType: 'image/jpeg',
|
|
87
|
+
size: 0,
|
|
88
|
+
bucket: 'media',
|
|
89
|
+
objectKey,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
await this.db.user.update({
|
|
93
|
+
where: { id: userId },
|
|
94
|
+
data: { fileAssetId: fileAsset.id },
|
|
95
|
+
});
|
|
96
|
+
this.logger.info(`Profile picture asset created and linked for user ${userId}`, 'AUTH');
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
message: 'Profile picture uploaded successfully',
|
|
100
|
+
signedUrl: signedUrlData.signedUrl,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Signup creates a user + sends verification email + kicks off Stripe customer
|
|
105
|
+
* creation. The caller (router) does NOT need to set any cookies here.
|
|
106
|
+
*/
|
|
107
|
+
async signup(input) {
|
|
108
|
+
const existing = await this.db.user.findUnique({
|
|
109
|
+
where: { email: input.email },
|
|
110
|
+
});
|
|
111
|
+
if (existing) {
|
|
112
|
+
throw new TRPCError({ code: 'CONFLICT', message: 'Email already registered' });
|
|
113
|
+
}
|
|
114
|
+
const hash = await bcrypt.hash(input.password, 10);
|
|
115
|
+
const userRole = await this.db.role.findUnique({
|
|
116
|
+
where: { name: 'User' },
|
|
117
|
+
});
|
|
118
|
+
const user = await this.db.user.create({
|
|
119
|
+
data: {
|
|
120
|
+
name: input.name,
|
|
121
|
+
email: input.email,
|
|
122
|
+
passwordHash: hash,
|
|
123
|
+
roleId: userRole?.id,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
await notifyAdminsOnSignup(this.db, {
|
|
127
|
+
id: user.id,
|
|
128
|
+
name: user.name,
|
|
129
|
+
email: user.email,
|
|
130
|
+
});
|
|
131
|
+
const token = crypto.randomUUID();
|
|
132
|
+
await this.db.verificationToken.create({
|
|
133
|
+
data: {
|
|
134
|
+
identifier: input.email,
|
|
135
|
+
token,
|
|
136
|
+
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
sendVerificationEmail(input.email, token, input.name).catch(() => { });
|
|
140
|
+
createStripeCustomer(input.email, input.name).then(async (stripeCustomerId) => {
|
|
141
|
+
if (stripeCustomerId) {
|
|
142
|
+
await this.db.user
|
|
143
|
+
.update({
|
|
144
|
+
where: { id: user.id },
|
|
145
|
+
data: { stripe_customer_id: stripeCustomerId },
|
|
146
|
+
})
|
|
147
|
+
.catch((err) => this.logger.error(`Failed to update user with stripe_customer_id: ${err.message}`, 'AUTH'));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return { id: user.id, email: user.email, name: user.name };
|
|
151
|
+
}
|
|
152
|
+
async verifyEmail(token) {
|
|
153
|
+
const record = await this.db.verificationToken.findUnique({
|
|
154
|
+
where: { token },
|
|
155
|
+
});
|
|
156
|
+
if (!record) {
|
|
157
|
+
throw new TRPCError({
|
|
158
|
+
code: 'NOT_FOUND',
|
|
159
|
+
message: 'Invalid or expired verification link',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (record.expires < new Date()) {
|
|
163
|
+
await this.db.verificationToken.deleteMany({ where: { token } });
|
|
164
|
+
throw new TRPCError({
|
|
165
|
+
code: 'BAD_REQUEST',
|
|
166
|
+
message: 'Verification link has expired. Please request a new one.',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
await this.db.user.update({
|
|
170
|
+
where: { email: record.identifier },
|
|
171
|
+
data: { emailVerified: new Date() },
|
|
172
|
+
});
|
|
173
|
+
await this.db.verificationToken.deleteMany({ where: { token } });
|
|
174
|
+
return { success: true, message: 'Email verified successfully' };
|
|
175
|
+
}
|
|
176
|
+
async resendVerification(sessionUserId) {
|
|
177
|
+
if (!sessionUserId) {
|
|
178
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not logged in' });
|
|
179
|
+
}
|
|
180
|
+
const user = await this.db.user.findUnique({ where: { id: sessionUserId } });
|
|
181
|
+
if (!user || !user.email) {
|
|
182
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
183
|
+
}
|
|
184
|
+
if (user.emailVerified) {
|
|
185
|
+
return { success: true, message: 'Email is already verified' };
|
|
186
|
+
}
|
|
187
|
+
await this.db.verificationToken.deleteMany({
|
|
188
|
+
where: { identifier: user.email },
|
|
189
|
+
});
|
|
190
|
+
const token = crypto.randomUUID();
|
|
191
|
+
await this.db.verificationToken.create({
|
|
192
|
+
data: {
|
|
193
|
+
identifier: user.email,
|
|
194
|
+
token,
|
|
195
|
+
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
const sent = await sendVerificationEmail(user.email, token, user.name);
|
|
199
|
+
if (!sent) {
|
|
200
|
+
throw new TRPCError({
|
|
201
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
202
|
+
message: 'Failed to send email. Please try again.',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return { success: true, message: 'Verification email sent' };
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Verifies credentials and returns the user + an auth token. The router
|
|
209
|
+
* is responsible for setting the cookie (Express `res` is not available
|
|
210
|
+
* here — we keep services HTTP-free).
|
|
211
|
+
*/
|
|
212
|
+
async login(input) {
|
|
213
|
+
const user = await this.db.user.findUnique({ where: { email: input.email } });
|
|
214
|
+
if (!user) {
|
|
215
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Invalid credentials' });
|
|
216
|
+
}
|
|
217
|
+
if (user.deletedAt) {
|
|
218
|
+
throw new TRPCError({
|
|
219
|
+
code: 'UNAUTHORIZED',
|
|
220
|
+
message: 'Account scheduled for deletion. Please check your email for a restore link.',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const valid = await bcrypt.compare(input.password, user.passwordHash);
|
|
224
|
+
if (!valid) {
|
|
225
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Invalid credentials' });
|
|
226
|
+
}
|
|
227
|
+
const authToken = createCustomAuthToken(user.id);
|
|
228
|
+
return {
|
|
229
|
+
id: user.id,
|
|
230
|
+
email: user.email,
|
|
231
|
+
name: user.name,
|
|
232
|
+
token: authToken,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
async requestPasswordReset(input) {
|
|
236
|
+
const email = input.email.trim().toLowerCase();
|
|
237
|
+
const generic = {
|
|
238
|
+
success: true,
|
|
239
|
+
message: 'If an account exists for this email, we sent password reset instructions.',
|
|
240
|
+
};
|
|
241
|
+
const user = await this.db.user.findUnique({
|
|
242
|
+
where: { email },
|
|
243
|
+
select: {
|
|
244
|
+
id: true,
|
|
245
|
+
email: true,
|
|
246
|
+
name: true,
|
|
247
|
+
passwordHash: true,
|
|
248
|
+
deletedAt: true,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
if (!user?.passwordHash || user.deletedAt || !user.email) {
|
|
252
|
+
return generic;
|
|
253
|
+
}
|
|
254
|
+
await passwordResetDb(this.db).deleteMany({
|
|
255
|
+
where: { userId: user.id, usedAt: null },
|
|
256
|
+
});
|
|
257
|
+
const rawToken = crypto.randomBytes(32).toString('hex');
|
|
258
|
+
const tokenHash = hashPasswordResetToken(rawToken);
|
|
259
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
|
|
260
|
+
await passwordResetDb(this.db).create({
|
|
261
|
+
data: { userId: user.id, tokenHash, expiresAt },
|
|
262
|
+
});
|
|
263
|
+
sendPasswordResetEmail(user.email, rawToken, user.name).catch(() => { });
|
|
264
|
+
return generic;
|
|
265
|
+
}
|
|
266
|
+
async resetPassword(input) {
|
|
267
|
+
const tokenHash = hashPasswordResetToken(input.token);
|
|
268
|
+
const record = await passwordResetDb(this.db).findUnique({
|
|
269
|
+
where: { tokenHash },
|
|
270
|
+
});
|
|
271
|
+
if (!record || record.usedAt || record.expiresAt < new Date()) {
|
|
272
|
+
throw new TRPCError({
|
|
273
|
+
code: 'BAD_REQUEST',
|
|
274
|
+
message: 'Invalid or expired reset link. Please request a new one.',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const newHash = await bcrypt.hash(input.newPassword, 10);
|
|
278
|
+
await this.db.user.update({
|
|
279
|
+
where: { id: record.userId },
|
|
280
|
+
data: { passwordHash: newHash },
|
|
281
|
+
});
|
|
282
|
+
await passwordResetDb(this.db).update({
|
|
283
|
+
where: { id: record.id },
|
|
284
|
+
data: { usedAt: new Date() },
|
|
285
|
+
});
|
|
286
|
+
await passwordResetDb(this.db).deleteMany({
|
|
287
|
+
where: { userId: record.userId, id: { not: record.id } },
|
|
288
|
+
});
|
|
289
|
+
return { success: true, message: 'Password updated. You can sign in now.' };
|
|
290
|
+
}
|
|
291
|
+
async getSession(sessionUserId) {
|
|
292
|
+
if (!sessionUserId) {
|
|
293
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'No session found' });
|
|
294
|
+
}
|
|
295
|
+
const user = await this.db.user.findUnique({
|
|
296
|
+
where: { id: sessionUserId },
|
|
297
|
+
include: { profilePicture: true, role: true },
|
|
298
|
+
});
|
|
299
|
+
if (!user) {
|
|
300
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
301
|
+
}
|
|
302
|
+
const profilePictureUrl = user.profilePicture?.objectKey
|
|
303
|
+
? `/profile-picture/${user.profilePicture.objectKey}?t=${new Date(user.updatedAt).getTime()}`
|
|
304
|
+
: null;
|
|
305
|
+
this.logger.info(`Session fetched for user ${sessionUserId}, profilePicture: ${profilePictureUrl}`, 'AUTH');
|
|
306
|
+
return {
|
|
307
|
+
user: {
|
|
308
|
+
id: user.id,
|
|
309
|
+
email: user.email,
|
|
310
|
+
name: user.name,
|
|
311
|
+
emailVerified: !!user.emailVerified,
|
|
312
|
+
profilePicture: profilePictureUrl,
|
|
313
|
+
role: user.role,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
async requestAccountDeletion(userId) {
|
|
318
|
+
const user = await this.db.user.findUnique({ where: { id: userId } });
|
|
319
|
+
if (!user) {
|
|
320
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
321
|
+
}
|
|
322
|
+
await this.db.user.update({
|
|
323
|
+
where: { id: user.id },
|
|
324
|
+
data: { deletedAt: new Date() },
|
|
325
|
+
});
|
|
326
|
+
await notifyAdminsAccountDeletionScheduled(this.db, {
|
|
327
|
+
id: user.id,
|
|
328
|
+
name: user.name,
|
|
329
|
+
email: user.email,
|
|
330
|
+
}).catch(() => { });
|
|
331
|
+
await this.db.verificationToken.deleteMany({
|
|
332
|
+
where: { identifier: `restore-${user.email}` },
|
|
333
|
+
});
|
|
334
|
+
const token = crypto.randomUUID();
|
|
335
|
+
await this.db.verificationToken.create({
|
|
336
|
+
data: {
|
|
337
|
+
identifier: `restore-${user.email}`,
|
|
338
|
+
token,
|
|
339
|
+
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
if (user.email) {
|
|
343
|
+
sendAccountDeletionScheduledEmail(user.email, token).catch(() => { });
|
|
344
|
+
}
|
|
345
|
+
return { success: true, message: 'Account scheduled for deletion' };
|
|
346
|
+
}
|
|
347
|
+
async restoreAccount(token) {
|
|
348
|
+
const record = await this.db.verificationToken.findUnique({ where: { token } });
|
|
349
|
+
if (!record || !record.identifier.startsWith('restore-')) {
|
|
350
|
+
throw new TRPCError({
|
|
351
|
+
code: 'NOT_FOUND',
|
|
352
|
+
message: 'Invalid or expired restore link',
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (record.expires < new Date()) {
|
|
356
|
+
await this.db.verificationToken.deleteMany({ where: { token } });
|
|
357
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Restore link has expired.' });
|
|
358
|
+
}
|
|
359
|
+
const email = record.identifier.replace('restore-', '');
|
|
360
|
+
await this.db.user.update({
|
|
361
|
+
where: { email },
|
|
362
|
+
data: { deletedAt: null },
|
|
363
|
+
});
|
|
364
|
+
await this.db.verificationToken.deleteMany({ where: { token } });
|
|
365
|
+
sendAccountRestoredEmail(email).catch(() => { });
|
|
366
|
+
return { success: true, message: 'Account restored successfully' };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { PrismaClient } from '@prisma/client';
|
|
2
|
+
import { BaseService } from './base.service.js';
|
|
3
|
+
export declare function hashPasswordResetToken(rawToken: string): string;
|
|
4
|
+
export declare function createCustomAuthToken(userId: string): string;
|
|
5
|
+
export declare class AuthService extends BaseService {
|
|
6
|
+
constructor(db: PrismaClient);
|
|
7
|
+
updateProfile(userId: string, input: {
|
|
8
|
+
name: string;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
success: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
}>;
|
|
13
|
+
changePassword(userId: string, input: {
|
|
14
|
+
currentPassword: string;
|
|
15
|
+
newPassword: string;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
success: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
}>;
|
|
20
|
+
uploadProfilePicture(userId: string): Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
message: string;
|
|
23
|
+
signedUrl: string;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Signup creates a user + sends verification email + kicks off Stripe customer
|
|
27
|
+
* creation. The caller (router) does NOT need to set any cookies here.
|
|
28
|
+
*/
|
|
29
|
+
signup(input: {
|
|
30
|
+
name: string;
|
|
31
|
+
email: string;
|
|
32
|
+
password: string;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
id: string;
|
|
35
|
+
email: string | null;
|
|
36
|
+
name: string | null;
|
|
37
|
+
}>;
|
|
38
|
+
verifyEmail(token: string): Promise<{
|
|
39
|
+
success: boolean;
|
|
40
|
+
message: string;
|
|
41
|
+
}>;
|
|
42
|
+
resendVerification(sessionUserId: string | undefined): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
message: string;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Verifies credentials and returns the user + an auth token. The router
|
|
48
|
+
* is responsible for setting the cookie (Express `res` is not available
|
|
49
|
+
* here — we keep services HTTP-free).
|
|
50
|
+
*/
|
|
51
|
+
login(input: {
|
|
52
|
+
email: string;
|
|
53
|
+
password: string;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
id: string;
|
|
56
|
+
email: string | null;
|
|
57
|
+
name: string | null;
|
|
58
|
+
token: string;
|
|
59
|
+
}>;
|
|
60
|
+
requestPasswordReset(input: {
|
|
61
|
+
email: string;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
success: true;
|
|
64
|
+
message: string;
|
|
65
|
+
}>;
|
|
66
|
+
resetPassword(input: {
|
|
67
|
+
token: string;
|
|
68
|
+
newPassword: string;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
success: boolean;
|
|
71
|
+
message: string;
|
|
72
|
+
}>;
|
|
73
|
+
getSession(sessionUserId: string | undefined): Promise<{
|
|
74
|
+
user: {
|
|
75
|
+
id: string;
|
|
76
|
+
email: string | null;
|
|
77
|
+
name: string | null;
|
|
78
|
+
emailVerified: boolean;
|
|
79
|
+
profilePicture: string | null;
|
|
80
|
+
role: {
|
|
81
|
+
name: string;
|
|
82
|
+
id: string;
|
|
83
|
+
} | null;
|
|
84
|
+
};
|
|
85
|
+
}>;
|
|
86
|
+
requestAccountDeletion(userId: string): Promise<{
|
|
87
|
+
success: boolean;
|
|
88
|
+
message: string;
|
|
89
|
+
}>;
|
|
90
|
+
restoreAccount(token: string): Promise<{
|
|
91
|
+
success: boolean;
|
|
92
|
+
message: string;
|
|
93
|
+
}>;
|
|
94
|
+
}
|