@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.
- 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/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 +47 -0
- package/dist/lib/ai/index.js +28 -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 +6 -0
- package/dist/lib/ai/llm-client.js +19 -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 +528 -33
- 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 +36 -421
- 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 +5 -5
- 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 +3 -2
- 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 +0 -4
- package/dist/routers/workspace.js +28 -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 +55 -0
- package/dist/services/billing/payment.service.js +368 -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 +27 -0
- package/dist/services/billing/usage.service.js +77 -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 +453 -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 +140 -0
- package/dist/services/content/flashcard.service.js +326 -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 +307 -0
- package/dist/services/workspace/workspace.service.js +394 -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.js +2 -2
- 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/lib/ai/config.ts +34 -0
- package/src/lib/ai/embedding-client.ts +47 -0
- package/src/lib/ai/index.ts +62 -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 +31 -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 +81 -519
- 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 +138 -1109
- 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 +436 -0
- package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
- package/src/{lib/usage_service.ts → services/billing/usage.service.ts} +32 -12
- package/src/services/content/copilot.service.ts +596 -0
- package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +6 -3
- package/src/services/content/flashcard.service.ts +394 -0
- package/src/services/content/media-analysis.service.ts +556 -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} +1 -1
- 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 +481 -0
- package/src/trpc.ts +2 -2
- package/src/lib/ai-session.ts +0 -704
- 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
package/dist/routers/members.js
CHANGED
|
@@ -1,735 +1,47 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { TRPCError } from '@trpc/server';
|
|
3
2
|
import { router, publicProcedure, authedProcedure } from '../trpc.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import PusherService from '../lib/pusher.js';
|
|
7
|
-
import { notifyInviteAccepted, notifyInviteRecipient, notifyWorkspaceMembershipRemoved, notifyWorkspaceRoleChanged, } from '../lib/notification-service.js';
|
|
8
|
-
/**
|
|
9
|
-
* Members router for workspace member management
|
|
10
|
-
*
|
|
11
|
-
* Features:
|
|
12
|
-
* - Get workspace members
|
|
13
|
-
* - Invite new members via email
|
|
14
|
-
* - Accept invitations via UUID
|
|
15
|
-
* - Change member roles
|
|
16
|
-
* - Remove members
|
|
17
|
-
* - Get current user's role
|
|
18
|
-
*/
|
|
3
|
+
import { MemberService } from '../services/members/member.service.js';
|
|
4
|
+
import { InvitationService } from '../services/members/invitation.service.js';
|
|
19
5
|
export const members = router({
|
|
20
|
-
/**
|
|
21
|
-
* Get all members of a workspace
|
|
22
|
-
*/
|
|
23
6
|
getMembers: authedProcedure
|
|
24
|
-
.input(z.object({
|
|
25
|
-
|
|
26
|
-
}))
|
|
27
|
-
.query(async ({ ctx, input }) => {
|
|
28
|
-
// Check if user has access to this workspace
|
|
29
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
30
|
-
where: {
|
|
31
|
-
id: input.workspaceId,
|
|
32
|
-
OR: [
|
|
33
|
-
{ ownerId: ctx.session.user.id },
|
|
34
|
-
{ members: { some: { userId: ctx.session.user.id } } }
|
|
35
|
-
]
|
|
36
|
-
},
|
|
37
|
-
include: {
|
|
38
|
-
owner: {
|
|
39
|
-
select: {
|
|
40
|
-
id: true,
|
|
41
|
-
name: true,
|
|
42
|
-
email: true,
|
|
43
|
-
profilePicture: {
|
|
44
|
-
select: {
|
|
45
|
-
id: true,
|
|
46
|
-
name: true,
|
|
47
|
-
url: true,
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
members: {
|
|
53
|
-
include: {
|
|
54
|
-
user: {
|
|
55
|
-
select: {
|
|
56
|
-
id: true,
|
|
57
|
-
name: true,
|
|
58
|
-
email: true,
|
|
59
|
-
profilePicture: {
|
|
60
|
-
select: {
|
|
61
|
-
id: true,
|
|
62
|
-
name: true,
|
|
63
|
-
url: true,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
if (!workspace) {
|
|
73
|
-
throw new TRPCError({
|
|
74
|
-
code: 'NOT_FOUND',
|
|
75
|
-
message: 'Workspace not found or access denied'
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
const workspaceMembers = [
|
|
79
|
-
{
|
|
80
|
-
id: workspace.owner.id,
|
|
81
|
-
name: workspace.owner.name || 'Unknown',
|
|
82
|
-
email: workspace.owner.email || '',
|
|
83
|
-
role: 'owner',
|
|
84
|
-
joinedAt: workspace.createdAt,
|
|
85
|
-
},
|
|
86
|
-
...workspace.members.map(membership => ({
|
|
87
|
-
id: membership.user.id,
|
|
88
|
-
name: membership.user.name || 'Unknown',
|
|
89
|
-
email: membership.user.email || '',
|
|
90
|
-
role: membership.role,
|
|
91
|
-
joinedAt: membership.joinedAt,
|
|
92
|
-
}))
|
|
93
|
-
];
|
|
94
|
-
logger.info(`👥 Fetched ${workspaceMembers.length} members for workspace ${input.workspaceId}`, 'WORKSPACE', {
|
|
95
|
-
memberEmails: workspaceMembers.map(m => m.email)
|
|
96
|
-
});
|
|
97
|
-
return workspaceMembers;
|
|
98
|
-
}),
|
|
99
|
-
/**
|
|
100
|
-
* Get current user's role in a workspace
|
|
101
|
-
*/
|
|
7
|
+
.input(z.object({ workspaceId: z.string() }))
|
|
8
|
+
.query(({ ctx, input }) => new MemberService(ctx.db).getMembers(ctx.session.user.id, input.workspaceId)),
|
|
102
9
|
getCurrentUserRole: authedProcedure
|
|
103
|
-
.input(z.object({
|
|
104
|
-
|
|
105
|
-
}))
|
|
106
|
-
.query(async ({ ctx, input }) => {
|
|
107
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
108
|
-
where: { id: input.workspaceId },
|
|
109
|
-
select: {
|
|
110
|
-
ownerId: true,
|
|
111
|
-
members: {
|
|
112
|
-
where: { userId: ctx.session.user.id },
|
|
113
|
-
select: { role: true }
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
if (!workspace) {
|
|
118
|
-
throw new TRPCError({
|
|
119
|
-
code: 'NOT_FOUND',
|
|
120
|
-
message: 'Workspace not found'
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
if (workspace.ownerId === ctx.session.user.id) {
|
|
124
|
-
return 'owner';
|
|
125
|
-
}
|
|
126
|
-
if (workspace.members.length > 0) {
|
|
127
|
-
return workspace.members[0].role;
|
|
128
|
-
}
|
|
129
|
-
throw new TRPCError({
|
|
130
|
-
code: 'FORBIDDEN',
|
|
131
|
-
message: 'Access denied to this workspace'
|
|
132
|
-
});
|
|
133
|
-
}),
|
|
134
|
-
/**
|
|
135
|
-
* Invite a new member to the workspace
|
|
136
|
-
*/
|
|
10
|
+
.input(z.object({ workspaceId: z.string() }))
|
|
11
|
+
.query(({ ctx, input }) => new MemberService(ctx.db).getCurrentUserRole(ctx.session.user.id, input.workspaceId)),
|
|
137
12
|
inviteMember: authedProcedure
|
|
138
13
|
.input(z.object({
|
|
139
14
|
workspaceId: z.string(),
|
|
140
15
|
email: z.string().email(),
|
|
141
16
|
role: z.enum(['admin', 'member']).default('member'),
|
|
142
17
|
}))
|
|
143
|
-
.mutation(
|
|
144
|
-
// Check if user is owner or admin of the workspace
|
|
145
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
146
|
-
where: {
|
|
147
|
-
id: input.workspaceId,
|
|
148
|
-
ownerId: ctx.session.user.id // Only owners can invite for now
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
if (!workspace) {
|
|
152
|
-
throw new TRPCError({
|
|
153
|
-
code: 'NOT_FOUND',
|
|
154
|
-
message: 'Workspace not found or insufficient permissions'
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
// Check if user is already a member
|
|
158
|
-
const existingMember = await ctx.db.user.findFirst({
|
|
159
|
-
where: {
|
|
160
|
-
email: input.email,
|
|
161
|
-
OR: [
|
|
162
|
-
{ id: workspace.ownerId },
|
|
163
|
-
{ workspaceMemberships: { some: { workspaceId: input.workspaceId } } }
|
|
164
|
-
]
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
if (existingMember) {
|
|
168
|
-
throw new TRPCError({
|
|
169
|
-
code: 'BAD_REQUEST',
|
|
170
|
-
message: 'User is already a member of this workspace'
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
// Check if there's already a pending invitation
|
|
174
|
-
const existingInvitation = await ctx.db.workspaceInvitation.findFirst({
|
|
175
|
-
where: {
|
|
176
|
-
workspaceId: input.workspaceId,
|
|
177
|
-
email: input.email,
|
|
178
|
-
acceptedAt: null,
|
|
179
|
-
expiresAt: { gt: new Date() }
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
if (existingInvitation) {
|
|
183
|
-
throw new TRPCError({
|
|
184
|
-
code: 'BAD_REQUEST',
|
|
185
|
-
message: 'Invitation already sent to this email'
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
// Create invitation
|
|
189
|
-
const invitation = await ctx.db.workspaceInvitation.create({
|
|
190
|
-
data: {
|
|
191
|
-
workspaceId: input.workspaceId,
|
|
192
|
-
email: input.email,
|
|
193
|
-
role: input.role,
|
|
194
|
-
invitedById: ctx.session.user.id,
|
|
195
|
-
},
|
|
196
|
-
include: {
|
|
197
|
-
workspace: {
|
|
198
|
-
select: {
|
|
199
|
-
title: true,
|
|
200
|
-
owner: {
|
|
201
|
-
select: {
|
|
202
|
-
name: true,
|
|
203
|
-
email: true,
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
const invitedExistingUser = await ctx.db.user.findUnique({
|
|
211
|
-
where: { email: input.email },
|
|
212
|
-
select: { id: true, name: true },
|
|
213
|
-
});
|
|
214
|
-
if (invitedExistingUser) {
|
|
215
|
-
await notifyInviteRecipient(ctx.db, {
|
|
216
|
-
invitedUserId: invitedExistingUser.id,
|
|
217
|
-
inviterUserId: ctx.session.user.id,
|
|
218
|
-
workspaceId: input.workspaceId,
|
|
219
|
-
workspaceTitle: invitation.workspace.title,
|
|
220
|
-
invitationId: invitation.id,
|
|
221
|
-
invitationToken: invitation.token,
|
|
222
|
-
inviterName: invitation.workspace.owner.name || invitation.workspace.owner.email,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
logger.info(`🎫 Invitation created for ${input.email} to workspace ${input.workspaceId} with role ${input.role}`, 'WORKSPACE', {
|
|
226
|
-
invitationId: invitation.id,
|
|
227
|
-
workspaceId: input.workspaceId,
|
|
228
|
-
email: input.email,
|
|
229
|
-
role: input.role,
|
|
230
|
-
invitedBy: ctx.session.user.id
|
|
231
|
-
});
|
|
232
|
-
// Send email notification
|
|
233
|
-
await sendInvitationEmail({
|
|
234
|
-
email: invitation.email,
|
|
235
|
-
token: invitation.token,
|
|
236
|
-
role: invitation.role,
|
|
237
|
-
workspaceTitle: invitation.workspace.title,
|
|
238
|
-
invitedByName: invitation.workspace.owner.name || invitation.workspace.owner.email || 'Someone',
|
|
239
|
-
});
|
|
240
|
-
return {
|
|
241
|
-
invitationId: invitation.id,
|
|
242
|
-
token: invitation.token,
|
|
243
|
-
email: invitation.email,
|
|
244
|
-
role: invitation.role,
|
|
245
|
-
expiresAt: invitation.expiresAt,
|
|
246
|
-
workspaceTitle: invitation.workspace.title,
|
|
247
|
-
invitedByName: invitation.workspace.owner.name || invitation.workspace.owner.email,
|
|
248
|
-
};
|
|
249
|
-
}),
|
|
18
|
+
.mutation(({ ctx, input }) => new InvitationService(ctx.db).inviteMember(ctx.session.user.id, input)),
|
|
250
19
|
getInvitations: authedProcedure
|
|
251
|
-
.input(z.object({
|
|
252
|
-
|
|
253
|
-
}))
|
|
254
|
-
.query(async ({ ctx, input }) => {
|
|
255
|
-
const invitations = await ctx.db.workspaceInvitation.findMany({
|
|
256
|
-
where: { workspaceId: input.workspaceId },
|
|
257
|
-
});
|
|
258
|
-
return invitations;
|
|
259
|
-
}),
|
|
260
|
-
/**
|
|
261
|
-
* Accept an invitation (public endpoint)
|
|
262
|
-
*/
|
|
20
|
+
.input(z.object({ workspaceId: z.string() }))
|
|
21
|
+
.query(({ ctx, input }) => new InvitationService(ctx.db).getInvitations(input.workspaceId)),
|
|
263
22
|
acceptInvite: publicProcedure
|
|
264
|
-
.input(z.object({
|
|
265
|
-
|
|
266
|
-
}))
|
|
267
|
-
.mutation(async ({ ctx, input }) => {
|
|
268
|
-
// Find the invitation
|
|
269
|
-
const invitation = await ctx.db.workspaceInvitation.findFirst({
|
|
270
|
-
where: {
|
|
271
|
-
token: input.token,
|
|
272
|
-
acceptedAt: null,
|
|
273
|
-
expiresAt: { gt: new Date() }
|
|
274
|
-
},
|
|
275
|
-
include: {
|
|
276
|
-
workspace: {
|
|
277
|
-
select: {
|
|
278
|
-
id: true,
|
|
279
|
-
title: true,
|
|
280
|
-
ownerId: true,
|
|
281
|
-
owner: {
|
|
282
|
-
select: {
|
|
283
|
-
name: true,
|
|
284
|
-
email: true,
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
if (!invitation) {
|
|
292
|
-
throw new TRPCError({
|
|
293
|
-
code: 'NOT_FOUND',
|
|
294
|
-
message: 'Invalid or expired invitation'
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
// Check if user is authenticated
|
|
298
|
-
if (!ctx.session?.user) {
|
|
299
|
-
throw new TRPCError({
|
|
300
|
-
code: 'UNAUTHORIZED',
|
|
301
|
-
message: 'Please log in to accept this invitation'
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
const user = await ctx.db.user.findFirst({ where: { id: ctx.session.user.id } });
|
|
305
|
-
if (!user || !user.email)
|
|
306
|
-
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
307
|
-
logger.info(`🔍 Verification check for ${user.email} accepting invite for ${invitation.email}`, 'WORKSPACE');
|
|
308
|
-
// Check if the email matches the user's email (case-insensitive)
|
|
309
|
-
if (user.email.toLowerCase() !== invitation.email.toLowerCase()) {
|
|
310
|
-
logger.warn(`❌ Invitation email mismatch: user ${user.email} vs invite ${invitation.email}`, 'WORKSPACE');
|
|
311
|
-
throw new TRPCError({
|
|
312
|
-
code: 'BAD_REQUEST',
|
|
313
|
-
message: 'This invitation was sent to a different email address'
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
// Check if user is already a member
|
|
317
|
-
const isAlreadyMember = await ctx.db.workspace.findFirst({
|
|
318
|
-
where: {
|
|
319
|
-
id: invitation.workspaceId,
|
|
320
|
-
OR: [
|
|
321
|
-
{ ownerId: ctx.session.user.id },
|
|
322
|
-
{ members: { some: { userId: ctx.session.user.id } } }
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
if (isAlreadyMember) {
|
|
327
|
-
logger.info(`ℹ️ User ${ctx.session.user.id} is already a member of workspace ${invitation.workspaceId}. Marking invite as accepted.`, 'WORKSPACE');
|
|
328
|
-
// Mark invitation as accepted even if already a member
|
|
329
|
-
await ctx.db.workspaceInvitation.update({
|
|
330
|
-
where: { id: invitation.id },
|
|
331
|
-
data: { acceptedAt: new Date() }
|
|
332
|
-
});
|
|
333
|
-
return {
|
|
334
|
-
workspaceId: invitation.workspaceId,
|
|
335
|
-
workspaceTitle: invitation.workspace.title,
|
|
336
|
-
role: invitation.role,
|
|
337
|
-
ownerName: invitation.workspace.owner.name || invitation.workspace.owner.email,
|
|
338
|
-
message: 'You are already a member of this workspace'
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
// Add user to workspace with proper role.
|
|
342
|
-
// This can race if accept is triggered twice (e.g. redirects/retries),
|
|
343
|
-
// so treat duplicate membership as a successful, idempotent accept.
|
|
344
|
-
let memberAdded = false;
|
|
345
|
-
try {
|
|
346
|
-
await ctx.db.workspaceMember.create({
|
|
347
|
-
data: {
|
|
348
|
-
workspaceId: invitation.workspaceId,
|
|
349
|
-
userId: ctx.session.user.id,
|
|
350
|
-
role: invitation.role,
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
memberAdded = true;
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
if (error?.code !== 'P2002') {
|
|
357
|
-
throw error;
|
|
358
|
-
}
|
|
359
|
-
logger.info(`ℹ️ Duplicate invite accept handled for user ${ctx.session.user.id} in workspace ${invitation.workspaceId}`, 'WORKSPACE');
|
|
360
|
-
}
|
|
361
|
-
// Mark invitation as accepted
|
|
362
|
-
await ctx.db.workspaceInvitation.update({
|
|
363
|
-
where: { id: invitation.id },
|
|
364
|
-
data: { acceptedAt: new Date() }
|
|
365
|
-
});
|
|
366
|
-
if (memberAdded) {
|
|
367
|
-
await notifyInviteAccepted(ctx.db, {
|
|
368
|
-
recipientUserIds: [invitation.invitedById, invitation.workspace.ownerId],
|
|
369
|
-
actorUserId: ctx.session.user.id,
|
|
370
|
-
workspaceId: invitation.workspaceId,
|
|
371
|
-
workspaceTitle: invitation.workspace.title,
|
|
372
|
-
memberName: user.name || user.email,
|
|
373
|
-
invitationId: invitation.id,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
logger.info(`✅ Invitation accepted by ${ctx.session.user.id} (${user.email}) for workspace ${invitation.workspaceId}`, 'WORKSPACE', {
|
|
377
|
-
invitationId: invitation.id,
|
|
378
|
-
workspaceId: invitation.workspaceId,
|
|
379
|
-
userId: ctx.session.user.id,
|
|
380
|
-
email: invitation.email
|
|
381
|
-
});
|
|
382
|
-
// Try to emit a Pusher event if possible
|
|
383
|
-
try {
|
|
384
|
-
if (memberAdded) {
|
|
385
|
-
await PusherService.emitMemberJoined(invitation.workspaceId, {
|
|
386
|
-
id: ctx.session.user.id,
|
|
387
|
-
name: user.name || 'Member',
|
|
388
|
-
email: user.email,
|
|
389
|
-
role: invitation.role,
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
catch (e) {
|
|
394
|
-
logger.error('Failed to emit member joined event', e);
|
|
395
|
-
}
|
|
396
|
-
return {
|
|
397
|
-
workspaceId: invitation.workspaceId,
|
|
398
|
-
workspaceTitle: invitation.workspace.title,
|
|
399
|
-
role: invitation.role,
|
|
400
|
-
ownerName: invitation.workspace.owner.name || invitation.workspace.owner.email,
|
|
401
|
-
...(memberAdded ? {} : { message: 'You are already a member of this workspace' }),
|
|
402
|
-
};
|
|
403
|
-
}),
|
|
404
|
-
/**
|
|
405
|
-
* Change a member's role (owner only)
|
|
406
|
-
*/
|
|
23
|
+
.input(z.object({ token: z.string() }))
|
|
24
|
+
.mutation(({ ctx, input }) => new InvitationService(ctx.db).acceptInvite(ctx.session?.user?.id, input.token)),
|
|
407
25
|
changeMemberRole: authedProcedure
|
|
408
26
|
.input(z.object({
|
|
409
27
|
workspaceId: z.string(),
|
|
410
28
|
memberId: z.string(),
|
|
411
29
|
role: z.enum(['admin', 'member']),
|
|
412
30
|
}))
|
|
413
|
-
.mutation(
|
|
414
|
-
// Check if user is owner of the workspace
|
|
415
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
416
|
-
where: {
|
|
417
|
-
id: input.workspaceId,
|
|
418
|
-
ownerId: ctx.session.user.id
|
|
419
|
-
},
|
|
420
|
-
select: { id: true, title: true, ownerId: true },
|
|
421
|
-
});
|
|
422
|
-
if (!workspace) {
|
|
423
|
-
throw new TRPCError({
|
|
424
|
-
code: 'NOT_FOUND',
|
|
425
|
-
message: 'Workspace not found or insufficient permissions'
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
// Check if member exists and is not the owner
|
|
429
|
-
if (input.memberId === workspace.ownerId) {
|
|
430
|
-
throw new TRPCError({
|
|
431
|
-
code: 'BAD_REQUEST',
|
|
432
|
-
message: 'Cannot change owner role'
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
const member = await ctx.db.workspaceMember.findFirst({
|
|
436
|
-
where: {
|
|
437
|
-
workspaceId: input.workspaceId,
|
|
438
|
-
userId: input.memberId
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
if (!member) {
|
|
442
|
-
throw new TRPCError({
|
|
443
|
-
code: 'NOT_FOUND',
|
|
444
|
-
message: 'Member not found in this workspace'
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
// Update the member's role
|
|
448
|
-
const updatedMember = await ctx.db.workspaceMember.update({
|
|
449
|
-
where: { id: member.id },
|
|
450
|
-
data: { role: input.role },
|
|
451
|
-
include: {
|
|
452
|
-
user: {
|
|
453
|
-
select: {
|
|
454
|
-
id: true,
|
|
455
|
-
name: true,
|
|
456
|
-
email: true,
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
logger.info(`🔄 Member role changed for ${input.memberId} from ${member.role} to ${input.role} in workspace ${input.workspaceId}`, 'WORKSPACE', {
|
|
462
|
-
workspaceId: input.workspaceId,
|
|
463
|
-
memberId: input.memberId,
|
|
464
|
-
oldRole: member.role,
|
|
465
|
-
newRole: input.role,
|
|
466
|
-
changedBy: ctx.session.user.id
|
|
467
|
-
});
|
|
468
|
-
const actor = await ctx.db.user.findUnique({
|
|
469
|
-
where: { id: ctx.session.user.id },
|
|
470
|
-
select: { name: true, email: true },
|
|
471
|
-
});
|
|
472
|
-
await notifyWorkspaceRoleChanged(ctx.db, {
|
|
473
|
-
memberUserId: input.memberId,
|
|
474
|
-
workspaceId: input.workspaceId,
|
|
475
|
-
workspaceTitle: workspace.title,
|
|
476
|
-
newRole: input.role,
|
|
477
|
-
oldRole: member.role,
|
|
478
|
-
actorUserId: ctx.session.user.id,
|
|
479
|
-
actorName: actor?.name || actor?.email || 'A workspace admin',
|
|
480
|
-
}).catch(() => { });
|
|
481
|
-
return {
|
|
482
|
-
memberId: input.memberId,
|
|
483
|
-
role: input.role,
|
|
484
|
-
memberName: updatedMember.user.name || updatedMember.user.email,
|
|
485
|
-
message: 'Role changed successfully'
|
|
486
|
-
};
|
|
487
|
-
}),
|
|
488
|
-
/**
|
|
489
|
-
* Remove a member from the workspace (owner only)
|
|
490
|
-
*/
|
|
31
|
+
.mutation(({ ctx, input }) => new MemberService(ctx.db).changeMemberRole(ctx.session.user.id, input)),
|
|
491
32
|
removeMember: authedProcedure
|
|
492
|
-
.input(z.object({
|
|
493
|
-
|
|
494
|
-
memberId: z.string(),
|
|
495
|
-
}))
|
|
496
|
-
.mutation(async ({ ctx, input }) => {
|
|
497
|
-
// Check if user is owner of the workspace
|
|
498
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
499
|
-
where: {
|
|
500
|
-
id: input.workspaceId,
|
|
501
|
-
ownerId: ctx.session.user.id
|
|
502
|
-
},
|
|
503
|
-
select: { id: true, title: true, ownerId: true },
|
|
504
|
-
});
|
|
505
|
-
if (!workspace) {
|
|
506
|
-
throw new TRPCError({
|
|
507
|
-
code: 'NOT_FOUND',
|
|
508
|
-
message: 'Workspace not found or insufficient permissions'
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
// Check if trying to remove the owner
|
|
512
|
-
if (input.memberId === workspace.ownerId) {
|
|
513
|
-
throw new TRPCError({
|
|
514
|
-
code: 'BAD_REQUEST',
|
|
515
|
-
message: 'Cannot remove workspace owner'
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
// Check if member exists
|
|
519
|
-
const member = await ctx.db.workspaceMember.findFirst({
|
|
520
|
-
where: {
|
|
521
|
-
workspaceId: input.workspaceId,
|
|
522
|
-
userId: input.memberId
|
|
523
|
-
},
|
|
524
|
-
include: {
|
|
525
|
-
user: {
|
|
526
|
-
select: {
|
|
527
|
-
name: true,
|
|
528
|
-
email: true,
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
if (!member) {
|
|
534
|
-
throw new TRPCError({
|
|
535
|
-
code: 'NOT_FOUND',
|
|
536
|
-
message: 'Member not found in this workspace'
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
const actor = await ctx.db.user.findUnique({
|
|
540
|
-
where: { id: ctx.session.user.id },
|
|
541
|
-
select: { name: true, email: true },
|
|
542
|
-
});
|
|
543
|
-
await notifyWorkspaceMembershipRemoved(ctx.db, {
|
|
544
|
-
memberUserId: input.memberId,
|
|
545
|
-
workspaceId: input.workspaceId,
|
|
546
|
-
workspaceTitle: workspace.title,
|
|
547
|
-
actorUserId: ctx.session.user.id,
|
|
548
|
-
actorName: actor?.name || actor?.email || 'A workspace admin',
|
|
549
|
-
}).catch(() => { });
|
|
550
|
-
// Remove member from workspace
|
|
551
|
-
await ctx.db.workspaceMember.delete({
|
|
552
|
-
where: { id: member.id }
|
|
553
|
-
});
|
|
554
|
-
logger.info(`🗑️ Member ${input.memberId} removed from workspace ${input.workspaceId}`, 'WORKSPACE', {
|
|
555
|
-
workspaceId: input.workspaceId,
|
|
556
|
-
memberId: input.memberId,
|
|
557
|
-
removedBy: ctx.session.user.id
|
|
558
|
-
});
|
|
559
|
-
return {
|
|
560
|
-
memberId: input.memberId,
|
|
561
|
-
message: 'Member removed successfully'
|
|
562
|
-
};
|
|
563
|
-
}),
|
|
564
|
-
/**
|
|
565
|
-
* Get pending invitations for a workspace (owner only)
|
|
566
|
-
*/
|
|
33
|
+
.input(z.object({ workspaceId: z.string(), memberId: z.string() }))
|
|
34
|
+
.mutation(({ ctx, input }) => new MemberService(ctx.db).removeMember(ctx.session.user.id, input)),
|
|
567
35
|
getPendingInvitations: authedProcedure
|
|
568
|
-
.input(z.object({
|
|
569
|
-
|
|
570
|
-
}))
|
|
571
|
-
.query(async ({ ctx, input }) => {
|
|
572
|
-
// Check if user is owner of the workspace
|
|
573
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
574
|
-
where: {
|
|
575
|
-
id: input.workspaceId,
|
|
576
|
-
ownerId: ctx.session.user.id
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
if (!workspace) {
|
|
580
|
-
throw new TRPCError({
|
|
581
|
-
code: 'NOT_FOUND',
|
|
582
|
-
message: 'Workspace not found or insufficient permissions'
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
const invitations = await ctx.db.workspaceInvitation.findMany({
|
|
586
|
-
where: {
|
|
587
|
-
workspaceId: input.workspaceId,
|
|
588
|
-
acceptedAt: null,
|
|
589
|
-
expiresAt: { gt: new Date() }
|
|
590
|
-
},
|
|
591
|
-
include: {
|
|
592
|
-
invitedBy: {
|
|
593
|
-
select: {
|
|
594
|
-
name: true,
|
|
595
|
-
email: true,
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
},
|
|
599
|
-
orderBy: { createdAt: 'desc' }
|
|
600
|
-
});
|
|
601
|
-
return invitations.map(invitation => ({
|
|
602
|
-
id: invitation.id,
|
|
603
|
-
email: invitation.email,
|
|
604
|
-
role: invitation.role,
|
|
605
|
-
token: invitation.token,
|
|
606
|
-
expiresAt: invitation.expiresAt,
|
|
607
|
-
createdAt: invitation.createdAt,
|
|
608
|
-
invitedByName: invitation.invitedBy.name || invitation.invitedBy.email,
|
|
609
|
-
}));
|
|
610
|
-
}),
|
|
611
|
-
/**
|
|
612
|
-
* Cancel a pending invitation (owner only)
|
|
613
|
-
*/
|
|
36
|
+
.input(z.object({ workspaceId: z.string() }))
|
|
37
|
+
.query(({ ctx, input }) => new InvitationService(ctx.db).getPendingInvitations(ctx.session.user.id, input.workspaceId)),
|
|
614
38
|
cancelInvitation: authedProcedure
|
|
615
|
-
.input(z.object({
|
|
616
|
-
|
|
617
|
-
}))
|
|
618
|
-
.mutation(async ({ ctx, input }) => {
|
|
619
|
-
// Check if user is owner of the workspace
|
|
620
|
-
const invitation = await ctx.db.workspaceInvitation.findFirst({
|
|
621
|
-
where: {
|
|
622
|
-
id: input.invitationId,
|
|
623
|
-
acceptedAt: null,
|
|
624
|
-
workspace: {
|
|
625
|
-
ownerId: ctx.session.user.id
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
if (!invitation) {
|
|
630
|
-
throw new TRPCError({
|
|
631
|
-
code: 'NOT_FOUND',
|
|
632
|
-
message: 'Invitation not found or insufficient permissions'
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
// Delete the invitation
|
|
636
|
-
await ctx.db.workspaceInvitation.delete({
|
|
637
|
-
where: { id: input.invitationId }
|
|
638
|
-
});
|
|
639
|
-
logger.info(`❌ Invitation cancelled for ${invitation.email} to workspace ${invitation.workspaceId}`, 'WORKSPACE', {
|
|
640
|
-
invitationId: input.invitationId,
|
|
641
|
-
workspaceId: invitation.workspaceId,
|
|
642
|
-
email: invitation.email,
|
|
643
|
-
cancelledBy: ctx.session.user.id
|
|
644
|
-
});
|
|
645
|
-
return {
|
|
646
|
-
invitationId: input.invitationId,
|
|
647
|
-
message: 'Invitation cancelled successfully'
|
|
648
|
-
};
|
|
649
|
-
}),
|
|
650
|
-
/**
|
|
651
|
-
* Resend a pending invitation (owner only)
|
|
652
|
-
*/
|
|
39
|
+
.input(z.object({ invitationId: z.string() }))
|
|
40
|
+
.mutation(({ ctx, input }) => new InvitationService(ctx.db).cancelInvitation(ctx.session.user.id, input.invitationId)),
|
|
653
41
|
resendInvitation: authedProcedure
|
|
654
|
-
.input(z.object({
|
|
655
|
-
|
|
656
|
-
}))
|
|
657
|
-
.mutation(async ({ ctx, input }) => {
|
|
658
|
-
// Check if user is owner of the workspace and invitation is pending
|
|
659
|
-
const invitation = await ctx.db.workspaceInvitation.findFirst({
|
|
660
|
-
where: {
|
|
661
|
-
id: input.invitationId,
|
|
662
|
-
acceptedAt: null,
|
|
663
|
-
workspace: {
|
|
664
|
-
ownerId: ctx.session.user.id
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
include: {
|
|
668
|
-
workspace: {
|
|
669
|
-
select: {
|
|
670
|
-
title: true,
|
|
671
|
-
owner: {
|
|
672
|
-
select: {
|
|
673
|
-
name: true,
|
|
674
|
-
email: true,
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
if (!invitation) {
|
|
682
|
-
throw new TRPCError({
|
|
683
|
-
code: 'NOT_FOUND',
|
|
684
|
-
message: 'Invitation not found or insufficient permissions'
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
// Check if expired and update expiry if needed
|
|
688
|
-
if (invitation.expiresAt < new Date()) {
|
|
689
|
-
const newExpiry = new Date();
|
|
690
|
-
newExpiry.setDate(newExpiry.getDate() + 7);
|
|
691
|
-
await ctx.db.workspaceInvitation.update({
|
|
692
|
-
where: { id: invitation.id },
|
|
693
|
-
data: { expiresAt: newExpiry }
|
|
694
|
-
});
|
|
695
|
-
invitation.expiresAt = newExpiry;
|
|
696
|
-
}
|
|
697
|
-
// Send email notification
|
|
698
|
-
await sendInvitationEmail({
|
|
699
|
-
email: invitation.email,
|
|
700
|
-
token: invitation.token,
|
|
701
|
-
role: invitation.role,
|
|
702
|
-
workspaceTitle: invitation.workspace.title,
|
|
703
|
-
invitedByName: invitation.workspace.owner.name || invitation.workspace.owner.email || 'Someone',
|
|
704
|
-
});
|
|
705
|
-
logger.info(`📧 Invitation resent to ${invitation.email} for workspace ${invitation.workspaceId}`, 'WORKSPACE', {
|
|
706
|
-
invitationId: invitation.id,
|
|
707
|
-
workspaceId: invitation.workspaceId,
|
|
708
|
-
email: invitation.email,
|
|
709
|
-
resentBy: ctx.session.user.id
|
|
710
|
-
});
|
|
711
|
-
return {
|
|
712
|
-
invitationId: invitation.id,
|
|
713
|
-
message: 'Invitation email resent successfully'
|
|
714
|
-
};
|
|
715
|
-
}),
|
|
716
|
-
/**
|
|
717
|
-
* DEBUG ONLY: Get all invitations for a workspace
|
|
718
|
-
*/
|
|
42
|
+
.input(z.object({ invitationId: z.string() }))
|
|
43
|
+
.mutation(({ ctx, input }) => new InvitationService(ctx.db).resendInvitation(ctx.session.user.id, input.invitationId)),
|
|
719
44
|
getAllInvitationsDebug: authedProcedure
|
|
720
|
-
.input(z.object({
|
|
721
|
-
|
|
722
|
-
}))
|
|
723
|
-
.query(async ({ ctx, input }) => {
|
|
724
|
-
// Check if user is owner
|
|
725
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
726
|
-
where: { id: input.workspaceId, ownerId: ctx.session.user.id }
|
|
727
|
-
});
|
|
728
|
-
if (!workspace)
|
|
729
|
-
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
|
730
|
-
return ctx.db.workspaceInvitation.findMany({
|
|
731
|
-
where: { workspaceId: input.workspaceId },
|
|
732
|
-
orderBy: { createdAt: 'desc' }
|
|
733
|
-
});
|
|
734
|
-
}),
|
|
45
|
+
.input(z.object({ workspaceId: z.string() }))
|
|
46
|
+
.query(({ ctx, input }) => new InvitationService(ctx.db).getAllInvitationsDebug(ctx.session.user.id, input.workspaceId)),
|
|
735
47
|
});
|