@goscribe/server 1.3.0 → 1.3.1

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.
Files changed (79) hide show
  1. package/dist/context.d.ts +5 -1
  2. package/dist/lib/activity_human_description.d.ts +13 -0
  3. package/dist/lib/activity_human_description.js +221 -0
  4. package/dist/lib/activity_human_description.test.d.ts +1 -0
  5. package/dist/lib/activity_human_description.test.js +16 -0
  6. package/dist/lib/activity_log_service.d.ts +87 -0
  7. package/dist/lib/activity_log_service.js +276 -0
  8. package/dist/lib/activity_log_service.test.d.ts +1 -0
  9. package/dist/lib/activity_log_service.test.js +27 -0
  10. package/dist/lib/ai-session.d.ts +15 -2
  11. package/dist/lib/ai-session.js +147 -85
  12. package/dist/lib/constants.d.ts +13 -0
  13. package/dist/lib/constants.js +12 -0
  14. package/dist/lib/email.d.ts +11 -0
  15. package/dist/lib/email.js +193 -0
  16. package/dist/lib/env.d.ts +13 -0
  17. package/dist/lib/env.js +16 -0
  18. package/dist/lib/inference.d.ts +4 -1
  19. package/dist/lib/inference.js +3 -3
  20. package/dist/lib/logger.d.ts +4 -4
  21. package/dist/lib/logger.js +30 -8
  22. package/dist/lib/notification-service.d.ts +152 -0
  23. package/dist/lib/notification-service.js +473 -0
  24. package/dist/lib/notification-service.test.d.ts +1 -0
  25. package/dist/lib/notification-service.test.js +87 -0
  26. package/dist/lib/prisma.d.ts +2 -1
  27. package/dist/lib/prisma.js +5 -1
  28. package/dist/lib/pusher.d.ts +23 -0
  29. package/dist/lib/pusher.js +69 -5
  30. package/dist/lib/retry.d.ts +15 -0
  31. package/dist/lib/retry.js +37 -0
  32. package/dist/lib/storage.js +2 -2
  33. package/dist/lib/stripe.d.ts +9 -0
  34. package/dist/lib/stripe.js +36 -0
  35. package/dist/lib/subscription_service.d.ts +37 -0
  36. package/dist/lib/subscription_service.js +654 -0
  37. package/dist/lib/usage_service.d.ts +26 -0
  38. package/dist/lib/usage_service.js +59 -0
  39. package/dist/lib/worksheet-generation.d.ts +91 -0
  40. package/dist/lib/worksheet-generation.js +95 -0
  41. package/dist/lib/worksheet-generation.test.d.ts +1 -0
  42. package/dist/lib/worksheet-generation.test.js +20 -0
  43. package/dist/lib/workspace-access.d.ts +18 -0
  44. package/dist/lib/workspace-access.js +13 -0
  45. package/dist/routers/_app.d.ts +1349 -253
  46. package/dist/routers/_app.js +10 -0
  47. package/dist/routers/admin.d.ts +361 -0
  48. package/dist/routers/admin.js +633 -0
  49. package/dist/routers/annotations.d.ts +219 -0
  50. package/dist/routers/annotations.js +187 -0
  51. package/dist/routers/auth.d.ts +88 -7
  52. package/dist/routers/auth.js +339 -19
  53. package/dist/routers/chat.d.ts +6 -12
  54. package/dist/routers/copilot.d.ts +199 -0
  55. package/dist/routers/copilot.js +571 -0
  56. package/dist/routers/flashcards.d.ts +47 -81
  57. package/dist/routers/flashcards.js +143 -27
  58. package/dist/routers/members.d.ts +36 -7
  59. package/dist/routers/members.js +200 -19
  60. package/dist/routers/notifications.d.ts +99 -0
  61. package/dist/routers/notifications.js +127 -0
  62. package/dist/routers/payment.d.ts +89 -0
  63. package/dist/routers/payment.js +403 -0
  64. package/dist/routers/podcast.d.ts +8 -13
  65. package/dist/routers/podcast.js +54 -31
  66. package/dist/routers/studyguide.d.ts +1 -29
  67. package/dist/routers/studyguide.js +80 -71
  68. package/dist/routers/worksheets.d.ts +105 -38
  69. package/dist/routers/worksheets.js +258 -68
  70. package/dist/routers/workspace.d.ts +139 -60
  71. package/dist/routers/workspace.js +455 -315
  72. package/dist/scripts/purge-deleted-users.d.ts +1 -0
  73. package/dist/scripts/purge-deleted-users.js +149 -0
  74. package/dist/server.js +130 -10
  75. package/dist/services/flashcard-progress.service.d.ts +18 -66
  76. package/dist/services/flashcard-progress.service.js +51 -42
  77. package/dist/trpc.d.ts +20 -21
  78. package/dist/trpc.js +150 -1
  79. package/package.json +1 -1
