@goscribe/server 1.1.7 → 1.3.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 +43 -0
- package/check-difficulty.cjs +14 -0
- package/check-questions.cjs +14 -0
- package/db-summary.cjs +22 -0
- package/dist/routers/auth.js +1 -1
- package/mcq-test.cjs +36 -0
- package/package.json +10 -2
- package/prisma/migrations/20260413143206_init/migration.sql +873 -0
- package/prisma/schema.prisma +485 -292
- package/src/context.ts +4 -1
- package/src/lib/activity_human_description.test.ts +28 -0
- package/src/lib/activity_human_description.ts +239 -0
- package/src/lib/activity_log_service.test.ts +37 -0
- package/src/lib/activity_log_service.ts +353 -0
- package/src/lib/ai-session.ts +194 -112
- package/src/lib/constants.ts +14 -0
- package/src/lib/email.ts +230 -0
- package/src/lib/env.ts +23 -6
- package/src/lib/inference.ts +3 -3
- package/src/lib/logger.ts +26 -9
- package/src/lib/notification-service.test.ts +106 -0
- package/src/lib/notification-service.ts +677 -0
- package/src/lib/prisma.ts +6 -1
- package/src/lib/pusher.ts +90 -6
- package/src/lib/retry.ts +61 -0
- package/src/lib/storage.ts +2 -2
- package/src/lib/stripe.ts +39 -0
- package/src/lib/subscription_service.ts +722 -0
- package/src/lib/usage_service.ts +74 -0
- package/src/lib/worksheet-generation.test.ts +31 -0
- package/src/lib/worksheet-generation.ts +139 -0
- package/src/lib/workspace-access.ts +13 -0
- package/src/routers/_app.ts +11 -0
- package/src/routers/admin.ts +710 -0
- package/src/routers/annotations.ts +227 -0
- package/src/routers/auth.ts +432 -33
- package/src/routers/copilot.ts +719 -0
- package/src/routers/flashcards.ts +207 -80
- package/src/routers/members.ts +280 -80
- package/src/routers/notifications.ts +142 -0
- package/src/routers/payment.ts +448 -0
- package/src/routers/podcast.ts +133 -108
- package/src/routers/studyguide.ts +80 -74
- package/src/routers/worksheets.ts +300 -80
- package/src/routers/workspace.ts +538 -328
- package/src/scripts/purge-deleted-users.ts +167 -0
- package/src/server.ts +140 -12
- package/src/services/flashcard-progress.service.ts +52 -43
- package/src/trpc.ts +184 -5
- package/test-generate.js +30 -0
- package/test-ratio.cjs +9 -0
- package/zod-test.cjs +22 -0
- package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
- package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
- package/prisma/seed.mjs +0 -135
- package/src/routers/meetingsummary.ts +0 -416
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
// import { z } from 'zod';
|
|
2
|
-
// import { TRPCError } from '@trpc/server';
|
|
3
|
-
// import { router, authedProcedure } from '../trpc.js';
|
|
4
|
-
// import OpenAI from 'openai';
|
|
5
|
-
// import fs from 'fs';
|
|
6
|
-
// import path from 'path';
|
|
7
|
-
// import { v4 as uuidv4 } from 'uuid';
|
|
8
|
-
|
|
9
|
-
// // Prisma enum values mapped manually to avoid type import issues in ESM
|
|
10
|
-
// const ArtifactType = {
|
|
11
|
-
// STUDY_GUIDE: 'STUDY_GUIDE',
|
|
12
|
-
// FLASHCARD_SET: 'FLASHCARD_SET',
|
|
13
|
-
// WORKSHEET: 'WORKSHEET',
|
|
14
|
-
// MEETING_SUMMARY: 'MEETING_SUMMARY',
|
|
15
|
-
// PODCAST_EPISODE: 'PODCAST_EPISODE',
|
|
16
|
-
// } as const;
|
|
17
|
-
|
|
18
|
-
// // Initialize OpenAI client
|
|
19
|
-
// const openai = new OpenAI({
|
|
20
|
-
// apiKey: process.env.OPENAI_API_KEY,
|
|
21
|
-
// });
|
|
22
|
-
|
|
23
|
-
// // Meeting summary schema for structured data
|
|
24
|
-
// const meetingSchema = z.object({
|
|
25
|
-
// title: z.string(),
|
|
26
|
-
// participants: z.array(z.string()),
|
|
27
|
-
// date: z.string(),
|
|
28
|
-
// duration: z.string().optional(),
|
|
29
|
-
// agenda: z.array(z.string()).optional(),
|
|
30
|
-
// transcript: z.string().optional(),
|
|
31
|
-
// notes: z.string().optional(),
|
|
32
|
-
// });
|
|
33
|
-
|
|
34
|
-
// export const meetingSummarize = router({
|
|
35
|
-
// // List all meeting summaries for a workspace
|
|
36
|
-
// listSummaries: authedProcedure
|
|
37
|
-
// .input(z.object({ workspaceId: z.string() }))
|
|
38
|
-
// .query(async ({ ctx, input }) => {
|
|
39
|
-
// const workspace = await ctx.db.workspace.findFirst({
|
|
40
|
-
// where: { id: input.workspaceId, ownerId: ctx.session.user.id },
|
|
41
|
-
// });
|
|
42
|
-
// if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
43
|
-
|
|
44
|
-
// return ctx.db.artifact.findMany({
|
|
45
|
-
// where: {
|
|
46
|
-
// workspaceId: input.workspaceId,
|
|
47
|
-
// type: ArtifactType.MEETING_SUMMARY
|
|
48
|
-
// },
|
|
49
|
-
// include: {
|
|
50
|
-
// versions: {
|
|
51
|
-
// orderBy: { version: 'desc' },
|
|
52
|
-
// take: 1, // Get only the latest version
|
|
53
|
-
// },
|
|
54
|
-
// },
|
|
55
|
-
// orderBy: { updatedAt: 'desc' },
|
|
56
|
-
// });
|
|
57
|
-
// }),
|
|
58
|
-
|
|
59
|
-
// // Get a specific meeting summary
|
|
60
|
-
// getSummary: authedProcedure
|
|
61
|
-
// .input(z.object({ summaryId: z.string() }))
|
|
62
|
-
// .query(async ({ ctx, input }) => {
|
|
63
|
-
// const summary = await ctx.db.artifact.findFirst({
|
|
64
|
-
// where: {
|
|
65
|
-
// id: input.summaryId,
|
|
66
|
-
// type: ArtifactType.MEETING_SUMMARY,
|
|
67
|
-
// workspace: { ownerId: ctx.session.user.id }
|
|
68
|
-
// },
|
|
69
|
-
// include: {
|
|
70
|
-
// versions: {
|
|
71
|
-
// orderBy: { version: 'desc' },
|
|
72
|
-
// take: 1,
|
|
73
|
-
// },
|
|
74
|
-
// },
|
|
75
|
-
// });
|
|
76
|
-
// if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
77
|
-
// return summary;
|
|
78
|
-
// }),
|
|
79
|
-
|
|
80
|
-
// // Upload and process audio/video file
|
|
81
|
-
// uploadFile: authedProcedure
|
|
82
|
-
// .input(z.object({
|
|
83
|
-
// workspaceId: z.string(),
|
|
84
|
-
// fileName: z.string(),
|
|
85
|
-
// fileBuffer: z.string(), // Base64 encoded file
|
|
86
|
-
// mimeType: z.string(),
|
|
87
|
-
// title: z.string().optional(),
|
|
88
|
-
// }))
|
|
89
|
-
// .mutation(async ({ ctx, input }) => {
|
|
90
|
-
// const workspace = await ctx.db.workspace.findFirst({
|
|
91
|
-
// where: { id: input.workspaceId, ownerId: ctx.session.user.id },
|
|
92
|
-
// });
|
|
93
|
-
// if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
94
|
-
|
|
95
|
-
// // Validate file type
|
|
96
|
-
// const allowedTypes = ['audio/mpeg', 'audio/mp3', 'video/mp4', 'audio/wav', 'audio/m4a'];
|
|
97
|
-
// if (!allowedTypes.includes(input.mimeType)) {
|
|
98
|
-
// throw new TRPCError({
|
|
99
|
-
// code: 'BAD_REQUEST',
|
|
100
|
-
// message: 'Unsupported file type. Please upload MP3, MP4, WAV, or M4A files.'
|
|
101
|
-
// });
|
|
102
|
-
// }
|
|
103
|
-
|
|
104
|
-
// try {
|
|
105
|
-
// // Create temporary file
|
|
106
|
-
// const tempDir = path.join(process.cwd(), 'temp');
|
|
107
|
-
// if (!fs.existsSync(tempDir)) {
|
|
108
|
-
// fs.mkdirSync(tempDir, { recursive: true });
|
|
109
|
-
// }
|
|
110
|
-
|
|
111
|
-
// const fileId = uuidv4();
|
|
112
|
-
// const fileExtension = path.extname(input.fileName);
|
|
113
|
-
// const tempFilePath = path.join(tempDir, `${fileId}${fileExtension}`);
|
|
114
|
-
|
|
115
|
-
// // Write buffer to temporary file
|
|
116
|
-
// const fileBuffer = Buffer.from(input.fileBuffer, 'base64');
|
|
117
|
-
// fs.writeFileSync(tempFilePath, fileBuffer);
|
|
118
|
-
|
|
119
|
-
// // Transcribe audio using OpenAI Whisper
|
|
120
|
-
// const transcript = await openai.audio.transcriptions.create({
|
|
121
|
-
// file: fs.createReadStream(tempFilePath),
|
|
122
|
-
// model: 'whisper-1',
|
|
123
|
-
// response_format: 'text',
|
|
124
|
-
// });
|
|
125
|
-
|
|
126
|
-
// // Generate meeting summary using GPT
|
|
127
|
-
// const summaryResponse = await openai.chat.completions.create({
|
|
128
|
-
// model: 'gpt-4-turbo-preview',
|
|
129
|
-
// messages: [
|
|
130
|
-
// {
|
|
131
|
-
// role: 'system',
|
|
132
|
-
// content: `You are a meeting summarizer. Given a meeting transcript, create a comprehensive summary that includes:
|
|
133
|
-
// 1. Meeting title/topic
|
|
134
|
-
// 2. Key participants mentioned
|
|
135
|
-
// 3. Main discussion points
|
|
136
|
-
// 4. Action items
|
|
137
|
-
// 5. Decisions made
|
|
138
|
-
// 6. Next steps
|
|
139
|
-
// Format the response as JSON with the following structure:
|
|
140
|
-
// {
|
|
141
|
-
// "title": "Meeting title",
|
|
142
|
-
// "participants": ["participant1", "participant2"],
|
|
143
|
-
// "keyPoints": ["point1", "point2"],
|
|
144
|
-
// "actionItems": ["action1", "action2"],
|
|
145
|
-
// "decisions": ["decision1", "decision2"],
|
|
146
|
-
// "nextSteps": ["step1", "step2"],
|
|
147
|
-
// "summary": "Overall meeting summary"
|
|
148
|
-
// }`
|
|
149
|
-
// },
|
|
150
|
-
// {
|
|
151
|
-
// role: 'user',
|
|
152
|
-
// content: `Please summarize this meeting transcript: ${transcript}`
|
|
153
|
-
// }
|
|
154
|
-
// ],
|
|
155
|
-
// });
|
|
156
|
-
|
|
157
|
-
// let summaryData;
|
|
158
|
-
// try {
|
|
159
|
-
// summaryData = JSON.parse(summaryResponse.choices[0]?.message?.content || '{}');
|
|
160
|
-
// } catch (parseError) {
|
|
161
|
-
// // Fallback if JSON parsing fails
|
|
162
|
-
// summaryData = {
|
|
163
|
-
// title: input.title || 'Meeting Summary',
|
|
164
|
-
// participants: [],
|
|
165
|
-
// keyPoints: [],
|
|
166
|
-
// actionItems: [],
|
|
167
|
-
// decisions: [],
|
|
168
|
-
// nextSteps: [],
|
|
169
|
-
// summary: summaryResponse.choices[0]?.message?.content || 'Unable to generate summary'
|
|
170
|
-
// };
|
|
171
|
-
// }
|
|
172
|
-
|
|
173
|
-
// // Create artifact in database
|
|
174
|
-
// const artifact = await ctx.db.artifact.create({
|
|
175
|
-
// data: {
|
|
176
|
-
// workspaceId: input.workspaceId,
|
|
177
|
-
// type: ArtifactType.MEETING_SUMMARY,
|
|
178
|
-
// title: summaryData.title || input.title || 'Meeting Summary',
|
|
179
|
-
// content: JSON.stringify({
|
|
180
|
-
// originalFileName: input.fileName,
|
|
181
|
-
// transcript: transcript,
|
|
182
|
-
// ...summaryData
|
|
183
|
-
// }),
|
|
184
|
-
// },
|
|
185
|
-
// });
|
|
186
|
-
|
|
187
|
-
// // Create initial version
|
|
188
|
-
// await ctx.db.artifactVersion.create({
|
|
189
|
-
// data: {
|
|
190
|
-
// artifactId: artifact.id,
|
|
191
|
-
// version: 1,
|
|
192
|
-
// content: artifact.content,
|
|
193
|
-
// },
|
|
194
|
-
// });
|
|
195
|
-
|
|
196
|
-
// // Clean up temporary file
|
|
197
|
-
// fs.unlinkSync(tempFilePath);
|
|
198
|
-
|
|
199
|
-
// return {
|
|
200
|
-
// id: artifact.id,
|
|
201
|
-
// title: artifact.title,
|
|
202
|
-
// summary: summaryData,
|
|
203
|
-
// transcript: transcript,
|
|
204
|
-
// };
|
|
205
|
-
|
|
206
|
-
// } catch (error) {
|
|
207
|
-
// console.error('Error processing meeting file:', error);
|
|
208
|
-
// throw new TRPCError({
|
|
209
|
-
// code: 'INTERNAL_SERVER_ERROR',
|
|
210
|
-
// message: 'Failed to process meeting file'
|
|
211
|
-
// });
|
|
212
|
-
// }
|
|
213
|
-
// }),
|
|
214
|
-
|
|
215
|
-
// // Process meeting data from schema
|
|
216
|
-
// processSchema: authedProcedure
|
|
217
|
-
// .input(z.object({
|
|
218
|
-
// workspaceId: z.string(),
|
|
219
|
-
// meetingData: meetingSchema,
|
|
220
|
-
// }))
|
|
221
|
-
// .mutation(async ({ ctx, input }) => {
|
|
222
|
-
// const workspace = await ctx.db.workspace.findFirst({
|
|
223
|
-
// where: { id: input.workspaceId, ownerId: ctx.session.user.id },
|
|
224
|
-
// });
|
|
225
|
-
// if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
226
|
-
|
|
227
|
-
// try {
|
|
228
|
-
// // Create content for AI processing
|
|
229
|
-
// const meetingContent = `
|
|
230
|
-
// Meeting Title: ${input.meetingData.title}
|
|
231
|
-
// Participants: ${input.meetingData.participants.join(', ')}
|
|
232
|
-
// Date: ${input.meetingData.date}
|
|
233
|
-
// Duration: ${input.meetingData.duration || 'Not specified'}
|
|
234
|
-
// Agenda: ${input.meetingData.agenda?.join(', ') || 'Not provided'}
|
|
235
|
-
// Notes: ${input.meetingData.notes || 'Not provided'}
|
|
236
|
-
// Transcript: ${input.meetingData.transcript || 'Not provided'}
|
|
237
|
-
// `;
|
|
238
|
-
|
|
239
|
-
// // Generate enhanced summary using GPT
|
|
240
|
-
// const summaryResponse = await openai.chat.completions.create({
|
|
241
|
-
// model: 'gpt-4-turbo-preview',
|
|
242
|
-
// messages: [
|
|
243
|
-
// {
|
|
244
|
-
// role: 'system',
|
|
245
|
-
// content: `You are a meeting summarizer. Given meeting information, create a comprehensive summary that includes:
|
|
246
|
-
// 1. Key discussion points
|
|
247
|
-
// 2. Action items
|
|
248
|
-
// 3. Decisions made
|
|
249
|
-
// 4. Next steps
|
|
250
|
-
// 5. Important insights
|
|
251
|
-
// Format the response as JSON with the following structure:
|
|
252
|
-
// {
|
|
253
|
-
// "keyPoints": ["point1", "point2"],
|
|
254
|
-
// "actionItems": ["action1", "action2"],
|
|
255
|
-
// "decisions": ["decision1", "decision2"],
|
|
256
|
-
// "nextSteps": ["step1", "step2"],
|
|
257
|
-
// "insights": ["insight1", "insight2"],
|
|
258
|
-
// "summary": "Overall meeting summary"
|
|
259
|
-
// }`
|
|
260
|
-
// },
|
|
261
|
-
// {
|
|
262
|
-
// role: 'user',
|
|
263
|
-
// content: `Please analyze and summarize this meeting information: ${meetingContent}`
|
|
264
|
-
// }
|
|
265
|
-
// ],
|
|
266
|
-
// });
|
|
267
|
-
|
|
268
|
-
// let summaryData;
|
|
269
|
-
// try {
|
|
270
|
-
// summaryData = JSON.parse(summaryResponse.choices[0]?.message?.content || '{}');
|
|
271
|
-
// } catch (parseError) {
|
|
272
|
-
// // Fallback if JSON parsing fails
|
|
273
|
-
// summaryData = {
|
|
274
|
-
// keyPoints: [],
|
|
275
|
-
// actionItems: [],
|
|
276
|
-
// decisions: [],
|
|
277
|
-
// nextSteps: [],
|
|
278
|
-
// insights: [],
|
|
279
|
-
// summary: summaryResponse.choices[0]?.message?.content || 'Unable to generate summary'
|
|
280
|
-
// };
|
|
281
|
-
// }
|
|
282
|
-
|
|
283
|
-
// // Create artifact in database
|
|
284
|
-
// const artifact = await ctx.db.artifact.create({
|
|
285
|
-
// data: {
|
|
286
|
-
// workspaceId: input.workspaceId,
|
|
287
|
-
// type: ArtifactType.MEETING_SUMMARY,
|
|
288
|
-
// title: input.meetingData.title,
|
|
289
|
-
// content: JSON.stringify({
|
|
290
|
-
// originalData: input.meetingData,
|
|
291
|
-
// ...summaryData
|
|
292
|
-
// }),
|
|
293
|
-
// },
|
|
294
|
-
// });
|
|
295
|
-
|
|
296
|
-
// // Create initial version
|
|
297
|
-
// await ctx.db.artifactVersion.create({
|
|
298
|
-
// data: {
|
|
299
|
-
// artifactId: artifact.id,
|
|
300
|
-
// version: 1,
|
|
301
|
-
// content: artifact.content,
|
|
302
|
-
// },
|
|
303
|
-
// });
|
|
304
|
-
|
|
305
|
-
// return {
|
|
306
|
-
// id: artifact.id,
|
|
307
|
-
// title: artifact.title,
|
|
308
|
-
// summary: summaryData,
|
|
309
|
-
// originalData: input.meetingData,
|
|
310
|
-
// };
|
|
311
|
-
|
|
312
|
-
// } catch (error) {
|
|
313
|
-
// console.error('Error processing meeting schema:', error);
|
|
314
|
-
// throw new TRPCError({
|
|
315
|
-
// code: 'INTERNAL_SERVER_ERROR',
|
|
316
|
-
// message: 'Failed to process meeting data'
|
|
317
|
-
// });
|
|
318
|
-
// }
|
|
319
|
-
// }),
|
|
320
|
-
|
|
321
|
-
// // Update an existing meeting summary
|
|
322
|
-
// updateSummary: authedProcedure
|
|
323
|
-
// .input(z.object({
|
|
324
|
-
// summaryId: z.string(),
|
|
325
|
-
// title: z.string().optional(),
|
|
326
|
-
// content: z.string().optional(),
|
|
327
|
-
// }))
|
|
328
|
-
// .mutation(async ({ ctx, input }) => {
|
|
329
|
-
// const summary = await ctx.db.artifact.findFirst({
|
|
330
|
-
// where: {
|
|
331
|
-
// id: input.summaryId,
|
|
332
|
-
// type: ArtifactType.MEETING_SUMMARY,
|
|
333
|
-
// workspace: { ownerId: ctx.session.user.id }
|
|
334
|
-
// },
|
|
335
|
-
// include: {
|
|
336
|
-
// versions: {
|
|
337
|
-
// orderBy: { version: 'desc' },
|
|
338
|
-
// take: 1,
|
|
339
|
-
// },
|
|
340
|
-
// },
|
|
341
|
-
// });
|
|
342
|
-
|
|
343
|
-
// if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
344
|
-
|
|
345
|
-
// // Update artifact
|
|
346
|
-
// const updatedArtifact = await ctx.db.artifact.update({
|
|
347
|
-
// where: { id: input.summaryId },
|
|
348
|
-
// data: {
|
|
349
|
-
// title: input.title ?? summary.title,
|
|
350
|
-
// content: input.content ?? summary.content,
|
|
351
|
-
// updatedAt: new Date(),
|
|
352
|
-
// },
|
|
353
|
-
// });
|
|
354
|
-
|
|
355
|
-
// // Create new version if content changed
|
|
356
|
-
// if (input.content && input.content !== summary.content) {
|
|
357
|
-
// const latestVersion = summary.versions[0]?.version || 0;
|
|
358
|
-
// await ctx.db.artifactVersion.create({
|
|
359
|
-
// data: {
|
|
360
|
-
// artifactId: input.summaryId,
|
|
361
|
-
// version: latestVersion + 1,
|
|
362
|
-
// content: input.content,
|
|
363
|
-
// },
|
|
364
|
-
// });
|
|
365
|
-
// }
|
|
366
|
-
|
|
367
|
-
// return updatedArtifact;
|
|
368
|
-
// }),
|
|
369
|
-
|
|
370
|
-
// // Delete a meeting summary
|
|
371
|
-
// deleteSummary: authedProcedure
|
|
372
|
-
// .input(z.object({ summaryId: z.string() }))
|
|
373
|
-
// .mutation(async ({ ctx, input }) => {
|
|
374
|
-
// const summary = await ctx.db.artifact.findFirst({
|
|
375
|
-
// where: {
|
|
376
|
-
// id: input.summaryId,
|
|
377
|
-
// type: ArtifactType.MEETING_SUMMARY,
|
|
378
|
-
// workspace: { ownerId: ctx.session.user.id }
|
|
379
|
-
// },
|
|
380
|
-
// });
|
|
381
|
-
|
|
382
|
-
// if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
383
|
-
|
|
384
|
-
// // Delete associated versions first
|
|
385
|
-
// await ctx.db.artifactVersion.deleteMany({
|
|
386
|
-
// where: { artifactId: input.summaryId },
|
|
387
|
-
// });
|
|
388
|
-
|
|
389
|
-
// // Delete the artifact
|
|
390
|
-
// await ctx.db.artifact.delete({
|
|
391
|
-
// where: { id: input.summaryId },
|
|
392
|
-
// });
|
|
393
|
-
|
|
394
|
-
// return true;
|
|
395
|
-
// }),
|
|
396
|
-
|
|
397
|
-
// // Get meeting versions/history
|
|
398
|
-
// getVersions: authedProcedure
|
|
399
|
-
// .input(z.object({ summaryId: z.string() }))
|
|
400
|
-
// .query(async ({ ctx, input }) => {
|
|
401
|
-
// const summary = await ctx.db.artifact.findFirst({
|
|
402
|
-
// where: {
|
|
403
|
-
// id: input.summaryId,
|
|
404
|
-
// type: ArtifactType.MEETING_SUMMARY,
|
|
405
|
-
// workspace: { ownerId: ctx.session.user.id }
|
|
406
|
-
// },
|
|
407
|
-
// });
|
|
408
|
-
|
|
409
|
-
// if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
410
|
-
|
|
411
|
-
// return ctx.db.artifactVersion.findMany({
|
|
412
|
-
// where: { artifactId: input.summaryId },
|
|
413
|
-
// orderBy: { version: 'desc' },
|
|
414
|
-
// });
|
|
415
|
-
// }),
|
|
416
|
-
// });
|