@goscribe/server 1.6.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/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/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/package.json +8 -9
- package/prisma/schema.prisma +3 -4
- package/prisma/seed.mjs +5 -2
- package/prisma.config.ts +16 -0
- package/src/lib/prisma.ts +18 -9
- package/src/scripts/purge-deleted-users.ts +1 -3
- package/tsconfig.json +3 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import Pusher from 'pusher';
|
|
2
|
+
import { logger } from './logger.js';
|
|
3
|
+
// Server-side Pusher instance
|
|
4
|
+
export const pusher = new Pusher({
|
|
5
|
+
appId: process.env.PUSHER_APP_ID || '',
|
|
6
|
+
key: process.env.PUSHER_KEY || '',
|
|
7
|
+
secret: process.env.PUSHER_SECRET || '',
|
|
8
|
+
cluster: process.env.PUSHER_CLUSTER || 'us2',
|
|
9
|
+
useTLS: true,
|
|
10
|
+
});
|
|
11
|
+
// Pusher service for managing notifications
|
|
12
|
+
export class PusherService {
|
|
13
|
+
static async emitUserEvent(userId, eventName, data) {
|
|
14
|
+
try {
|
|
15
|
+
const channel = `user_${userId}`;
|
|
16
|
+
await pusher.trigger(channel, eventName, data);
|
|
17
|
+
logger.info(`📡 Pusher user event sent: ${eventName} to ${channel}`);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
logger.error('Pusher user event error:', 'PUSHER', { error, userId, eventName });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Emit task completion notification
|
|
24
|
+
static async emitTaskComplete(workspaceId, event, data) {
|
|
25
|
+
try {
|
|
26
|
+
const channel = `workspace_${workspaceId}`;
|
|
27
|
+
const eventName = event;
|
|
28
|
+
await pusher.trigger(channel, eventName, data);
|
|
29
|
+
logger.info(`📡 Pusher notification sent: ${eventName} to ${channel}`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.error('Pusher notification error:', 'PUSHER', { error });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Emit AI analysis completion
|
|
36
|
+
static async emitAnalysisComplete(workspaceId, analysisType, result) {
|
|
37
|
+
await this.emitTaskComplete(workspaceId, `${analysisType}_ended`, {
|
|
38
|
+
type: analysisType,
|
|
39
|
+
result,
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Emit study guide completion
|
|
44
|
+
static async emitStudyGuideComplete(workspaceId, artifact) {
|
|
45
|
+
await this.emitTaskComplete(workspaceId, 'study_guide_generation_complete', {
|
|
46
|
+
artifactId: artifact.id,
|
|
47
|
+
title: artifact.title,
|
|
48
|
+
status: 'completed'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Emit flashcard completion
|
|
52
|
+
static async emitFlashcardComplete(workspaceId, artifact) {
|
|
53
|
+
await this.emitAnalysisComplete(workspaceId, 'flashcard', {
|
|
54
|
+
artifactId: artifact.id,
|
|
55
|
+
title: artifact.title,
|
|
56
|
+
status: 'completed'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Emit worksheet completion
|
|
60
|
+
static async emitWorksheetComplete(workspaceId, artifact) {
|
|
61
|
+
await this.emitAnalysisComplete(workspaceId, 'worksheet', {
|
|
62
|
+
artifactId: artifact.id,
|
|
63
|
+
title: artifact.title,
|
|
64
|
+
status: 'completed'
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Emit worksheet generation start notification
|
|
68
|
+
static async emitWorksheetGenerationStart(workspaceId) {
|
|
69
|
+
await this.emitTaskComplete(workspaceId, 'worksheet_generation_start', {});
|
|
70
|
+
}
|
|
71
|
+
// Emit new worksheet notification
|
|
72
|
+
static async emitWorksheetNew(workspaceId, worksheet) {
|
|
73
|
+
await this.emitTaskComplete(workspaceId, 'worksheet_new', { worksheet });
|
|
74
|
+
}
|
|
75
|
+
// Emit worksheet generation completion notification
|
|
76
|
+
static async emitWorksheetGenerationComplete(workspaceId, worksheet) {
|
|
77
|
+
await this.emitTaskComplete(workspaceId, 'worksheet_generation_complete', { worksheet });
|
|
78
|
+
}
|
|
79
|
+
// Emit podcast completion
|
|
80
|
+
static async emitPodcastComplete(workspaceId, artifact) {
|
|
81
|
+
await this.emitAnalysisComplete(workspaceId, 'podcast', {
|
|
82
|
+
artifactId: artifact.id,
|
|
83
|
+
title: artifact.title,
|
|
84
|
+
status: 'completed'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Emit overall analysis completion
|
|
88
|
+
static async emitOverallComplete(workspaceId, filename, artifacts) {
|
|
89
|
+
await this.emitTaskComplete(workspaceId, 'analysis_ended', {
|
|
90
|
+
filename,
|
|
91
|
+
artifacts,
|
|
92
|
+
timestamp: new Date().toISOString(),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Emit error notification
|
|
96
|
+
static async emitError(workspaceId, error, analysisType) {
|
|
97
|
+
const event = analysisType ? `${analysisType}_error` : 'analysis_error';
|
|
98
|
+
await this.emitTaskComplete(workspaceId, event, {
|
|
99
|
+
error,
|
|
100
|
+
analysisType,
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Emit analysis progress update (single event for all progress updates)
|
|
105
|
+
static async emitAnalysisProgress(workspaceId, progress) {
|
|
106
|
+
try {
|
|
107
|
+
const channel = `workspace_${workspaceId}`;
|
|
108
|
+
await pusher.trigger(channel, 'analysis_progress', progress);
|
|
109
|
+
logger.info(`📡 Analysis progress sent to ${channel}: ${progress.status}`);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logger.error('Pusher progress notification error:', 'PUSHER', { error });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Emit channel-specific events (for chat messages)
|
|
116
|
+
static async emitChannelEvent(channelId, event, data) {
|
|
117
|
+
try {
|
|
118
|
+
const channel = channelId; // Use channelId directly as channel name
|
|
119
|
+
const eventName = event;
|
|
120
|
+
await pusher.trigger(channel, eventName, data);
|
|
121
|
+
logger.info(`Pusher notification sent: ${eventName} to ${channel}`);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
logger.error('Pusher channel notification error:', 'PUSHER', { error });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Emit member joined notification
|
|
128
|
+
static async emitMemberJoined(workspaceId, member) {
|
|
129
|
+
await this.emitTaskComplete(workspaceId, 'member_joined', { member });
|
|
130
|
+
}
|
|
131
|
+
// Emit profile update notification
|
|
132
|
+
static async emitProfileUpdate(userId) {
|
|
133
|
+
await this.emitUserEvent(userId, 'profile_updated', {
|
|
134
|
+
userId,
|
|
135
|
+
timestamp: new Date().toISOString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Emit library (folders/workspaces) update notification
|
|
139
|
+
static async emitLibraryUpdate(userId) {
|
|
140
|
+
await this.emitUserEvent(userId, 'library_updated', {
|
|
141
|
+
userId,
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
static async emitNotificationNew(userId, data) {
|
|
146
|
+
await this.emitUserEvent(userId, 'notification_new', data);
|
|
147
|
+
}
|
|
148
|
+
static async emitNotificationReadState(userId, data) {
|
|
149
|
+
await this.emitUserEvent(userId, 'notification_read_state_changed', {
|
|
150
|
+
...data,
|
|
151
|
+
timestamp: data.timestamp ?? new Date().toISOString(),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// Emit payment success (top-up or subscription)
|
|
155
|
+
static async emitPaymentSuccess(userId, data) {
|
|
156
|
+
try {
|
|
157
|
+
const channel = `user_${userId}`;
|
|
158
|
+
const eventName = 'payment_success';
|
|
159
|
+
await pusher.trigger(channel, eventName, {
|
|
160
|
+
...data,
|
|
161
|
+
timestamp: new Date().toISOString(),
|
|
162
|
+
});
|
|
163
|
+
logger.info(`📡 Pusher payment success sent: ${eventName} to ${channel}`);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
logger.error('Pusher payment success error:', 'PUSHER', { error });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export default PusherService;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface RetryOptions {
|
|
2
|
+
/** Maximum number of attempts (default: 3) */
|
|
3
|
+
maxRetries?: number;
|
|
4
|
+
/** Base delay in ms for exponential backoff (default: 2000) */
|
|
5
|
+
baseDelayMs?: number;
|
|
6
|
+
/** Timeout per attempt in ms (default: 300000 = 5 minutes) */
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
/** Label for logging (optional) */
|
|
9
|
+
label?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Wraps an async function with retry logic and exponential backoff.
|
|
13
|
+
* Throws the last error if all attempts fail.
|
|
14
|
+
*/
|
|
15
|
+
export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { logger } from './logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps an async function with retry logic and exponential backoff.
|
|
4
|
+
* Throws the last error if all attempts fail.
|
|
5
|
+
*/
|
|
6
|
+
export async function withRetry(fn, options = {}) {
|
|
7
|
+
const { maxRetries = 3, baseDelayMs = 2000, timeoutMs = 300000, label = 'operation', } = options;
|
|
8
|
+
let lastError = null;
|
|
9
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
10
|
+
try {
|
|
11
|
+
logger.info(`[retry] ${label} attempt ${attempt}/${maxRetries}`);
|
|
12
|
+
// Create an AbortController for per-attempt timeout
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
15
|
+
try {
|
|
16
|
+
const result = await fn();
|
|
17
|
+
clearTimeout(timeoutId);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
clearTimeout(timeoutId);
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
27
|
+
logger.error(`[retry] ${label} attempt ${attempt} failed: ${lastError.message}`);
|
|
28
|
+
if (attempt < maxRetries) {
|
|
29
|
+
const delay = Math.pow(2, attempt) * baseDelayMs;
|
|
30
|
+
logger.info(`[retry] ${label} retrying in ${delay}ms...`);
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
logger.error(`[retry] ${label} all ${maxRetries} attempts failed`);
|
|
36
|
+
throw lastError;
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface UploadResult {
|
|
2
|
+
url: string;
|
|
3
|
+
signedUrl?: string;
|
|
4
|
+
objectKey: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function uploadToSupabase(fileBuffer: Buffer, fileName: string, contentType: string, makePublic?: boolean): Promise<UploadResult>;
|
|
7
|
+
export declare function generateSignedUrl(objectKey: string, expiresInHours?: number): Promise<string>;
|
|
8
|
+
export declare function deleteFromSupabase(objectKey: string): Promise<void>;
|
|
9
|
+
export declare function makeFilePublic(objectKey: string): Promise<void>;
|
|
10
|
+
export declare function makeFilePrivate(objectKey: string): Promise<void>;
|
|
11
|
+
export declare const supabaseClient: import("@supabase/supabase-js").SupabaseClient<any, "public", "public", any, any>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/server/lib/storage.ts
|
|
2
|
+
import { createClient } from '@supabase/supabase-js';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
// Initialize Supabase Storage
|
|
5
|
+
const supabaseUrl = process.env.SUPABASE_URL;
|
|
6
|
+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
7
|
+
if (!supabaseUrl || !supabaseServiceKey) {
|
|
8
|
+
throw new Error('Missing required Supabase environment variables: SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY');
|
|
9
|
+
}
|
|
10
|
+
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
|
11
|
+
const bucketName = process.env.SUPABASE_BUCKET || 'media';
|
|
12
|
+
export async function uploadToSupabase(fileBuffer, fileName, contentType, makePublic = false) {
|
|
13
|
+
const objectKey = `podcasts/${uuidv4()}_${fileName}`;
|
|
14
|
+
// Upload the file to Supabase Storage
|
|
15
|
+
const { data, error } = await supabase.storage
|
|
16
|
+
.from(bucketName)
|
|
17
|
+
.upload(objectKey, fileBuffer, {
|
|
18
|
+
contentType,
|
|
19
|
+
upsert: false,
|
|
20
|
+
});
|
|
21
|
+
if (error) {
|
|
22
|
+
throw new Error(`Failed to upload file to Supabase: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
const url = `${supabaseUrl}/storage/v1/object/public/${bucketName}/${objectKey}`;
|
|
25
|
+
// Generate signed URL for private files
|
|
26
|
+
let signedUrl;
|
|
27
|
+
if (!makePublic) {
|
|
28
|
+
const { data: signedUrlData, error: signedUrlError } = await supabase.storage
|
|
29
|
+
.from(bucketName)
|
|
30
|
+
.createSignedUrl(objectKey, 24 * 60 * 60); // 24 hours
|
|
31
|
+
if (signedUrlError) {
|
|
32
|
+
throw new Error(`Failed to generate signed URL: ${signedUrlError.message}`);
|
|
33
|
+
}
|
|
34
|
+
signedUrl = signedUrlData.signedUrl;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
url,
|
|
38
|
+
signedUrl,
|
|
39
|
+
objectKey,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function generateSignedUrl(objectKey, expiresInHours = 24) {
|
|
43
|
+
const { data, error } = await supabase.storage
|
|
44
|
+
.from(bucketName)
|
|
45
|
+
.createSignedUrl(objectKey, expiresInHours * 60 * 60);
|
|
46
|
+
if (error) {
|
|
47
|
+
throw new Error(`Failed to generate signed URL: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
return data.signedUrl;
|
|
50
|
+
}
|
|
51
|
+
export async function deleteFromSupabase(objectKey) {
|
|
52
|
+
const { error } = await supabase.storage
|
|
53
|
+
.from(bucketName)
|
|
54
|
+
.remove([objectKey]);
|
|
55
|
+
if (error) {
|
|
56
|
+
throw new Error(`Failed to delete file from Supabase: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function makeFilePublic(objectKey) {
|
|
60
|
+
// In Supabase, files are public by default when uploaded to public buckets
|
|
61
|
+
// For private buckets, you would need to update the bucket policy
|
|
62
|
+
// This function is kept for compatibility but may not be needed
|
|
63
|
+
// File is already public in Supabase Storage
|
|
64
|
+
}
|
|
65
|
+
export async function makeFilePrivate(objectKey) {
|
|
66
|
+
// In Supabase, you would need to update the bucket policy to make files private
|
|
67
|
+
// This function is kept for compatibility but may not be needed
|
|
68
|
+
// File privacy is controlled by bucket policy in Supabase Storage
|
|
69
|
+
}
|
|
70
|
+
// Export supabase client for direct access if needed
|
|
71
|
+
export const supabaseClient = supabase;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Stripe from 'stripe';
|
|
2
|
+
export declare const stripe: Stripe | null;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Stripe customer for a user.
|
|
5
|
+
*
|
|
6
|
+
* @param email - User's email
|
|
7
|
+
* @param name - User's name
|
|
8
|
+
* @returns The Stripe customer ID
|
|
9
|
+
*/
|
|
10
|
+
export declare function createStripeCustomer(email: string, name?: string): Promise<string | null>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Stripe from 'stripe';
|
|
2
|
+
import { env } from './env.js';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
if (!env.STRIPE_SECRET_KEY) {
|
|
5
|
+
logger.warn('STRIPE_SECRET_KEY is not set. Stripe functionality will be disabled.', 'STRIPE');
|
|
6
|
+
}
|
|
7
|
+
export const stripe = env.STRIPE_SECRET_KEY
|
|
8
|
+
? new Stripe(env.STRIPE_SECRET_KEY, {
|
|
9
|
+
apiVersion: '2026-02-25.clover',
|
|
10
|
+
})
|
|
11
|
+
: null;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Stripe customer for a user.
|
|
14
|
+
*
|
|
15
|
+
* @param email - User's email
|
|
16
|
+
* @param name - User's name
|
|
17
|
+
* @returns The Stripe customer ID
|
|
18
|
+
*/
|
|
19
|
+
export async function createStripeCustomer(email, name) {
|
|
20
|
+
if (!stripe) {
|
|
21
|
+
logger.error('Stripe is not initialized. Cannot create customer.', 'STRIPE');
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const customer = await stripe.customers.create({
|
|
26
|
+
email,
|
|
27
|
+
name,
|
|
28
|
+
});
|
|
29
|
+
logger.info(`Stripe customer created for ${email}: ${customer.id}`, 'STRIPE');
|
|
30
|
+
return customer.id;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
logger.error(`Failed to create Stripe customer for ${email}: ${error.message}`, 'STRIPE');
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Common validation schemas
|
|
4
|
+
*/
|
|
5
|
+
export declare const commonSchemas: {
|
|
6
|
+
id: z.ZodString;
|
|
7
|
+
email: z.ZodString;
|
|
8
|
+
url: z.ZodString;
|
|
9
|
+
pagination: z.ZodObject<{
|
|
10
|
+
page: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
search: z.ZodObject<{
|
|
14
|
+
query: z.ZodString;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Enums for type safety
|
|
19
|
+
*/
|
|
20
|
+
export declare const ArtifactType: z.ZodEnum<{
|
|
21
|
+
WORKSHEET: "WORKSHEET";
|
|
22
|
+
STUDY_GUIDE: "STUDY_GUIDE";
|
|
23
|
+
FLASHCARD_SET: "FLASHCARD_SET";
|
|
24
|
+
MEETING_SUMMARY: "MEETING_SUMMARY";
|
|
25
|
+
PODCAST_EPISODE: "PODCAST_EPISODE";
|
|
26
|
+
}>;
|
|
27
|
+
export declare const Difficulty: z.ZodEnum<{
|
|
28
|
+
EASY: "EASY";
|
|
29
|
+
MEDIUM: "MEDIUM";
|
|
30
|
+
HARD: "HARD";
|
|
31
|
+
}>;
|
|
32
|
+
export declare const QuestionType: z.ZodEnum<{
|
|
33
|
+
MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
|
|
34
|
+
TEXT: "TEXT";
|
|
35
|
+
NUMERIC: "NUMERIC";
|
|
36
|
+
TRUE_FALSE: "TRUE_FALSE";
|
|
37
|
+
MATCHING: "MATCHING";
|
|
38
|
+
FILL_IN_THE_BLANK: "FILL_IN_THE_BLANK";
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Validation helper that throws ValidationError
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateSchema<T extends z.ZodType>(schema: T, data: unknown): z.infer<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Sanitize string inputs
|
|
46
|
+
*/
|
|
47
|
+
export declare function sanitizeString(input: string, maxLength?: number): string;
|
|
48
|
+
/**
|
|
49
|
+
* Validate ownership
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateOwnership(ownerId: string, userId: string): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ValidationError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Common validation schemas
|
|
5
|
+
*/
|
|
6
|
+
export const commonSchemas = {
|
|
7
|
+
id: z.string().cuid(),
|
|
8
|
+
email: z.string().email(),
|
|
9
|
+
url: z.string().url(),
|
|
10
|
+
pagination: z.object({
|
|
11
|
+
page: z.number().int().positive().default(1),
|
|
12
|
+
limit: z.number().int().positive().max(100).default(20),
|
|
13
|
+
}),
|
|
14
|
+
search: z.object({
|
|
15
|
+
query: z.string().min(1).max(200),
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Enums for type safety
|
|
20
|
+
*/
|
|
21
|
+
export const ArtifactType = z.enum([
|
|
22
|
+
'STUDY_GUIDE',
|
|
23
|
+
'FLASHCARD_SET',
|
|
24
|
+
'WORKSHEET',
|
|
25
|
+
'MEETING_SUMMARY',
|
|
26
|
+
'PODCAST_EPISODE',
|
|
27
|
+
]);
|
|
28
|
+
export const Difficulty = z.enum(['EASY', 'MEDIUM', 'HARD']);
|
|
29
|
+
export const QuestionType = z.enum([
|
|
30
|
+
'MULTIPLE_CHOICE',
|
|
31
|
+
'TEXT',
|
|
32
|
+
'NUMERIC',
|
|
33
|
+
'TRUE_FALSE',
|
|
34
|
+
'MATCHING',
|
|
35
|
+
'FILL_IN_THE_BLANK',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Validation helper that throws ValidationError
|
|
39
|
+
*/
|
|
40
|
+
export function validateSchema(schema, data) {
|
|
41
|
+
const result = schema.safeParse(data);
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
const errors = result.error.message;
|
|
44
|
+
throw new ValidationError(`Validation failed: ${errors}`);
|
|
45
|
+
}
|
|
46
|
+
return result.data;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sanitize string inputs
|
|
50
|
+
*/
|
|
51
|
+
export function sanitizeString(input, maxLength = 10000) {
|
|
52
|
+
return input
|
|
53
|
+
.trim()
|
|
54
|
+
.slice(0, maxLength)
|
|
55
|
+
.replace(/[<>]/g, ''); // Basic XSS prevention
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate ownership
|
|
59
|
+
*/
|
|
60
|
+
export function validateOwnership(ownerId, userId) {
|
|
61
|
+
if (ownerId !== userId) {
|
|
62
|
+
throw new ValidationError('You do not have permission to access this resource');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Meta.kind value for the private, auto-managed KB attached to each workspace. */
|
|
2
|
+
export const WORKSPACE_KB_KIND = 'workspace';
|
|
3
|
+
export function isWorkspaceManagedKb(kb) {
|
|
4
|
+
if (!kb.meta || typeof kb.meta !== 'object')
|
|
5
|
+
return false;
|
|
6
|
+
return kb.meta.kind === WORKSPACE_KB_KIND;
|
|
7
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ArtifactType, Prisma, PrismaClient } from '@prisma/client';
|
|
2
|
+
import { BaseRepository } from './base.repository.js';
|
|
3
|
+
export declare class ArtifactRepository extends BaseRepository {
|
|
4
|
+
constructor(db: PrismaClient);
|
|
5
|
+
/**
|
|
6
|
+
* Fetch an artifact if the user can access the containing workspace
|
|
7
|
+
* (owner or member).
|
|
8
|
+
*/
|
|
9
|
+
findByIdAccessible(artifactId: string, userId: string, include?: Prisma.ArtifactInclude): Prisma.Prisma__ArtifactClient<{
|
|
10
|
+
id: string;
|
|
11
|
+
createdAt: Date;
|
|
12
|
+
updatedAt: Date;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string | null;
|
|
15
|
+
createdById: string | null;
|
|
16
|
+
type: ArtifactType;
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
difficulty: import("@prisma/client").Difficulty | null;
|
|
19
|
+
estimatedTime: string | null;
|
|
20
|
+
isArchived: boolean;
|
|
21
|
+
generating: boolean;
|
|
22
|
+
generatingMetadata: import("@prisma/client/runtime/client").JsonValue | null;
|
|
23
|
+
worksheetConfig: import("@prisma/client/runtime/client").JsonValue | null;
|
|
24
|
+
imageObjectKey: string | null;
|
|
25
|
+
} | null, null, import("@prisma/client/runtime/client").DefaultArgs, {
|
|
26
|
+
omit: Prisma.GlobalOmitConfig | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
findByIdAndTypeAccessible(artifactId: string, type: ArtifactType, userId: string, include?: Prisma.ArtifactInclude): Prisma.Prisma__ArtifactClient<{
|
|
29
|
+
id: string;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
updatedAt: Date;
|
|
32
|
+
title: string;
|
|
33
|
+
description: string | null;
|
|
34
|
+
createdById: string | null;
|
|
35
|
+
type: ArtifactType;
|
|
36
|
+
workspaceId: string;
|
|
37
|
+
difficulty: import("@prisma/client").Difficulty | null;
|
|
38
|
+
estimatedTime: string | null;
|
|
39
|
+
isArchived: boolean;
|
|
40
|
+
generating: boolean;
|
|
41
|
+
generatingMetadata: import("@prisma/client/runtime/client").JsonValue | null;
|
|
42
|
+
worksheetConfig: import("@prisma/client/runtime/client").JsonValue | null;
|
|
43
|
+
imageObjectKey: string | null;
|
|
44
|
+
} | null, null, import("@prisma/client/runtime/client").DefaultArgs, {
|
|
45
|
+
omit: Prisma.GlobalOmitConfig | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
listByWorkspaceAndType(workspaceId: string, type: ArtifactType, userId: string, args?: Omit<Prisma.ArtifactFindManyArgs, 'where'>): Prisma.PrismaPromise<{
|
|
48
|
+
id: string;
|
|
49
|
+
createdAt: Date;
|
|
50
|
+
updatedAt: Date;
|
|
51
|
+
title: string;
|
|
52
|
+
description: string | null;
|
|
53
|
+
createdById: string | null;
|
|
54
|
+
type: ArtifactType;
|
|
55
|
+
workspaceId: string;
|
|
56
|
+
difficulty: import("@prisma/client").Difficulty | null;
|
|
57
|
+
estimatedTime: string | null;
|
|
58
|
+
isArchived: boolean;
|
|
59
|
+
generating: boolean;
|
|
60
|
+
generatingMetadata: import("@prisma/client/runtime/client").JsonValue | null;
|
|
61
|
+
worksheetConfig: import("@prisma/client/runtime/client").JsonValue | null;
|
|
62
|
+
imageObjectKey: string | null;
|
|
63
|
+
}[]>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BaseRepository } from './base.repository.js';
|
|
2
|
+
import { workspaceAccessWhere } from './workspace.repository.js';
|
|
3
|
+
export class ArtifactRepository extends BaseRepository {
|
|
4
|
+
constructor(db) {
|
|
5
|
+
super(db);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Fetch an artifact if the user can access the containing workspace
|
|
9
|
+
* (owner or member).
|
|
10
|
+
*/
|
|
11
|
+
findByIdAccessible(artifactId, userId, include) {
|
|
12
|
+
return this.db.artifact.findFirst({
|
|
13
|
+
where: {
|
|
14
|
+
id: artifactId,
|
|
15
|
+
workspace: workspaceAccessWhere(userId),
|
|
16
|
+
},
|
|
17
|
+
include,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
findByIdAndTypeAccessible(artifactId, type, userId, include) {
|
|
21
|
+
return this.db.artifact.findFirst({
|
|
22
|
+
where: {
|
|
23
|
+
id: artifactId,
|
|
24
|
+
type,
|
|
25
|
+
workspace: workspaceAccessWhere(userId),
|
|
26
|
+
},
|
|
27
|
+
include,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
listByWorkspaceAndType(workspaceId, type, userId, args) {
|
|
31
|
+
return this.db.artifact.findMany({
|
|
32
|
+
...args,
|
|
33
|
+
where: {
|
|
34
|
+
workspaceId,
|
|
35
|
+
type,
|
|
36
|
+
workspace: workspaceAccessWhere(userId),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PrismaClient } from '@prisma/client';
|
|
2
|
+
import { logger } from '../lib/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Shared base class for repositories.
|
|
5
|
+
*
|
|
6
|
+
* Repositories own data access. They wrap a Prisma client and expose
|
|
7
|
+
* intent-revealing methods (e.g. `findAccessibleById(workspaceId, userId)`)
|
|
8
|
+
* so that the shape of `where` clauses doesn't leak into services/routers.
|
|
9
|
+
*/
|
|
10
|
+
export declare abstract class BaseRepository {
|
|
11
|
+
protected readonly db: PrismaClient;
|
|
12
|
+
protected readonly logger: typeof logger;
|
|
13
|
+
constructor(db: PrismaClient);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { logger } from '../lib/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared base class for repositories.
|
|
4
|
+
*
|
|
5
|
+
* Repositories own data access. They wrap a Prisma client and expose
|
|
6
|
+
* intent-revealing methods (e.g. `findAccessibleById(workspaceId, userId)`)
|
|
7
|
+
* so that the shape of `where` clauses doesn't leak into services/routers.
|
|
8
|
+
*/
|
|
9
|
+
export class BaseRepository {
|
|
10
|
+
constructor(db) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
}
|