@@ -1,17 +1,15 @@
1
1
  import { z } from 'zod';
2
2
  import { TRPCError } from '@trpc/server';
3
- import { router, authedProcedure } from '../trpc.js';
3
+ import { router, authedProcedure, limitedProcedure } from '../trpc.js';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import inference from '../lib/inference.js';
6
6
  import { generateSignedUrl, deleteFromSupabase } from '../lib/storage.js';
7
7
  import PusherService from '../lib/pusher.js';
8
8
  import { aiSessionService } from '../lib/ai-session.js';
9
- // Prisma enum values mapped manually to avoid type import issues in ESM
10
- const ArtifactType = {
11
- PODCAST_EPISODE: 'PODCAST_EPISODE',
12
- STUDY_GUIDE: 'STUDY_GUIDE',
13
- FLASHCARD_SET: 'FLASHCARD_SET',
14
- };
9
+ import { ArtifactType } from '../lib/constants.js';
10
+ import { workspaceAccessFilter } from '../lib/workspace-access.js';
11
+ import { logger } from '../lib/logger.js';
12
+ import { notifyArtifactFailed, notifyArtifactReady } from '../lib/notification-service.js';
15
13
  // Podcast segment schema
16
14
  const podcastSegmentSchema = z.object({
17
15
  id: z.string(),
@@ -85,9 +83,9 @@ export const podcast = router({
85
83
  },
86
84
  orderBy: { updatedAt: 'desc' },
87
85
  });
88
- console.log(`📻 Found ${artifacts.length} podcast artifacts`);
86
+ logger.debug(`Found ${artifacts.length} podcast artifacts`);
89
87
  artifacts.forEach((artifact, i) => {
90
- console.log(` Podcast ${i + 1}: "${artifact.title}" - ${artifact.podcastSegments.length} segments`);
88
+ logger.debug(` Podcast ${i + 1}: "${artifact.title}" - ${artifact.podcastSegments.length} segments`);
91
89
  });
92
90
  // Transform to include segments with fresh signed URLs
93
91
  const episodesWithUrls = await Promise.all(artifacts.map(async (artifact) => {
@@ -112,7 +110,7 @@ export const podcast = router({
112
110
  };
113
111
  }
114
112
  catch (error) {
115
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
113
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
116
114
  return {
117
115
  id: segment.id,
118
116
  title: segment.title,
@@ -138,11 +136,11 @@ export const podcast = router({
138
136
  let metadata = null;
139
137
  if (latestVersion) {
140
138
  try {
141
- console.log(latestVersion.data);
139
+ logger.debug(JSON.stringify(latestVersion.data));
142
140
  metadata = podcastMetadataSchema.parse(latestVersion.data);
143
141
  }
144
142
  catch (error) {
145
- console.error('Failed to parse podcast metadata:', error);
143
+ logger.error('Failed to parse podcast metadata:', error);
146
144
  }
147
145
  }
148
146
  return {
@@ -172,7 +170,7 @@ export const podcast = router({
172
170
  where: {
173
171
  id: input.episodeId,
174
172
  type: ArtifactType.PODCAST_EPISODE,
175
- workspace: { ownerId: ctx.session.user.id }
173
+ workspace: workspaceAccessFilter(ctx.session.user.id)
176
174
  },
177
175
  include: {
178
176
  versions: {
@@ -184,18 +182,18 @@ export const podcast = router({
184
182
  },
185
183
  },
186
184
  });
187
- console.log(episode);
185
+ logger.debug(JSON.stringify(episode));
188
186
  if (!episode)
189
187
  throw new TRPCError({ code: 'NOT_FOUND' });
190
188
  const latestVersion = episode.versions[0];
191
189
  if (!latestVersion)
192
190
  throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
193
- console.log(latestVersion);
191
+ logger.debug(JSON.stringify(latestVersion));
194
192
  try {
195
193
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
196
194
  }
197
195
  catch (error) {
198
- console.error('Failed to parse podcast metadata:', error);
196
+ logger.error('Failed to parse podcast metadata:', error);
199
197
  }
200
198
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
201
199
  const imageUrl = episode.imageObjectKey ? await generateSignedUrl(episode.imageObjectKey, 24) : null;
@@ -217,7 +215,7 @@ export const podcast = router({
217
215
  };
218
216
  }
219
217
  catch (error) {
220
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
218
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
221
219
  return {
222
220
  id: segment.id,
223
221
  title: segment.title,
@@ -256,7 +254,7 @@ export const podcast = router({
256
254
  };
257
255
  }),
258
256
  // Generate podcast episode from text input
259
- generateEpisode: authedProcedure
257
+ generateEpisode: limitedProcedure
260
258
  .input(z.object({
261
259
  workspaceId: z.string(),
262
260
  podcastData: podcastInputSchema,
@@ -378,7 +376,7 @@ export const podcast = router({
378
376
  }
379
377
  catch (audioError) {
380
378
  const errorMessage = audioError instanceof Error ? audioError.message : 'Unknown error';
381
- console.error(`❌ Error generating audio for segment ${i + 1}:`, {
379
+ logger.error(`❌ Error generating audio for segment ${i + 1}:`, {
382
380
  title: segment.title,
383
381
  error: errorMessage,
384
382
  stack: audioError instanceof Error ? audioError.stack : undefined,
@@ -401,9 +399,17 @@ export const podcast = router({
401
399
  }
402
400
  // Check if any segments were successfully generated
403
401
  if (segments.length === 0) {
404
- console.error('No segments were successfully generated');
402
+ logger.error('No segments were successfully generated');
405
403
  await PusherService.emitError(input.workspaceId, `Failed to generate any segments. ${failedSegments.length} segment(s) failed.`, 'podcast');
406
404
  // Cleanup the artifact
405
+ await notifyArtifactFailed(ctx.db, {
406
+ userId: ctx.session.user.id,
407
+ workspaceId: input.workspaceId,
408
+ artifactType: ArtifactType.PODCAST_EPISODE,
409
+ artifactId: newArtifact.id,
410
+ title: input.podcastData.title,
411
+ message: `Failed to generate any audio segments. All ${failedSegments.length} attempts failed.`,
412
+ }).catch(() => { });
407
413
  await ctx.db.artifact.delete({
408
414
  where: { id: newArtifact.id },
409
415
  });
@@ -447,7 +453,7 @@ export const podcast = router({
447
453
 
448
454
  Podcast Title: ${structure.episodeTitle}
449
455
  Segments: ${JSON.stringify(segments.map(s => ({ title: s.title, keyPoints: s.keyPoints })))}`;
450
- const summaryResponse = await inference(summaryPrompt);
456
+ const summaryResponse = await inference([{ role: "user", content: summaryPrompt }]);
451
457
  const summaryContent = summaryResponse.choices[0].message.content || '';
452
458
  let episodeSummary;
453
459
  try {
@@ -459,7 +465,7 @@ export const podcast = router({
459
465
  episodeSummary = JSON.parse(jsonMatch[0]);
460
466
  }
461
467
  catch (parseError) {
462
- console.error('Failed to parse summary response:', summaryContent);
468
+ logger.error('Failed to parse summary response:', summaryContent);
463
469
  await PusherService.emitTaskComplete(input.workspaceId, 'podcast_summary_error', {
464
470
  error: 'Failed to parse summary response'
465
471
  });
@@ -533,7 +539,14 @@ export const podcast = router({
533
539
  },
534
540
  });
535
541
  // Emit podcast generation completion notification
536
- await PusherService.emitPodcastComplete(input.workspaceId, {});
542
+ await PusherService.emitPodcastComplete(input.workspaceId, newArtifact);
543
+ await notifyArtifactReady(ctx.db, {
544
+ userId: ctx.session.user.id,
545
+ workspaceId: input.workspaceId,
546
+ artifactId: newArtifact.id,
547
+ artifactType: ArtifactType.PODCAST_EPISODE,
548
+ title: metadata.title,
549
+ }).catch(() => { });
537
550
  return {
538
551
  id: newArtifact.id,
539
552
  title: metadata.title,
@@ -543,7 +556,17 @@ export const podcast = router({
543
556
  };
544
557
  }
545
558
  catch (error) {
546
- console.error('Error generating podcast episode:', error);
559
+ logger.error('Error generating podcast episode:', error);
560
+ await notifyArtifactFailed(ctx.db, {
561
+ userId: ctx.session.user.id,
562
+ workspaceId: input.workspaceId,
563
+ artifactType: ArtifactType.PODCAST_EPISODE,
564
+ artifactId: newArtifact.id,
565
+ title: input.podcastData.title,
566
+ message: error instanceof Error
567
+ ? error.message
568
+ : 'Podcast generation failed.',
569
+ }).catch(() => { });
547
570
  await ctx.db.artifact.delete({
548
571
  where: {
549
572
  id: newArtifact.id,
@@ -570,7 +593,7 @@ export const podcast = router({
570
593
  where: {
571
594
  id: input.episodeId,
572
595
  type: ArtifactType.PODCAST_EPISODE,
573
- workspace: { ownerId: ctx.session.user.id }
596
+ workspace: workspaceAccessFilter(ctx.session.user.id)
574
597
  },
575
598
  include: {
576
599
  versions: {
@@ -618,7 +641,7 @@ export const podcast = router({
618
641
  where: {
619
642
  id: input.episodeId,
620
643
  type: ArtifactType.PODCAST_EPISODE,
621
- workspace: { ownerId: ctx.session.user.id }
644
+ workspace: workspaceAccessFilter(ctx.session.user.id)
622
645
  },
623
646
  include: {
624
647
  versions: {
@@ -667,7 +690,7 @@ export const podcast = router({
667
690
  where: {
668
691
  id: input.episodeId,
669
692
  type: ArtifactType.PODCAST_EPISODE,
670
- workspace: { ownerId: ctx.session.user.id }
693
+ workspace: workspaceAccessFilter(ctx.session.user.id)
671
694
  },
672
695
  include: {
673
696
  versions: {
@@ -695,7 +718,7 @@ export const podcast = router({
695
718
  await deleteFromSupabase(segment.objectKey);
696
719
  }
697
720
  catch (error) {
698
- console.error(`Failed to delete audio file ${segment.objectKey}:`, error);
721
+ logger.error(`Failed to delete audio file ${segment.objectKey}:`, error);
699
722
  }
700
723
  }
701
724
  }
@@ -719,7 +742,7 @@ export const podcast = router({
719
742
  return true;
720
743
  }
721
744
  catch (error) {
722
- console.error('Error deleting episode:', error);
745
+ logger.error('Error deleting episode:', error);
723
746
  await PusherService.emitError(episode.workspaceId, `Failed to delete episode: ${error instanceof Error ? error.message : 'Unknown error'}`, 'podcast');
724
747
  throw new TRPCError({
725
748
  code: 'INTERNAL_SERVER_ERROR',
@@ -735,7 +758,7 @@ export const podcast = router({
735
758
  where: {
736
759
  id: input.segmentId,
737
760
  artifact: {
738
- workspace: { ownerId: ctx.session.user.id }
761
+ workspace: workspaceAccessFilter(ctx.session.user.id)
739
762
  }
740
763
  },
741
764
  include: {
@@ -751,7 +774,7 @@ export const podcast = router({
751
774
  audioUrl = await generateSignedUrl(segment.objectKey, 24); // 24 hours
752
775
  }
753
776
  catch (error) {
754
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
777
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
755
778
  }
756
779
  }
757
780
  return {
@@ -1,11 +1,5 @@
1
1
  export declare const studyguide: import("@trpc/server").TRPCBuiltRouter<{
2
- ctx: {
3
- db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
4
- session: any;
5
- req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
6
- res: import("express").Response<any, Record<string, any>>;
7
- cookies: Record<string, string | undefined>;
8
- };
2
+ ctx: import("../context.js").Context;
9
3
  meta: object;
10
4
  errorShape: {
11
5
  data: {
@@ -39,26 +33,4 @@ export declare const studyguide: import("@trpc/server").TRPCBuiltRouter<{
39
33
  };
40
34
  meta: object;
41
35
  }>;
42
- edit: import("@trpc/server").TRPCMutationProcedure<{
43
- input: {
44
- workspaceId: string;
45
- content: string;
46
- studyGuideId?: string | undefined;
47
- data?: Record<string, unknown> | undefined;
48
- title?: string | undefined;
49
- };
50
- output: {
51
- artifactId: string;
52
- version: {
53
- id: string;
54
- createdAt: Date;
55
- createdById: string | null;
56
- artifactId: string;
57
- content: string;
58
- data: import("@prisma/client/runtime/library").JsonValue | null;
59
- version: number;
60
- };
61
- };
62
- meta: object;
63
- }>;
64
36
  }>>;
@@ -1,16 +1,16 @@
1
1
  import { z } from 'zod';
2
+ import { TRPCError } from '@trpc/server';
2
3
  import { router, authedProcedure } from '../trpc.js';
3
- // Mirror Prisma enum to avoid direct type import
4
- const ArtifactType = {
5
- STUDY_GUIDE: 'STUDY_GUIDE',
6
- };
4
+ import { ArtifactType } from '../lib/constants.js';
5
+ import { getUserUsage, getUserPlanLimits } from '../lib/usage_service.js';
6
+ import { workspaceAccessFilter } from '../lib/workspace-access.js';
7
7
  const initializeEditorJsEmptyBlock = () => ({
8
8
  time: Date.now(),
9
9
  blocks: [
10
10
  {
11
11
  id: 'initial',
12
12
  type: 'paragraph',
13
- data: { text: 'Start writing your study guide...' },
13
+ data: { text: 'Upload some files to begin creating your revision workspace...' },
14
14
  },
15
15
  ],
16
16
  version: '2.27.0',
@@ -27,14 +27,23 @@ export const studyguide = router({
27
27
  where: {
28
28
  workspaceId: input.workspaceId,
29
29
  type: ArtifactType.STUDY_GUIDE,
30
- workspace: { ownerId: ctx.session.user.id },
30
+ workspace: workspaceAccessFilter(ctx.session.user.id),
31
31
  },
32
32
  include: {
33
33
  versions: { orderBy: { version: 'desc' }, take: 1 },
34
34
  },
35
35
  });
36
- console.log('artifact', artifact);
37
36
  if (!artifact) {
37
+ const [usage, limits] = await Promise.all([
38
+ getUserUsage(ctx.session.user.id),
39
+ getUserPlanLimits(ctx.session.user.id)
40
+ ]);
41
+ if (limits && usage.studyGuides >= limits.maxStudyGuides) {
42
+ throw new TRPCError({
43
+ code: 'FORBIDDEN',
44
+ message: 'Study Guide limit reached. Please upgrade your plan to create more workspaces with study guides.'
45
+ });
46
+ }
38
47
  artifact = await ctx.db.artifact.create({
39
48
  data: {
40
49
  workspaceId: input.workspaceId,
@@ -58,68 +67,68 @@ export const studyguide = router({
58
67
  return { artifactId: artifact.id, title: artifact.title, latestVersion };
59
68
  }),
60
69
  // Edit study guide content by creating a new version, or create if doesn't exist
61
- edit: authedProcedure
62
- .input(z.object({
63
- workspaceId: z.string(),
64
- studyGuideId: z.string().optional(),
65
- content: z.string().min(1),
66
- data: z.record(z.string(), z.unknown()).optional(),
67
- title: z.string().min(1).optional(),
68
- }))
69
- .mutation(async ({ ctx, input }) => {
70
- let artifact;
71
- if (input.studyGuideId) {
72
- // Try to find existing study guide
73
- artifact = await ctx.db.artifact.findFirst({
74
- where: {
75
- id: input.studyGuideId,
76
- type: ArtifactType.STUDY_GUIDE,
77
- workspace: { ownerId: ctx.session.user.id },
78
- },
79
- });
80
- }
81
- else {
82
- // Find by workspace if no specific studyGuideId provided
83
- artifact = await ctx.db.artifact.findFirst({
84
- where: {
85
- workspaceId: input.workspaceId,
86
- type: ArtifactType.STUDY_GUIDE,
87
- workspace: { ownerId: ctx.session.user.id },
88
- },
89
- });
90
- }
91
- // If no study guide found, create a new one
92
- if (!artifact) {
93
- artifact = await ctx.db.artifact.create({
94
- data: {
95
- workspaceId: input.workspaceId,
96
- type: ArtifactType.STUDY_GUIDE,
97
- title: 'Study Guide',
98
- createdById: ctx.session.user.id,
99
- },
100
- });
101
- }
102
- const last = await ctx.db.artifactVersion.findFirst({
103
- where: { artifactId: artifact.id },
104
- orderBy: { version: 'desc' },
105
- });
106
- if (input.title && input.title !== artifact.title) {
107
- console.log('rename');
108
- await ctx.db.artifact.update({
109
- where: { id: artifact.id },
110
- data: { title: input.title },
111
- });
112
- }
113
- const nextVersion = (last?.version ?? 0) + 1;
114
- const version = await ctx.db.artifactVersion.create({
115
- data: {
116
- artifactId: artifact.id,
117
- content: input.content,
118
- data: input.data,
119
- version: nextVersion,
120
- createdById: ctx.session.user.id,
121
- },
122
- });
123
- return { artifactId: artifact.id, version };
124
- }),
70
+ // edit: authedProcedure
71
+ // .input(
72
+ // z.object({
73
+ // workspaceId: z.string(),
74
+ // studyGuideId: z.string().optional(),
75
+ // content: z.string().min(1),
76
+ // data: z.record(z.string(), z.unknown()).optional(),
77
+ // title: z.string().min(1).optional(),
78
+ // })
79
+ // )
80
+ // .mutation(async ({ ctx, input }) => {
81
+ // let artifact;
82
+ // if (input.studyGuideId) {
83
+ // // Try to find existing study guide
84
+ // artifact = await ctx.db.artifact.findFirst({
85
+ // where: {
86
+ // id: input.studyGuideId,
87
+ // type: ArtifactType.STUDY_GUIDE,
88
+ // workspace: workspaceAccessFilter(ctx.session.user.id),
89
+ // },
90
+ // });
91
+ // } else {
92
+ // // Find by workspace if no specific studyGuideId provided
93
+ // artifact = await ctx.db.artifact.findFirst({
94
+ // where: {
95
+ // workspaceId: input.workspaceId,
96
+ // type: ArtifactType.STUDY_GUIDE,
97
+ // workspace: workspaceAccessFilter(ctx.session.user.id),
98
+ // },
99
+ // });
100
+ // }
101
+ // // If no study guide found, create a new one
102
+ // if (!artifact) {
103
+ // artifact = await ctx.db.artifact.create({
104
+ // data: {
105
+ // workspaceId: input.workspaceId,
106
+ // type: ArtifactType.STUDY_GUIDE,
107
+ // title: 'Study Guide',
108
+ // createdById: ctx.session.user.id,
109
+ // },
110
+ // });
111
+ // }
112
+ // const last = await ctx.db.artifactVersion.findFirst({
113
+ // where: { artifactId: artifact.id },
114
+ // orderBy: { version: 'desc' },
115
+ // });
116
+ // if (input.title && input.title !== artifact.title) {
117
+ // await ctx.db.artifact.update({
118
+ // where: { id: artifact.id },
119
+ // data: { title: input.title },
120
+ // });
121
+ // }
122
+ // const nextVersion = (last?.version ?? 0) + 1;
123
+ // const version = await ctx.db.artifactVersion.create({
124
+ // data: {
125
+ // artifactId: artifact.id,
126
+ // content: input.content,
127
+ // data: input.data ?? undefined,
128
+ // version: nextVersion,
129
+ // createdById: ctx.session.user.id,
130
+ // },
131
+ // });
132
+ // return { artifactId: artifact.id, version };
133
+ // }),
125
134
  });