@goscribe/server 1.5.0 → 1.7.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/dist/context.d.ts +14 -1
- package/dist/context.js +23 -2
- package/dist/generated/prisma/client.d.ts +224 -0
- package/dist/generated/prisma/client.js +34 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +941 -0
- package/dist/generated/prisma/commonInputTypes.js +10 -0
- package/dist/generated/prisma/enums.d.ts +67 -0
- package/dist/generated/prisma/enums.js +66 -0
- package/dist/generated/prisma/internal/class.d.ts +539 -0
- package/dist/generated/prisma/internal/class.js +49 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +3924 -0
- package/dist/generated/prisma/internal/prismaNamespace.js +557 -0
- package/dist/generated/prisma/models/ActivityLog.d.ts +1847 -0
- package/dist/generated/prisma/models/ActivityLog.js +1 -0
- package/dist/generated/prisma/models/Artifact.d.ts +2345 -0
- package/dist/generated/prisma/models/Artifact.js +1 -0
- package/dist/generated/prisma/models/ArtifactVersion.d.ts +1550 -0
- package/dist/generated/prisma/models/ArtifactVersion.js +1 -0
- package/dist/generated/prisma/models/Channel.d.ts +1257 -0
- package/dist/generated/prisma/models/Channel.js +1 -0
- package/dist/generated/prisma/models/Chat.d.ts +1339 -0
- package/dist/generated/prisma/models/Chat.js +1 -0
- package/dist/generated/prisma/models/CopilotConversation.d.ts +1450 -0
- package/dist/generated/prisma/models/CopilotConversation.js +1 -0
- package/dist/generated/prisma/models/CopilotMessage.d.ts +1179 -0
- package/dist/generated/prisma/models/CopilotMessage.js +1 -0
- package/dist/generated/prisma/models/FileAsset.d.ts +1832 -0
- package/dist/generated/prisma/models/FileAsset.js +1 -0
- package/dist/generated/prisma/models/Flashcard.d.ts +1460 -0
- package/dist/generated/prisma/models/Flashcard.js +1 -0
- package/dist/generated/prisma/models/FlashcardProgress.d.ts +1782 -0
- package/dist/generated/prisma/models/FlashcardProgress.js +1 -0
- package/dist/generated/prisma/models/Folder.d.ts +1685 -0
- package/dist/generated/prisma/models/Folder.js +1 -0
- package/dist/generated/prisma/models/IdempotencyRecord.d.ts +1319 -0
- package/dist/generated/prisma/models/IdempotencyRecord.js +1 -0
- package/dist/generated/prisma/models/Invoice.d.ts +1586 -0
- package/dist/generated/prisma/models/Invoice.js +1 -0
- package/dist/generated/prisma/models/KnowledgeBase.d.ts +1721 -0
- package/dist/generated/prisma/models/KnowledgeBase.js +1 -0
- package/dist/generated/prisma/models/KnowledgeBaseChunk.d.ts +1333 -0
- package/dist/generated/prisma/models/KnowledgeBaseChunk.js +1 -0
- package/dist/generated/prisma/models/KnowledgeBaseDocument.d.ts +1695 -0
- package/dist/generated/prisma/models/KnowledgeBaseDocument.js +1 -0
- package/dist/generated/prisma/models/Notification.d.ts +1992 -0
- package/dist/generated/prisma/models/Notification.js +1 -0
- package/dist/generated/prisma/models/PasswordResetToken.d.ts +1210 -0
- package/dist/generated/prisma/models/PasswordResetToken.js +1 -0
- package/dist/generated/prisma/models/Plan.d.ts +1431 -0
- package/dist/generated/prisma/models/Plan.js +1 -0
- package/dist/generated/prisma/models/PlanLimit.d.ts +1328 -0
- package/dist/generated/prisma/models/PlanLimit.js +1 -0
- package/dist/generated/prisma/models/PodcastSegment.d.ts +1564 -0
- package/dist/generated/prisma/models/PodcastSegment.js +1 -0
- package/dist/generated/prisma/models/ResourcePrice.d.ts +1008 -0
- package/dist/generated/prisma/models/ResourcePrice.js +1 -0
- package/dist/generated/prisma/models/Role.d.ts +1065 -0
- package/dist/generated/prisma/models/Role.js +1 -0
- package/dist/generated/prisma/models/Session.d.ts +1105 -0
- package/dist/generated/prisma/models/Session.js +1 -0
- package/dist/generated/prisma/models/StripeEvent.d.ts +1081 -0
- package/dist/generated/prisma/models/StripeEvent.js +1 -0
- package/dist/generated/prisma/models/StudyGuideComment.d.ts +1321 -0
- package/dist/generated/prisma/models/StudyGuideComment.js +1 -0
- package/dist/generated/prisma/models/StudyGuideHighlight.d.ts +1629 -0
- package/dist/generated/prisma/models/StudyGuideHighlight.js +1 -0
- package/dist/generated/prisma/models/Subscription.d.ts +1677 -0
- package/dist/generated/prisma/models/Subscription.js +1 -0
- package/dist/generated/prisma/models/User.d.ts +7559 -0
- package/dist/generated/prisma/models/User.js +1 -0
- package/dist/generated/prisma/models/UserCredit.d.ts +1249 -0
- package/dist/generated/prisma/models/UserCredit.js +1 -0
- package/dist/generated/prisma/models/VerificationToken.d.ts +946 -0
- package/dist/generated/prisma/models/VerificationToken.js +1 -0
- package/dist/generated/prisma/models/WorksheetPreset.d.ts +1433 -0
- package/dist/generated/prisma/models/WorksheetPreset.js +1 -0
- package/dist/generated/prisma/models/WorksheetQuestion.d.ts +1491 -0
- package/dist/generated/prisma/models/WorksheetQuestion.js +1 -0
- package/dist/generated/prisma/models/WorksheetQuestionProgress.d.ts +1620 -0
- package/dist/generated/prisma/models/WorksheetQuestionProgress.js +1 -0
- package/dist/generated/prisma/models/Workspace.d.ts +3620 -0
- package/dist/generated/prisma/models/Workspace.js +1 -0
- package/dist/generated/prisma/models/WorkspaceInvitation.d.ts +1490 -0
- package/dist/generated/prisma/models/WorkspaceInvitation.js +1 -0
- package/dist/generated/prisma/models/WorkspaceKnowledgeBase.d.ts +1410 -0
- package/dist/generated/prisma/models/WorkspaceKnowledgeBase.js +1 -0
- package/dist/generated/prisma/models/WorkspaceMember.d.ts +1326 -0
- package/dist/generated/prisma/models/WorkspaceMember.js +1 -0
- package/dist/generated/prisma/models.d.ts +39 -0
- package/dist/generated/prisma/models.js +1 -0
- package/dist/lib/ai/index.d.ts +3 -2
- package/dist/lib/ai/index.js +3 -2
- package/dist/lib/ai/llm-client.d.ts +1 -0
- package/dist/lib/ai/llm-client.js +17 -0
- package/dist/routers/_app.d.ts +40 -80
- package/dist/routers/auth.js +1 -1
- package/dist/routers/flashcards.d.ts +12 -1
- package/dist/routers/payment.d.ts +1 -12
- package/dist/routers/workspace.d.ts +27 -67
- package/dist/routers/workspace.js +1 -0
- package/dist/services/billing/payment.service.d.ts +1 -12
- package/dist/services/billing/payment.service.js +3 -6
- package/dist/services/billing/usage.service.d.ts +30 -10
- package/dist/services/billing/usage.service.js +87 -15
- package/dist/services/content/copilot.service.js +15 -29
- package/dist/services/content/flashcard-progress.service.js +9 -9
- package/dist/services/content/flashcard.service.d.ts +45 -1
- package/dist/services/content/flashcard.service.js +81 -68
- package/dist/services/content/media-analysis.service.js +27 -27
- package/dist/services/content/worksheet-generation.service.test.js +2 -2
- package/dist/services/workspace/workspace.service.d.ts +23 -67
- package/dist/services/workspace/workspace.service.js +69 -62
- package/dist/src/context.d.ts +27 -0
- package/dist/src/context.js +33 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +1 -0
- package/dist/src/lib/ai/config.d.ts +20 -0
- package/dist/src/lib/ai/config.js +31 -0
- package/dist/src/lib/ai/embedding-client.d.ts +8 -0
- package/dist/src/lib/ai/embedding-client.js +30 -0
- package/dist/src/lib/ai/index.d.ts +48 -0
- package/dist/src/lib/ai/index.js +29 -0
- package/dist/src/lib/ai/inference-backend/client.d.ts +28 -0
- package/dist/src/lib/ai/inference-backend/client.js +301 -0
- package/dist/src/lib/ai/inference-backend/mocks.d.ts +12 -0
- package/dist/src/lib/ai/inference-backend/mocks.js +133 -0
- package/dist/src/lib/ai/inference-backend/types.d.ts +44 -0
- package/dist/src/lib/ai/inference-backend/types.js +1 -0
- package/dist/src/lib/ai/json-parse.d.ts +2 -0
- package/dist/src/lib/ai/json-parse.js +34 -0
- package/dist/src/lib/ai/llm-client.d.ts +7 -0
- package/dist/src/lib/ai/llm-client.js +36 -0
- package/dist/src/lib/ai/mock.d.ts +2 -0
- package/dist/src/lib/ai/mock.js +10 -0
- package/dist/src/lib/ai/types.d.ts +9 -0
- package/dist/src/lib/ai/types.js +1 -0
- package/dist/src/lib/auth.d.ts +36 -0
- package/dist/src/lib/auth.js +117 -0
- package/dist/src/lib/chunking.d.ts +19 -0
- package/dist/src/lib/chunking.js +47 -0
- package/dist/src/lib/constants.d.ts +13 -0
- package/dist/src/lib/constants.js +12 -0
- package/dist/src/lib/curated-kb-seed.d.ts +12 -0
- package/dist/src/lib/curated-kb-seed.js +155 -0
- package/dist/src/lib/email.d.ts +11 -0
- package/dist/src/lib/email.js +152 -0
- package/dist/src/lib/embeddings.d.ts +2 -0
- package/dist/src/lib/embeddings.js +1 -0
- package/dist/src/lib/ensure-curated-kb-catalog.d.ts +6 -0
- package/dist/src/lib/ensure-curated-kb-catalog.js +53 -0
- package/dist/src/lib/env.d.ts +41 -0
- package/dist/src/lib/env.js +57 -0
- package/dist/src/lib/errors.d.ts +33 -0
- package/dist/src/lib/errors.js +78 -0
- package/dist/src/lib/file.d.ts +0 -0
- package/dist/src/lib/file.js +1 -0
- package/dist/src/lib/inference.d.ts +1 -0
- package/dist/src/lib/inference.js +1 -0
- package/dist/src/lib/kb-meta.d.ts +8 -0
- package/dist/src/lib/kb-meta.js +77 -0
- package/dist/src/lib/logger.d.ts +62 -0
- package/dist/src/lib/logger.js +364 -0
- package/dist/src/lib/pdf.d.ts +11 -0
- package/dist/src/lib/pdf.js +11 -0
- package/dist/src/lib/prisma.d.ts +3 -0
- package/dist/src/lib/prisma.js +15 -0
- package/dist/src/lib/pusher.d.ts +38 -0
- package/dist/src/lib/pusher.js +170 -0
- package/dist/src/lib/retry.d.ts +15 -0
- package/dist/src/lib/retry.js +37 -0
- package/dist/src/lib/storage.d.ts +11 -0
- package/dist/src/lib/storage.js +71 -0
- package/dist/src/lib/stripe.d.ts +10 -0
- package/dist/src/lib/stripe.js +36 -0
- package/dist/src/lib/validation.d.ts +51 -0
- package/dist/src/lib/validation.js +64 -0
- package/dist/src/lib/workspace-kb.d.ts +5 -0
- package/dist/src/lib/workspace-kb.js +7 -0
- package/dist/src/repositories/artifact.repository.d.ts +64 -0
- package/dist/src/repositories/artifact.repository.js +40 -0
- package/dist/src/repositories/base.repository.d.ts +14 -0
- package/dist/src/repositories/base.repository.js +14 -0
- package/dist/src/repositories/invitation.repository.d.ts +104 -0
- package/dist/src/repositories/invitation.repository.js +44 -0
- package/dist/src/repositories/notification.repository.d.ts +76 -0
- package/dist/src/repositories/notification.repository.js +44 -0
- package/dist/src/repositories/user.repository.d.ts +84 -0
- package/dist/src/repositories/user.repository.js +37 -0
- package/dist/src/repositories/workspace-member.repository.d.ts +35 -0
- package/dist/src/repositories/workspace-member.repository.js +31 -0
- package/dist/src/repositories/workspace.repository.d.ts +101 -0
- package/dist/src/repositories/workspace.repository.js +79 -0
- package/dist/src/routers/_app.d.ts +3464 -0
- package/dist/src/routers/_app.js +36 -0
- package/dist/src/routers/admin.d.ts +358 -0
- package/dist/src/routers/admin.js +105 -0
- package/dist/src/routers/annotations.d.ts +219 -0
- package/dist/src/routers/annotations.js +29 -0
- package/dist/src/routers/artifactVersions.d.ts +65 -0
- package/dist/src/routers/artifactVersions.js +14 -0
- package/dist/src/routers/auth.d.ts +161 -0
- package/dist/src/routers/auth.js +97 -0
- package/dist/src/routers/chat.d.ts +170 -0
- package/dist/src/routers/chat.js +32 -0
- package/dist/src/routers/copilot.d.ts +200 -0
- package/dist/src/routers/copilot.js +52 -0
- package/dist/src/routers/flashcards.d.ts +336 -0
- package/dist/src/routers/flashcards.js +93 -0
- package/dist/src/routers/knowledgeBase.d.ts +421 -0
- package/dist/src/routers/knowledgeBase.js +118 -0
- package/dist/src/routers/members.d.ts +169 -0
- package/dist/src/routers/members.js +47 -0
- package/dist/src/routers/notifications.d.ts +99 -0
- package/dist/src/routers/notifications.js +25 -0
- package/dist/src/routers/payment.d.ts +80 -0
- package/dist/src/routers/payment.js +21 -0
- package/dist/src/routers/podcast.d.ts +287 -0
- package/dist/src/routers/podcast.js +34 -0
- package/dist/src/routers/studyguide.d.ts +36 -0
- package/dist/src/routers/studyguide.js +8 -0
- package/dist/src/routers/worksheets.d.ts +429 -0
- package/dist/src/routers/worksheets.js +139 -0
- package/dist/src/routers/workspace.d.ts +563 -0
- package/dist/src/routers/workspace.js +104 -0
- package/dist/src/scripts/purge-deleted-users.d.ts +1 -0
- package/dist/src/scripts/purge-deleted-users.js +148 -0
- package/dist/src/server.d.ts +1 -0
- package/dist/src/server.js +190 -0
- package/dist/src/services/activity/activity-human-description.service.d.ts +13 -0
- package/dist/src/services/activity/activity-human-description.service.js +221 -0
- package/dist/src/services/activity/activity-human-description.service.test.d.ts +1 -0
- package/dist/src/services/activity/activity-human-description.service.test.js +16 -0
- package/dist/src/services/activity/activity-log.service.d.ts +87 -0
- package/dist/src/services/activity/activity-log.service.js +276 -0
- package/dist/src/services/activity/activity-log.service.test.d.ts +1 -0
- package/dist/src/services/activity/activity-log.service.test.js +27 -0
- package/dist/src/services/admin/admin.service.d.ts +270 -0
- package/dist/src/services/admin/admin.service.js +476 -0
- package/dist/src/services/ai/ai-session.service.d.ts +5 -0
- package/dist/src/services/ai/ai-session.service.js +4 -0
- package/dist/src/services/artifacts/annotation.service.d.ts +177 -0
- package/dist/src/services/artifacts/annotation.service.js +154 -0
- package/dist/src/services/artifacts/artifact-version.service.d.ts +38 -0
- package/dist/src/services/artifacts/artifact-version.service.js +129 -0
- package/dist/src/services/artifacts/chat.service.d.ts +127 -0
- package/dist/src/services/artifacts/chat.service.js +182 -0
- package/dist/src/services/artifacts/study-guide.service.d.ts +18 -0
- package/dist/src/services/artifacts/study-guide.service.js +65 -0
- package/dist/src/services/auth/auth.service.d.ts +94 -0
- package/dist/src/services/auth/auth.service.js +368 -0
- package/dist/src/services/base.service.d.ts +14 -0
- package/dist/src/services/base.service.js +14 -0
- package/dist/src/services/billing/payment.service.d.ts +44 -0
- package/dist/src/services/billing/payment.service.js +365 -0
- package/dist/src/services/billing/subscription.service.d.ts +37 -0
- package/dist/src/services/billing/subscription.service.js +654 -0
- package/dist/src/services/billing/usage.service.d.ts +47 -0
- package/dist/src/services/billing/usage.service.js +149 -0
- package/dist/src/services/content/copilot.service.d.ts +113 -0
- package/dist/src/services/content/copilot.service.js +439 -0
- package/dist/src/services/content/flashcard-progress.service.d.ts +159 -0
- package/dist/src/services/content/flashcard-progress.service.js +432 -0
- package/dist/src/services/content/flashcard.service.d.ts +184 -0
- package/dist/src/services/content/flashcard.service.js +339 -0
- package/dist/src/services/content/media-analysis.service.d.ts +23 -0
- package/dist/src/services/content/media-analysis.service.js +404 -0
- package/dist/src/services/content/podcast.service.d.ts +267 -0
- package/dist/src/services/content/podcast.service.js +653 -0
- package/dist/src/services/content/worksheet-content.service.d.ts +37 -0
- package/dist/src/services/content/worksheet-content.service.js +84 -0
- package/dist/src/services/content/worksheet-content.service.test.d.ts +1 -0
- package/dist/src/services/content/worksheet-content.service.test.js +69 -0
- package/dist/src/services/content/worksheet-generation.service.d.ts +91 -0
- package/dist/src/services/content/worksheet-generation.service.js +95 -0
- package/dist/src/services/content/worksheet-generation.service.test.d.ts +1 -0
- package/dist/src/services/content/worksheet-generation.service.test.js +20 -0
- package/dist/src/services/content/worksheet.service.d.ts +347 -0
- package/dist/src/services/content/worksheet.service.js +599 -0
- package/dist/src/services/knowledge/knowledge-base.service.d.ts +316 -0
- package/dist/src/services/knowledge/knowledge-base.service.js +544 -0
- package/dist/src/services/members/invitation.service.d.ts +66 -0
- package/dist/src/services/members/invitation.service.js +348 -0
- package/dist/src/services/members/member.service.d.ts +36 -0
- package/dist/src/services/members/member.service.js +193 -0
- package/dist/src/services/notifications/notification.service.d.ts +214 -0
- package/dist/src/services/notifications/notification.service.js +550 -0
- package/dist/src/services/notifications/notification.service.test.d.ts +1 -0
- package/dist/src/services/notifications/notification.service.test.js +87 -0
- package/dist/src/services/workspace/workspace-analytics.service.d.ts +24 -0
- package/dist/src/services/workspace/workspace-analytics.service.js +95 -0
- package/dist/src/services/workspace/workspace-kb.service.d.ts +40 -0
- package/dist/src/services/workspace/workspace-kb.service.js +184 -0
- package/dist/src/services/workspace/workspace.service.d.ts +263 -0
- package/dist/src/services/workspace/workspace.service.js +401 -0
- package/dist/src/trpc.d.ts +60 -0
- package/dist/src/trpc.js +217 -0
- package/dist/src/types/index.d.ts +126 -0
- package/dist/src/types/index.js +1 -0
- package/dist/trpc.d.ts +12 -4
- package/dist/trpc.js +5 -11
- package/package.json +8 -9
- package/prisma/schema.prisma +3 -4
- package/prisma/seed.mjs +5 -2
- package/prisma.config.ts +16 -0
- package/src/context.ts +33 -3
- package/src/lib/ai/index.ts +3 -0
- package/src/lib/ai/llm-client.ts +23 -0
- package/src/lib/prisma.ts +18 -9
- package/src/routers/auth.ts +1 -1
- package/src/routers/workspace.ts +4 -0
- package/src/scripts/purge-deleted-users.ts +1 -3
- package/src/services/billing/payment.service.ts +3 -6
- package/src/services/billing/usage.service.ts +190 -77
- package/src/services/content/copilot.service.ts +23 -32
- package/src/services/content/flashcard-progress.service.ts +12 -9
- package/src/services/content/flashcard.service.ts +89 -66
- package/src/services/content/media-analysis.service.ts +34 -29
- package/src/services/content/worksheet-generation.service.test.ts +2 -2
- package/src/services/workspace/workspace.service.ts +73 -66
- package/src/trpc.ts +5 -13
- package/tsconfig.json +3 -0
|
@@ -1,94 +1,207 @@
|
|
|
1
|
+
import type { Prisma } from '@prisma/client';
|
|
1
2
|
import { prisma } from '../../lib/prisma.js';
|
|
3
|
+
import { workspaceAccessWhere } from '../../repositories/workspace.repository.js';
|
|
2
4
|
|
|
3
5
|
export interface UserUsage {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
flashcards: number;
|
|
7
|
+
worksheets: number;
|
|
8
|
+
studyGuides: number;
|
|
9
|
+
podcasts: number;
|
|
10
|
+
storageBytes: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AccountStats {
|
|
14
|
+
workspaces: number;
|
|
15
|
+
folders: number;
|
|
16
|
+
lastUpdated: Date | null;
|
|
17
|
+
spaceUsed: number;
|
|
18
|
+
spaceTotal: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UserPlanLimits {
|
|
22
|
+
id: string;
|
|
23
|
+
planId: string;
|
|
24
|
+
maxStorageBytes: bigint;
|
|
25
|
+
maxWorksheets: number;
|
|
26
|
+
maxFlashcards: number;
|
|
27
|
+
maxPodcasts: number;
|
|
28
|
+
maxStudyGuides: number;
|
|
29
|
+
createdAt: Date;
|
|
30
|
+
updatedAt: Date;
|
|
31
|
+
isFallbackPlan: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AccountSummary {
|
|
35
|
+
stats: AccountStats;
|
|
36
|
+
usage: UserUsage;
|
|
37
|
+
limits: UserPlanLimits;
|
|
38
|
+
hasActivePlan: boolean;
|
|
9
39
|
}
|
|
10
40
|
|
|
11
41
|
const FALLBACK_PLAN_LIMITS = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
42
|
+
id: 'fallback-free',
|
|
43
|
+
planId: 'fallback-free',
|
|
44
|
+
maxStorageBytes: BigInt(1024 * 1024 * 1024), // 1GB
|
|
45
|
+
maxWorksheets: 3,
|
|
46
|
+
maxFlashcards: 20,
|
|
47
|
+
maxPodcasts: 0,
|
|
48
|
+
maxStudyGuides: 1,
|
|
19
49
|
} as const;
|
|
20
50
|
|
|
51
|
+
const CACHE_TTL_MS = 30_000;
|
|
52
|
+
|
|
53
|
+
type CacheEntry<T> = { value: T; expiresAt: number };
|
|
54
|
+
|
|
55
|
+
const usageCache = new Map<string, CacheEntry<UserUsage>>();
|
|
56
|
+
const limitsCache = new Map<string, CacheEntry<UserPlanLimits>>();
|
|
57
|
+
const accountSummaryCache = new Map<string, CacheEntry<AccountSummary>>();
|
|
58
|
+
|
|
59
|
+
function readCache<T>(map: Map<string, CacheEntry<T>>, userId: string): T | null {
|
|
60
|
+
const entry = map.get(userId);
|
|
61
|
+
if (!entry || entry.expiresAt <= Date.now()) {
|
|
62
|
+
map.delete(userId);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return entry.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeCache<T>(map: Map<string, CacheEntry<T>>, userId: string, value: T) {
|
|
69
|
+
map.set(userId, { value, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Bust cached usage/limit reads after creates, deletes, or billing changes. */
|
|
73
|
+
export function invalidateUserBillingCache(userId: string) {
|
|
74
|
+
usageCache.delete(userId);
|
|
75
|
+
limitsCache.delete(userId);
|
|
76
|
+
accountSummaryCache.delete(userId);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function workspaceAccessFilter(userId: string): Prisma.WorkspaceWhereInput {
|
|
80
|
+
return workspaceAccessWhere(userId);
|
|
81
|
+
}
|
|
82
|
+
|
|
21
83
|
/**
|
|
22
84
|
* Counts all resources consumed by a user across the platform.
|
|
23
85
|
*/
|
|
24
86
|
export async function getUserUsage(userId: string): Promise<UserUsage> {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
87
|
+
const cached = readCache(usageCache, userId);
|
|
88
|
+
if (cached) return cached;
|
|
89
|
+
|
|
90
|
+
const [flashcards, worksheets, studyGuides, podcasts, storageResult] = await Promise.all([
|
|
91
|
+
prisma.artifact.count({ where: { createdById: userId, type: 'FLASHCARD_SET' } }),
|
|
92
|
+
prisma.artifact.count({ where: { createdById: userId, type: 'WORKSHEET' } }),
|
|
93
|
+
prisma.artifact.count({ where: { createdById: userId, type: 'STUDY_GUIDE' } }),
|
|
94
|
+
prisma.artifact.count({ where: { createdById: userId, type: 'PODCAST_EPISODE' } }),
|
|
95
|
+
prisma.fileAsset.aggregate({
|
|
96
|
+
_sum: { size: true },
|
|
97
|
+
where: { userId },
|
|
98
|
+
}),
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const usage: UserUsage = {
|
|
102
|
+
flashcards,
|
|
103
|
+
worksheets,
|
|
104
|
+
studyGuides,
|
|
105
|
+
podcasts,
|
|
106
|
+
storageBytes: Number(storageResult._sum.size || 0),
|
|
107
|
+
};
|
|
108
|
+
writeCache(usageCache, userId, usage);
|
|
109
|
+
return usage;
|
|
43
110
|
}
|
|
44
111
|
|
|
45
112
|
/**
|
|
46
|
-
* Retrieves the specific plan limits for a user based on their active subscription
|
|
113
|
+
* Retrieves the specific plan limits for a user based on their active subscription
|
|
47
114
|
* PLUS any extra credits purchased.
|
|
48
115
|
*/
|
|
49
|
-
export async function getUserPlanLimits(userId: string) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
116
|
+
export async function getUserPlanLimits(userId: string): Promise<UserPlanLimits> {
|
|
117
|
+
const cached = readCache(limitsCache, userId);
|
|
118
|
+
if (cached) return cached;
|
|
119
|
+
|
|
120
|
+
const [activeSub, freePlan, userCredits] = await Promise.all([
|
|
121
|
+
prisma.subscription.findFirst({
|
|
122
|
+
where: { userId, status: 'active' },
|
|
123
|
+
include: { plan: { include: { limit: true } } },
|
|
124
|
+
orderBy: { createdAt: 'desc' },
|
|
125
|
+
}),
|
|
126
|
+
prisma.plan.findFirst({
|
|
127
|
+
where: {
|
|
128
|
+
active: true,
|
|
129
|
+
price: 0,
|
|
130
|
+
limit: { isNot: null },
|
|
131
|
+
},
|
|
132
|
+
include: { limit: true },
|
|
133
|
+
orderBy: { createdAt: 'asc' },
|
|
134
|
+
}),
|
|
135
|
+
prisma.userCredit.groupBy({
|
|
136
|
+
by: ['resourceType'],
|
|
137
|
+
where: { userId },
|
|
138
|
+
_sum: { amount: true },
|
|
139
|
+
}),
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
const baseLimit = activeSub?.plan?.limit ?? freePlan?.limit;
|
|
143
|
+
const base = baseLimit ?? FALLBACK_PLAN_LIMITS;
|
|
144
|
+
|
|
145
|
+
const getCreditSum = (type: string) =>
|
|
146
|
+
userCredits.find((c) => c.resourceType === type)?._sum.amount || 0;
|
|
147
|
+
|
|
148
|
+
const limits: UserPlanLimits = {
|
|
149
|
+
id: base.id,
|
|
150
|
+
planId: base.planId,
|
|
151
|
+
maxStorageBytes: BigInt(Number(base.maxStorageBytes) + getCreditSum('STORAGE') * 1024 * 1024),
|
|
152
|
+
maxWorksheets: base.maxWorksheets + getCreditSum('WORKSHEET'),
|
|
153
|
+
maxFlashcards: base.maxFlashcards + getCreditSum('FLASHCARD_SET'),
|
|
154
|
+
maxPodcasts: base.maxPodcasts + getCreditSum('PODCAST_EPISODE'),
|
|
155
|
+
maxStudyGuides: base.maxStudyGuides + getCreditSum('STUDY_GUIDE'),
|
|
156
|
+
createdAt: baseLimit?.createdAt || new Date(),
|
|
157
|
+
updatedAt: baseLimit?.updatedAt || new Date(),
|
|
158
|
+
isFallbackPlan: !baseLimit,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
writeCache(limitsCache, userId, limits);
|
|
162
|
+
return limits;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Sidebar/dashboard summary: stats + usage + limits in one parallel DB round-trip batch.
|
|
167
|
+
*/
|
|
168
|
+
export async function getAccountSummary(userId: string): Promise<AccountSummary> {
|
|
169
|
+
const cached = readCache(accountSummaryCache, userId);
|
|
170
|
+
if (cached) return cached;
|
|
171
|
+
|
|
172
|
+
const workspaceWhere = workspaceAccessFilter(userId);
|
|
173
|
+
|
|
174
|
+
const [workspaceMeta, folderCount, usage, limits, storageUsed] = await Promise.all([
|
|
175
|
+
prisma.workspace.aggregate({
|
|
176
|
+
where: workspaceWhere,
|
|
177
|
+
_count: { _all: true },
|
|
178
|
+
_max: { updatedAt: true },
|
|
179
|
+
}),
|
|
180
|
+
prisma.folder.count({ where: { ownerId: userId } }),
|
|
181
|
+
getUserUsage(userId),
|
|
182
|
+
getUserPlanLimits(userId),
|
|
183
|
+
prisma.fileAsset.aggregate({
|
|
184
|
+
where: {
|
|
185
|
+
userId,
|
|
186
|
+
workspace: workspaceWhere,
|
|
187
|
+
},
|
|
188
|
+
_sum: { size: true },
|
|
189
|
+
}),
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
const summary: AccountSummary = {
|
|
193
|
+
stats: {
|
|
194
|
+
workspaces: workspaceMeta._count._all,
|
|
195
|
+
folders: folderCount,
|
|
196
|
+
lastUpdated: workspaceMeta._max.updatedAt,
|
|
197
|
+
spaceUsed: Number(storageUsed._sum.size ?? 0),
|
|
198
|
+
spaceTotal: Number(limits.maxStorageBytes),
|
|
199
|
+
},
|
|
200
|
+
usage,
|
|
201
|
+
limits,
|
|
202
|
+
hasActivePlan: !limits.isFallbackPlan,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
writeCache(accountSummaryCache, userId, summary);
|
|
206
|
+
return summary;
|
|
207
|
+
}
|
|
@@ -8,6 +8,7 @@ import { sanitizeString } from '../../lib/validation.js';
|
|
|
8
8
|
import { workspaceAccessWhere } from '../../repositories/workspace.repository.js';
|
|
9
9
|
import PusherService from '../../lib/pusher.js';
|
|
10
10
|
import { ArtifactType } from '../../lib/constants.js';
|
|
11
|
+
import { FlashcardService } from './flashcard.service.js';
|
|
11
12
|
|
|
12
13
|
export const copilotArtifactType = z.enum([
|
|
13
14
|
'study-guide',
|
|
@@ -382,10 +383,6 @@ export class CopilotService extends BaseService {
|
|
|
382
383
|
enforceRateLimit(userId, input.context.workspaceId);
|
|
383
384
|
await this.assertWorkspaceAccess(userId, input.context.workspaceId);
|
|
384
385
|
|
|
385
|
-
await PusherService.emitTaskComplete(input.context.workspaceId, 'copilot:thinking', {
|
|
386
|
-
status: 'started',
|
|
387
|
-
});
|
|
388
|
-
|
|
389
386
|
const history = await this.loadHistory(input.conversationId);
|
|
390
387
|
const model = await callCopilotModel({
|
|
391
388
|
mode: 'ask',
|
|
@@ -404,10 +401,6 @@ export class CopilotService extends BaseService {
|
|
|
404
401
|
input.context.documentPlainText ?? input.context.documentContent,
|
|
405
402
|
);
|
|
406
403
|
|
|
407
|
-
await PusherService.emitTaskComplete(input.context.workspaceId, 'copilot:response', {
|
|
408
|
-
status: 'completed',
|
|
409
|
-
});
|
|
410
|
-
|
|
411
404
|
await this.persistConversationExchange({
|
|
412
405
|
userId,
|
|
413
406
|
workspaceId: input.context.workspaceId,
|
|
@@ -426,10 +419,6 @@ export class CopilotService extends BaseService {
|
|
|
426
419
|
enforceRateLimit(userId, input.context.workspaceId);
|
|
427
420
|
await this.assertWorkspaceAccess(userId, input.context.workspaceId);
|
|
428
421
|
|
|
429
|
-
await PusherService.emitTaskComplete(input.context.workspaceId, 'copilot:thinking', {
|
|
430
|
-
status: 'started',
|
|
431
|
-
});
|
|
432
|
-
|
|
433
422
|
const question =
|
|
434
423
|
input.message && input.message.trim().length > 0
|
|
435
424
|
? input.message
|
|
@@ -448,10 +437,6 @@ export class CopilotService extends BaseService {
|
|
|
448
437
|
model.rawContent ||
|
|
449
438
|
'I could not explain this selection.';
|
|
450
439
|
|
|
451
|
-
await PusherService.emitTaskComplete(input.context.workspaceId, 'copilot:response', {
|
|
452
|
-
status: 'completed',
|
|
453
|
-
});
|
|
454
|
-
|
|
455
440
|
await this.persistConversationExchange({
|
|
456
441
|
userId,
|
|
457
442
|
workspaceId: input.context.workspaceId,
|
|
@@ -551,22 +536,28 @@ export class CopilotService extends BaseService {
|
|
|
551
536
|
});
|
|
552
537
|
}
|
|
553
538
|
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
539
|
+
const flashcardService = new FlashcardService(this.db);
|
|
540
|
+
const primarySet = await flashcardService.ensurePrimarySet(
|
|
541
|
+
userId,
|
|
542
|
+
input.context.workspaceId,
|
|
543
|
+
);
|
|
544
|
+
const orderOffset = primarySet.flashcards.reduce(
|
|
545
|
+
(max, card) => Math.max(max, card.order),
|
|
546
|
+
-1,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
await this.db.flashcard.createMany({
|
|
550
|
+
data: cards.map((card, index) => ({
|
|
551
|
+
artifactId: primarySet.id,
|
|
552
|
+
front: sanitizeString(card.front, 200),
|
|
553
|
+
back: sanitizeString(card.back, 500),
|
|
554
|
+
order: orderOffset + 1 + index,
|
|
555
|
+
tags: ['copilot-generated'],
|
|
556
|
+
})),
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const artifact = await this.db.artifact.findUniqueOrThrow({
|
|
560
|
+
where: { id: primarySet.id },
|
|
570
561
|
include: { flashcards: true },
|
|
571
562
|
});
|
|
572
563
|
|
|
@@ -320,25 +320,28 @@ export class FlashcardProgressService extends BaseService {
|
|
|
320
320
|
const now = new Date();
|
|
321
321
|
const LOW_MASTERY_THRESHOLD = 50; // Consider mastery < 50 as low
|
|
322
322
|
|
|
323
|
-
|
|
324
|
-
const latestArtifact = await this.db.artifact.findFirst({
|
|
323
|
+
const flashcardSets = await this.db.artifact.findMany({
|
|
325
324
|
where: {
|
|
326
325
|
workspaceId,
|
|
327
326
|
type: 'FLASHCARD_SET',
|
|
328
327
|
},
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
},
|
|
328
|
+
include: { _count: { select: { flashcards: true } } },
|
|
329
|
+
orderBy: { createdAt: 'asc' },
|
|
332
330
|
});
|
|
333
331
|
|
|
334
|
-
if (
|
|
332
|
+
if (flashcardSets.length === 0) {
|
|
335
333
|
return [];
|
|
336
334
|
}
|
|
337
335
|
|
|
338
|
-
|
|
336
|
+
const withCards = flashcardSets.filter((set) => set._count.flashcards > 0);
|
|
337
|
+
const candidates = withCards.length > 0 ? withCards : flashcardSets;
|
|
338
|
+
const primaryArtifact = candidates.reduce((best, current) =>
|
|
339
|
+
current._count.flashcards > best._count.flashcards ? current : best,
|
|
340
|
+
);
|
|
341
|
+
|
|
339
342
|
const allFlashcards = await this.db.flashcard.findMany({
|
|
340
343
|
where: {
|
|
341
|
-
artifactId:
|
|
344
|
+
artifactId: primaryArtifact.id,
|
|
342
345
|
},
|
|
343
346
|
include: {
|
|
344
347
|
artifact: true,
|
|
@@ -376,7 +379,7 @@ export class FlashcardProgressService extends BaseService {
|
|
|
376
379
|
}
|
|
377
380
|
],
|
|
378
381
|
flashcard: {
|
|
379
|
-
artifactId:
|
|
382
|
+
artifactId: primaryArtifact.id,
|
|
380
383
|
},
|
|
381
384
|
},
|
|
382
385
|
include: {
|
|
@@ -8,6 +8,7 @@ import { ai } from '../../lib/ai/index.js';
|
|
|
8
8
|
import { workspaceKbService } from '../workspace/workspace-kb.service.js';
|
|
9
9
|
import PusherService from '../../lib/pusher.js';
|
|
10
10
|
import { notifyArtifactFailed, notifyArtifactReady } from '../notifications/notification.service.js';
|
|
11
|
+
import { invalidateUserBillingCache } from '../billing/usage.service.js';
|
|
11
12
|
|
|
12
13
|
export const typedAnswerGradeSchema = z.object({
|
|
13
14
|
isCorrect: z.boolean(),
|
|
@@ -46,6 +47,55 @@ export class FlashcardService extends BaseService {
|
|
|
46
47
|
super(db);
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
private async findPrimarySet(userId: string, workspaceId: string) {
|
|
51
|
+
const sets = await this.db.artifact.findMany({
|
|
52
|
+
where: {
|
|
53
|
+
workspaceId,
|
|
54
|
+
type: ArtifactType.FLASHCARD_SET,
|
|
55
|
+
workspace: workspaceAccessWhere(userId),
|
|
56
|
+
},
|
|
57
|
+
include: {
|
|
58
|
+
flashcards: { orderBy: { order: 'asc' } },
|
|
59
|
+
_count: { select: { flashcards: true } },
|
|
60
|
+
},
|
|
61
|
+
orderBy: { createdAt: 'asc' },
|
|
62
|
+
});
|
|
63
|
+
if (sets.length === 0) return null;
|
|
64
|
+
|
|
65
|
+
const withCards = sets.filter((set) => set._count.flashcards > 0);
|
|
66
|
+
const candidates = withCards.length > 0 ? withCards : sets;
|
|
67
|
+
return candidates.reduce((best, current) =>
|
|
68
|
+
current._count.flashcards > best._count.flashcards ? current : best,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Returns the primary set, creating one only when flashcard content is first added. */
|
|
73
|
+
async ensurePrimarySet(userId: string, workspaceId: string) {
|
|
74
|
+
const existing = await this.findPrimarySet(userId, workspaceId);
|
|
75
|
+
if (existing) return existing;
|
|
76
|
+
|
|
77
|
+
const workspace = await this.db.workspace.findFirst({
|
|
78
|
+
where: { id: workspaceId, ...workspaceAccessWhere(userId) },
|
|
79
|
+
select: { id: true, title: true },
|
|
80
|
+
});
|
|
81
|
+
if (!workspace) {
|
|
82
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return this.db.artifact.create({
|
|
86
|
+
data: {
|
|
87
|
+
workspaceId,
|
|
88
|
+
type: ArtifactType.FLASHCARD_SET,
|
|
89
|
+
title: 'Flashcards',
|
|
90
|
+
createdById: userId,
|
|
91
|
+
},
|
|
92
|
+
include: {
|
|
93
|
+
flashcards: { orderBy: { order: 'asc' } },
|
|
94
|
+
_count: { select: { flashcards: true } },
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
49
99
|
async listSets(userId: string, workspaceId: string) {
|
|
50
100
|
const workspace = await this.db.workspace.findFirst({
|
|
51
101
|
where: { id: workspaceId, ownerId: userId },
|
|
@@ -61,35 +111,20 @@ export class FlashcardService extends BaseService {
|
|
|
61
111
|
}
|
|
62
112
|
|
|
63
113
|
async listCards(userId: string, workspaceId: string) {
|
|
64
|
-
const set = await this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
workspace: workspaceAccessWhere(userId),
|
|
69
|
-
},
|
|
114
|
+
const set = await this.findPrimarySet(userId, workspaceId);
|
|
115
|
+
if (!set) return [];
|
|
116
|
+
return this.db.flashcard.findMany({
|
|
117
|
+
where: { artifactId: set.id },
|
|
70
118
|
include: {
|
|
71
|
-
|
|
72
|
-
include: {
|
|
73
|
-
progress: { where: { userId } },
|
|
74
|
-
},
|
|
75
|
-
},
|
|
119
|
+
progress: { where: { userId } },
|
|
76
120
|
},
|
|
77
|
-
orderBy: {
|
|
121
|
+
orderBy: { order: 'asc' },
|
|
78
122
|
});
|
|
79
|
-
if (!set) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
80
|
-
return set.flashcards;
|
|
81
123
|
}
|
|
82
124
|
|
|
83
125
|
async isGenerating(userId: string, workspaceId: string) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
workspaceId,
|
|
87
|
-
type: ArtifactType.FLASHCARD_SET,
|
|
88
|
-
workspace: workspaceAccessWhere(userId),
|
|
89
|
-
},
|
|
90
|
-
orderBy: { createdAt: 'desc' },
|
|
91
|
-
});
|
|
92
|
-
return artifact?.generating;
|
|
126
|
+
const set = await this.findPrimarySet(userId, workspaceId);
|
|
127
|
+
return set?.generating ?? false;
|
|
93
128
|
}
|
|
94
129
|
|
|
95
130
|
async createCard(
|
|
@@ -103,25 +138,22 @@ export class FlashcardService extends BaseService {
|
|
|
103
138
|
order?: number;
|
|
104
139
|
},
|
|
105
140
|
) {
|
|
106
|
-
const set = await this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
include: { flashcards: true },
|
|
112
|
-
orderBy: { updatedAt: 'desc' },
|
|
113
|
-
});
|
|
114
|
-
if (!set) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
115
|
-
return this.db.flashcard.create({
|
|
141
|
+
const set = await this.ensurePrimarySet(userId, input.workspaceId);
|
|
142
|
+
const nextOrder =
|
|
143
|
+
input.order ??
|
|
144
|
+
(set.flashcards.reduce((max, card) => Math.max(max, card.order), -1) + 1);
|
|
145
|
+
const card = await this.db.flashcard.create({
|
|
116
146
|
data: {
|
|
117
147
|
artifactId: set.id,
|
|
118
148
|
front: input.front,
|
|
119
149
|
back: input.back,
|
|
120
150
|
acceptedAnswers: normalizeAcceptedAnswers(input.acceptedAnswers),
|
|
121
151
|
tags: input.tags ?? [],
|
|
122
|
-
order:
|
|
152
|
+
order: nextOrder,
|
|
123
153
|
},
|
|
124
154
|
});
|
|
155
|
+
invalidateUserBillingCache(userId);
|
|
156
|
+
return card;
|
|
125
157
|
}
|
|
126
158
|
|
|
127
159
|
async updateCard(
|
|
@@ -262,14 +294,11 @@ export class FlashcardService extends BaseService {
|
|
|
262
294
|
});
|
|
263
295
|
if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
264
296
|
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
select: { id: true, flashcards: true },
|
|
271
|
-
orderBy: { updatedAt: 'desc' },
|
|
272
|
-
});
|
|
297
|
+
const primarySet = await this.ensurePrimarySet(userId, input.workspaceId);
|
|
298
|
+
const orderOffset = primarySet.flashcards.reduce(
|
|
299
|
+
(max, card) => Math.max(max, card.order),
|
|
300
|
+
-1,
|
|
301
|
+
);
|
|
273
302
|
|
|
274
303
|
try {
|
|
275
304
|
await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_info', {
|
|
@@ -278,23 +307,15 @@ export class FlashcardService extends BaseService {
|
|
|
278
307
|
difficulty: input.difficulty,
|
|
279
308
|
});
|
|
280
309
|
|
|
281
|
-
|
|
310
|
+
await this.db.artifact.update({
|
|
311
|
+
where: { id: primarySet.id },
|
|
282
312
|
data: {
|
|
283
|
-
workspaceId: input.workspaceId,
|
|
284
|
-
type: ArtifactType.FLASHCARD_SET,
|
|
285
|
-
title: input.title || `Flashcards - ${new Date().toLocaleString()}`,
|
|
286
|
-
createdById: userId,
|
|
287
313
|
generating: true,
|
|
288
314
|
generatingMetadata: {
|
|
289
315
|
quantity: input.numCards,
|
|
290
316
|
difficulty: input.difficulty.toLowerCase(),
|
|
291
317
|
},
|
|
292
|
-
|
|
293
|
-
create: flashcardCurrent?.flashcards.map((card) => ({
|
|
294
|
-
front: card.front,
|
|
295
|
-
back: card.back,
|
|
296
|
-
})),
|
|
297
|
-
},
|
|
318
|
+
...(input.title ? { title: input.title } : {}),
|
|
298
319
|
},
|
|
299
320
|
});
|
|
300
321
|
|
|
@@ -324,10 +345,10 @@ export class FlashcardService extends BaseService {
|
|
|
324
345
|
const back = card.definition || card.back || card.answer || card.solution || `Answer ${i + 1}`;
|
|
325
346
|
await this.db.flashcard.create({
|
|
326
347
|
data: {
|
|
327
|
-
artifactId:
|
|
348
|
+
artifactId: primarySet.id,
|
|
328
349
|
front,
|
|
329
350
|
back,
|
|
330
|
-
order: i,
|
|
351
|
+
order: orderOffset + 1 + i,
|
|
331
352
|
tags: input.tags ?? ['ai-generated', input.difficulty],
|
|
332
353
|
},
|
|
333
354
|
});
|
|
@@ -342,10 +363,10 @@ export class FlashcardService extends BaseService {
|
|
|
342
363
|
const [front, back] = line.split(' - ');
|
|
343
364
|
await this.db.flashcard.create({
|
|
344
365
|
data: {
|
|
345
|
-
artifactId:
|
|
366
|
+
artifactId: primarySet.id,
|
|
346
367
|
front: front.trim(),
|
|
347
368
|
back: back.trim(),
|
|
348
|
-
order: i,
|
|
369
|
+
order: orderOffset + 1 + i,
|
|
349
370
|
tags: input.tags ?? ['ai-generated', input.difficulty],
|
|
350
371
|
},
|
|
351
372
|
});
|
|
@@ -354,13 +375,14 @@ export class FlashcardService extends BaseService {
|
|
|
354
375
|
}
|
|
355
376
|
}
|
|
356
377
|
|
|
357
|
-
await
|
|
358
|
-
|
|
359
|
-
await this.db.artifact.update({
|
|
360
|
-
where: { id: artifact.id },
|
|
378
|
+
const artifact = await this.db.artifact.update({
|
|
379
|
+
where: { id: primarySet.id },
|
|
361
380
|
data: { generating: false },
|
|
381
|
+
include: { flashcards: true },
|
|
362
382
|
});
|
|
363
383
|
|
|
384
|
+
await PusherService.emitFlashcardComplete(input.workspaceId, artifact);
|
|
385
|
+
|
|
364
386
|
await notifyArtifactReady(this.db, {
|
|
365
387
|
userId,
|
|
366
388
|
workspaceId: input.workspaceId,
|
|
@@ -369,14 +391,15 @@ export class FlashcardService extends BaseService {
|
|
|
369
391
|
title: artifact.title,
|
|
370
392
|
}).catch(() => {});
|
|
371
393
|
|
|
394
|
+
invalidateUserBillingCache(userId);
|
|
372
395
|
return { artifact, createdCards };
|
|
373
396
|
} catch (error) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
where: { id:
|
|
397
|
+
await this.db.artifact
|
|
398
|
+
.update({
|
|
399
|
+
where: { id: primarySet.id },
|
|
377
400
|
data: { generating: false },
|
|
378
|
-
})
|
|
379
|
-
|
|
401
|
+
})
|
|
402
|
+
.catch(() => {});
|
|
380
403
|
await PusherService.emitError(
|
|
381
404
|
input.workspaceId,
|
|
382
405
|
`Failed to generate flashcards: ${error}`,
|