@goscribe/server 1.2.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 (126) hide show
  1. package/check-difficulty.cjs +14 -0
  2. package/check-questions.cjs +14 -0
  3. package/db-summary.cjs +22 -0
  4. package/dist/context.d.ts +5 -1
  5. package/dist/lib/activity_human_description.d.ts +13 -0
  6. package/dist/lib/activity_human_description.js +221 -0
  7. package/dist/lib/activity_human_description.test.d.ts +1 -0
  8. package/dist/lib/activity_human_description.test.js +16 -0
  9. package/dist/lib/activity_log_service.d.ts +87 -0
  10. package/dist/lib/activity_log_service.js +276 -0
  11. package/dist/lib/activity_log_service.test.d.ts +1 -0
  12. package/dist/lib/activity_log_service.test.js +27 -0
  13. package/dist/lib/ai-session.d.ts +15 -2
  14. package/dist/lib/ai-session.js +147 -85
  15. package/dist/lib/constants.d.ts +13 -0
  16. package/dist/lib/constants.js +12 -0
  17. package/dist/lib/email.d.ts +11 -0
  18. package/dist/lib/email.js +193 -0
  19. package/dist/lib/env.d.ts +13 -0
  20. package/dist/lib/env.js +16 -0
  21. package/dist/lib/inference.d.ts +4 -1
  22. package/dist/lib/inference.js +3 -3
  23. package/dist/lib/logger.d.ts +4 -4
  24. package/dist/lib/logger.js +30 -8
  25. package/dist/lib/notification-service.d.ts +152 -0
  26. package/dist/lib/notification-service.js +473 -0
  27. package/dist/lib/notification-service.test.d.ts +1 -0
  28. package/dist/lib/notification-service.test.js +87 -0
  29. package/dist/lib/prisma.d.ts +2 -1
  30. package/dist/lib/prisma.js +5 -1
  31. package/dist/lib/pusher.d.ts +23 -0
  32. package/dist/lib/pusher.js +69 -5
  33. package/dist/lib/retry.d.ts +15 -0
  34. package/dist/lib/retry.js +37 -0
  35. package/dist/lib/storage.js +2 -2
  36. package/dist/lib/stripe.d.ts +9 -0
  37. package/dist/lib/stripe.js +36 -0
  38. package/dist/lib/subscription_service.d.ts +37 -0
  39. package/dist/lib/subscription_service.js +654 -0
  40. package/dist/lib/usage_service.d.ts +26 -0
  41. package/dist/lib/usage_service.js +59 -0
  42. package/dist/lib/worksheet-generation.d.ts +91 -0
  43. package/dist/lib/worksheet-generation.js +95 -0
  44. package/dist/lib/worksheet-generation.test.d.ts +1 -0
  45. package/dist/lib/worksheet-generation.test.js +20 -0
  46. package/dist/lib/workspace-access.d.ts +18 -0
  47. package/dist/lib/workspace-access.js +13 -0
  48. package/dist/routers/_app.d.ts +1349 -253
  49. package/dist/routers/_app.js +10 -0
  50. package/dist/routers/admin.d.ts +361 -0
  51. package/dist/routers/admin.js +633 -0
  52. package/dist/routers/annotations.d.ts +219 -0
  53. package/dist/routers/annotations.js +187 -0
  54. package/dist/routers/auth.d.ts +88 -7
  55. package/dist/routers/auth.js +339 -19
  56. package/dist/routers/chat.d.ts +6 -12
  57. package/dist/routers/copilot.d.ts +199 -0
  58. package/dist/routers/copilot.js +571 -0
  59. package/dist/routers/flashcards.d.ts +47 -81
  60. package/dist/routers/flashcards.js +143 -27
  61. package/dist/routers/members.d.ts +36 -7
  62. package/dist/routers/members.js +200 -19
  63. package/dist/routers/notifications.d.ts +99 -0
  64. package/dist/routers/notifications.js +127 -0
  65. package/dist/routers/payment.d.ts +89 -0
  66. package/dist/routers/payment.js +403 -0
  67. package/dist/routers/podcast.d.ts +8 -13
  68. package/dist/routers/podcast.js +54 -31
  69. package/dist/routers/studyguide.d.ts +1 -29
  70. package/dist/routers/studyguide.js +80 -71
  71. package/dist/routers/worksheets.d.ts +105 -38
  72. package/dist/routers/worksheets.js +258 -68
  73. package/dist/routers/workspace.d.ts +139 -60
  74. package/dist/routers/workspace.js +455 -315
  75. package/dist/scripts/purge-deleted-users.d.ts +1 -0
  76. package/dist/scripts/purge-deleted-users.js +149 -0
  77. package/dist/server.js +130 -10
  78. package/dist/services/flashcard-progress.service.d.ts +18 -66
  79. package/dist/services/flashcard-progress.service.js +51 -42
  80. package/dist/trpc.d.ts +20 -21
  81. package/dist/trpc.js +150 -1
  82. package/mcq-test.cjs +36 -0
  83. package/package.json +9 -2
  84. package/prisma/migrations/20260413143206_init/migration.sql +873 -0
  85. package/prisma/schema.prisma +471 -324
  86. package/src/context.ts +4 -1
  87. package/src/lib/activity_human_description.test.ts +28 -0
  88. package/src/lib/activity_human_description.ts +239 -0
  89. package/src/lib/activity_log_service.test.ts +37 -0
  90. package/src/lib/activity_log_service.ts +353 -0
  91. package/src/lib/ai-session.ts +79 -51
  92. package/src/lib/email.ts +213 -29
  93. package/src/lib/env.ts +23 -6
  94. package/src/lib/inference.ts +2 -2
  95. package/src/lib/notification-service.test.ts +106 -0
  96. package/src/lib/notification-service.ts +677 -0
  97. package/src/lib/prisma.ts +6 -1
  98. package/src/lib/pusher.ts +86 -2
  99. package/src/lib/stripe.ts +39 -0
  100. package/src/lib/subscription_service.ts +722 -0
  101. package/src/lib/usage_service.ts +74 -0
  102. package/src/lib/worksheet-generation.test.ts +31 -0
  103. package/src/lib/worksheet-generation.ts +139 -0
  104. package/src/routers/_app.ts +9 -0
  105. package/src/routers/admin.ts +710 -0
  106. package/src/routers/annotations.ts +41 -0
  107. package/src/routers/auth.ts +338 -28
  108. package/src/routers/copilot.ts +719 -0
  109. package/src/routers/flashcards.ts +201 -68
  110. package/src/routers/members.ts +280 -80
  111. package/src/routers/notifications.ts +142 -0
  112. package/src/routers/payment.ts +448 -0
  113. package/src/routers/podcast.ts +112 -83
  114. package/src/routers/studyguide.ts +12 -0
  115. package/src/routers/worksheets.ts +289 -66
  116. package/src/routers/workspace.ts +329 -122
  117. package/src/scripts/purge-deleted-users.ts +167 -0
  118. package/src/server.ts +137 -11
  119. package/src/services/flashcard-progress.service.ts +49 -37
  120. package/src/trpc.ts +184 -5
  121. package/test-generate.js +30 -0
  122. package/test-ratio.cjs +9 -0
  123. package/zod-test.cjs +22 -0
  124. package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
  125. package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
  126. package/prisma/seed.mjs +0 -135
@@ -0,0 +1,403 @@
1
+ import { z } from 'zod';
2
+ import { router, verifiedProcedure } from '../trpc.js';
3
+ import { TRPCError } from '@trpc/server';
4
+ import { stripe } from '../lib/stripe.js';
5
+ import { env } from '../lib/env.js';
6
+ import { getUserUsage, getUserPlanLimits } from '../lib/usage_service.js';
7
+ import { notifyPaymentSucceeded, notifySubscriptionActivated, notifySubscriptionPaymentSucceeded, } from '../lib/notification-service.js';
8
+ import { upsertSubscriptionFromStripe } from '../lib/subscription_service.js';
9
+ import { ArtifactType } from '../lib/prisma.js';
10
+ const ArtifactTypeUnion = z.enum(['STUDY_GUIDE', 'FLASHCARD_SET', 'WORKSHEET', 'MEETING_SUMMARY', 'PODCAST_EPISODE', 'STORAGE']);
11
+ /** Stripe Checkout URLs contain the session id (cs_…); we only store the URL in DB. */
12
+ const CHECKOUT_SESSION_ID_IN_URL = /cs_[a-zA-Z0-9]+/;
13
+ /**
14
+ * On idempotency conflict, we may have a stored checkout URL. Only reuse it if the
15
+ * Stripe session is still `open`. Otherwise release the row so a new session can be created
16
+ * (avoids redirecting users to a completed/expired checkout = "already completed").
17
+ */
18
+ async function reuseCheckoutUrlIfSessionStillOpen(stripeClient, db, existing, lockKey) {
19
+ const url = existing.stripeSessionId;
20
+ if (!url)
21
+ return null;
22
+ const idMatch = url.match(CHECKOUT_SESSION_ID_IN_URL);
23
+ if (!idMatch) {
24
+ await db.idempotencyRecord.updateMany({
25
+ where: { id: existing.id, activeLockKey: lockKey },
26
+ data: { activeLockKey: null, status: 'expired' },
27
+ });
28
+ return null;
29
+ }
30
+ try {
31
+ const session = await stripeClient.checkout.sessions.retrieve(idMatch[0]);
32
+ if (session.status === 'open') {
33
+ return url;
34
+ }
35
+ await db.idempotencyRecord.updateMany({
36
+ where: { id: existing.id, activeLockKey: lockKey },
37
+ data: {
38
+ activeLockKey: null,
39
+ status: session.status === 'complete' ? 'completed' : 'expired',
40
+ },
41
+ });
42
+ return null;
43
+ }
44
+ catch {
45
+ await db.idempotencyRecord.updateMany({
46
+ where: { id: existing.id, activeLockKey: lockKey },
47
+ data: { activeLockKey: null, status: 'expired' },
48
+ });
49
+ return null;
50
+ }
51
+ }
52
+ export const paymentRouter = router({
53
+ getPlans: verifiedProcedure
54
+ .query(async (opts) => {
55
+ const ctx = opts.ctx;
56
+ const userId = ctx.userId;
57
+ const plans = await ctx.db.plan.findMany({
58
+ where: { active: true },
59
+ include: { limit: true },
60
+ orderBy: { price: 'asc' }
61
+ });
62
+ const activeSubscriptions = await ctx.db.subscription.findMany({
63
+ where: {
64
+ userId: userId,
65
+ status: 'active'
66
+ }
67
+ });
68
+ return plans.map((plan) => {
69
+ return {
70
+ ...plan,
71
+ isActive: activeSubscriptions.some((sub) => sub.planId === plan.id)
72
+ };
73
+ });
74
+ }),
75
+ createCheckoutSession: verifiedProcedure
76
+ .input(z.object({
77
+ planId: z.string()
78
+ }))
79
+ .mutation(async (opts) => {
80
+ const ctx = opts.ctx;
81
+ const input = opts.input;
82
+ const userId = ctx.userId;
83
+ if (!stripe) {
84
+ throw new TRPCError({
85
+ code: 'INTERNAL_SERVER_ERROR',
86
+ message: 'Stripe not configured'
87
+ });
88
+ }
89
+ const user = await ctx.db.user.findUnique({ where: { id: userId } });
90
+ if (!user)
91
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
92
+ const lockKey = `pending_${userId}_${input.planId}`;
93
+ let attempt = null;
94
+ let retryCount = 0;
95
+ while (retryCount < 3) {
96
+ try {
97
+ attempt = await ctx.db.idempotencyRecord.create({
98
+ data: {
99
+ userId: userId,
100
+ planId: input.planId,
101
+ activeLockKey: lockKey,
102
+ status: 'pending'
103
+ }
104
+ });
105
+ break;
106
+ }
107
+ catch (err) {
108
+ if (err.code === 'P2002') {
109
+ const existing = await ctx.db.idempotencyRecord.findUnique({
110
+ where: { activeLockKey: lockKey }
111
+ });
112
+ if (!existing) {
113
+ retryCount++;
114
+ continue;
115
+ }
116
+ const isStale = (Date.now() - existing.updatedAt.getTime()) > (24 * 60 * 60 * 1000);
117
+ if (isStale) {
118
+ const result = await ctx.db.idempotencyRecord.updateMany({
119
+ where: { id: existing.id, activeLockKey: lockKey },
120
+ data: { activeLockKey: null, status: 'expired' }
121
+ });
122
+ if (result.count > 0) {
123
+ retryCount++;
124
+ continue;
125
+ }
126
+ }
127
+ if (existing.stripeSessionId && stripe) {
128
+ const reusable = await reuseCheckoutUrlIfSessionStillOpen(stripe, ctx.db, existing, lockKey);
129
+ if (reusable)
130
+ return { url: reusable };
131
+ retryCount++;
132
+ continue;
133
+ }
134
+ await new Promise(resolve => setTimeout(resolve, 800));
135
+ retryCount++;
136
+ continue;
137
+ }
138
+ throw err;
139
+ }
140
+ }
141
+ if (!attempt)
142
+ throw new TRPCError({ code: 'CONFLICT', message: "Concurrent request" });
143
+ const plan = await ctx.db.plan.findUnique({ where: { id: input.planId } });
144
+ if (!plan)
145
+ throw new TRPCError({ code: 'NOT_FOUND', message: "Plan not found" });
146
+ try {
147
+ const successUrl = env.STRIPE_SUCCESS_URL.includes('session_id=')
148
+ ? env.STRIPE_SUCCESS_URL
149
+ : `${env.STRIPE_SUCCESS_URL}${env.STRIPE_SUCCESS_URL.includes('?') ? '&' : '?'}session_id={CHECKOUT_SESSION_ID}`;
150
+ const session = await stripe.checkout.sessions.create({
151
+ customer: user.stripe_customer_id || undefined,
152
+ customer_email: user.stripe_customer_id ? undefined : (user.email || undefined),
153
+ line_items: [{ price: plan.stripePriceId, quantity: 1 }],
154
+ mode: plan.interval ? 'subscription' : 'payment',
155
+ subscription_data: plan.interval ? {
156
+ metadata: {
157
+ userId: userId,
158
+ planId: plan.id,
159
+ attemptId: attempt.id
160
+ }
161
+ } : undefined,
162
+ success_url: successUrl,
163
+ cancel_url: env.STRIPE_CANCEL_URL,
164
+ metadata: {
165
+ userId: userId,
166
+ planId: plan.id,
167
+ attemptId: attempt.id
168
+ }
169
+ }, {
170
+ idempotencyKey: attempt.id
171
+ });
172
+ await ctx.db.idempotencyRecord.update({
173
+ where: { id: attempt.id },
174
+ data: { stripeSessionId: session.url }
175
+ });
176
+ return { url: session.url };
177
+ }
178
+ catch (error) {
179
+ await ctx.db.idempotencyRecord.update({
180
+ where: { id: attempt.id },
181
+ data: { status: 'failed', activeLockKey: null }
182
+ });
183
+ throw new TRPCError({
184
+ code: 'INTERNAL_SERVER_ERROR',
185
+ message: error.message
186
+ });
187
+ }
188
+ }),
189
+ confirmCheckoutSuccess: verifiedProcedure
190
+ .input(z.object({
191
+ sessionId: z.string().min(1),
192
+ }))
193
+ .mutation(async (opts) => {
194
+ const ctx = opts.ctx;
195
+ const input = opts.input;
196
+ if (!stripe) {
197
+ throw new TRPCError({
198
+ code: 'INTERNAL_SERVER_ERROR',
199
+ message: 'Stripe not configured'
200
+ });
201
+ }
202
+ const session = await stripe.checkout.sessions.retrieve(input.sessionId, {
203
+ expand: ['line_items', 'subscription'],
204
+ });
205
+ const metadata = session.metadata || {};
206
+ if (metadata.userId !== ctx.userId) {
207
+ throw new TRPCError({
208
+ code: 'FORBIDDEN',
209
+ message: 'This checkout session does not belong to the current user',
210
+ });
211
+ }
212
+ if (session.status !== 'complete') {
213
+ return { confirmed: false, reason: 'checkout_not_complete' };
214
+ }
215
+ const plan = metadata.planId
216
+ ? await ctx.db.plan.findUnique({ where: { id: metadata.planId } })
217
+ : null;
218
+ if (session.mode === 'payment' && session.payment_status === 'paid') {
219
+ await notifyPaymentSucceeded(ctx.db, {
220
+ userId: ctx.userId,
221
+ planId: metadata.planId,
222
+ planName: plan?.name || metadata.planType,
223
+ stripeSessionId: session.id,
224
+ amountPaid: session.amount_total ?? undefined,
225
+ });
226
+ return { confirmed: true, kind: 'payment' };
227
+ }
228
+ if (session.mode === 'subscription') {
229
+ const stripeSubscriptionId = typeof session.subscription === 'string'
230
+ ? session.subscription
231
+ : session.subscription?.id;
232
+ if (stripeSubscriptionId) {
233
+ await upsertSubscriptionFromStripe(stripeSubscriptionId);
234
+ await notifySubscriptionActivated(ctx.db, {
235
+ userId: ctx.userId,
236
+ planId: metadata.planId,
237
+ planName: plan?.name || metadata.planType,
238
+ stripeSubscriptionId,
239
+ });
240
+ if (session.payment_status === 'paid') {
241
+ await notifySubscriptionPaymentSucceeded(ctx.db, {
242
+ userId: ctx.userId,
243
+ planId: metadata.planId,
244
+ planName: plan?.name || metadata.planType,
245
+ stripeInvoiceId: `checkout_${session.id}`,
246
+ amountPaid: session.amount_total ?? undefined,
247
+ });
248
+ }
249
+ }
250
+ return { confirmed: true, kind: 'subscription' };
251
+ }
252
+ return { confirmed: false, reason: 'unsupported_mode' };
253
+ }),
254
+ createResourcePurchaseSession: verifiedProcedure
255
+ .input(z.object({
256
+ resourceType: z.nativeEnum(ArtifactType),
257
+ quantity: z.number().min(1).default(1),
258
+ }))
259
+ .mutation(async ({ ctx, input }) => {
260
+ if (!stripe) {
261
+ throw new TRPCError({
262
+ code: 'INTERNAL_SERVER_ERROR',
263
+ message: 'Stripe is not configured on the server',
264
+ });
265
+ }
266
+ const userId = ctx.userId;
267
+ const user = await ctx.db.user.findUnique({
268
+ where: { id: userId },
269
+ });
270
+ if (!user) {
271
+ throw new TRPCError({
272
+ code: 'NOT_FOUND',
273
+ message: 'User not found',
274
+ });
275
+ }
276
+ const resourcePrice = await ctx.db.resourcePrice.findUnique({
277
+ where: { resourceType: input.resourceType },
278
+ });
279
+ if (!resourcePrice) {
280
+ throw new TRPCError({
281
+ code: 'PRECONDITION_FAILED',
282
+ message: 'Price not set',
283
+ });
284
+ }
285
+ const lockKey = `topup_${userId}_${input.resourceType}`;
286
+ let attempt = null;
287
+ let retryCount = 0;
288
+ while (retryCount < 3) {
289
+ try {
290
+ attempt = await ctx.db.idempotencyRecord.create({
291
+ data: {
292
+ userId: userId,
293
+ resourceType: input.resourceType,
294
+ activeLockKey: lockKey,
295
+ status: 'pending'
296
+ }
297
+ });
298
+ break;
299
+ }
300
+ catch (err) {
301
+ if (err.code === 'P2002') {
302
+ const existing = await ctx.db.idempotencyRecord.findUnique({
303
+ where: { activeLockKey: lockKey }
304
+ });
305
+ if (!existing) {
306
+ retryCount++;
307
+ continue;
308
+ }
309
+ const isStale = (Date.now() - existing.updatedAt.getTime()) > (24 * 60 * 60 * 1000);
310
+ if (isStale) {
311
+ const result = await ctx.db.idempotencyRecord.updateMany({
312
+ where: { id: existing.id, activeLockKey: lockKey },
313
+ data: { activeLockKey: null, status: 'expired' }
314
+ });
315
+ if (result.count > 0) {
316
+ retryCount++;
317
+ continue;
318
+ }
319
+ }
320
+ if (existing.stripeSessionId && stripe) {
321
+ const reusable = await reuseCheckoutUrlIfSessionStillOpen(stripe, ctx.db, existing, lockKey);
322
+ if (reusable)
323
+ return { url: reusable };
324
+ retryCount++;
325
+ continue;
326
+ }
327
+ await new Promise(resolve => setTimeout(resolve, 800));
328
+ retryCount++;
329
+ continue;
330
+ }
331
+ throw err;
332
+ }
333
+ }
334
+ if (!attempt)
335
+ throw new TRPCError({ code: 'CONFLICT', message: "Concurrent request" });
336
+ try {
337
+ const resourceName = input.resourceType
338
+ .split('_')
339
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
340
+ .join(' ');
341
+ const session = await stripe.checkout.sessions.create({
342
+ customer: user.stripe_customer_id || undefined,
343
+ line_items: [
344
+ {
345
+ price_data: {
346
+ currency: 'usd',
347
+ product_data: {
348
+ name: `Add-on: extra ${resourceName}s`,
349
+ description: `Purchase of ${input.quantity} additional ${resourceName}(s)`
350
+ },
351
+ unit_amount: resourcePrice.priceCents
352
+ },
353
+ quantity: input.quantity
354
+ }
355
+ ],
356
+ mode: 'payment',
357
+ success_url: `${env.STRIPE_SUCCESS_URL}?success=true`,
358
+ cancel_url: env.STRIPE_CANCEL_URL,
359
+ metadata: {
360
+ userId: userId,
361
+ resourceType: input.resourceType,
362
+ quantity: input.quantity.toString(),
363
+ isPurchase: 'true',
364
+ attemptId: attempt.id,
365
+ },
366
+ invoice_creation: {
367
+ enabled: true,
368
+ },
369
+ }, {
370
+ idempotencyKey: attempt.id
371
+ });
372
+ await ctx.db.idempotencyRecord.update({
373
+ where: { id: attempt.id },
374
+ data: { stripeSessionId: session.url }
375
+ });
376
+ return { url: session.url };
377
+ }
378
+ catch (error) {
379
+ await ctx.db.idempotencyRecord.update({
380
+ where: { id: attempt.id },
381
+ data: { status: 'failed', activeLockKey: null }
382
+ });
383
+ throw new TRPCError({
384
+ code: 'INTERNAL_SERVER_ERROR',
385
+ message: error.message
386
+ });
387
+ }
388
+ }),
389
+ getUsageOverview: verifiedProcedure
390
+ .query(async (opts) => {
391
+ const ctx = opts.ctx;
392
+ const userId = ctx.userId;
393
+ const [usage, limits] = await Promise.all([
394
+ getUserUsage(userId),
395
+ getUserPlanLimits(userId)
396
+ ]);
397
+ return { usage: usage, limits: limits, hasActivePlan: (!!limits) };
398
+ }),
399
+ getResourcePrices: verifiedProcedure
400
+ .query(async (opts) => {
401
+ return opts.ctx.db.resourcePrice.findMany();
402
+ })
403
+ });
@@ -1,11 +1,5 @@
1
1
  export declare const podcast: 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: {
@@ -181,11 +175,11 @@ export declare const podcast: import("@trpc/server").TRPCBuiltRouter<{
181
175
  createdAt: Date;
182
176
  updatedAt: Date;
183
177
  title: string;
184
- objectKey: string | null;
185
178
  generating: boolean;
186
179
  generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
187
180
  artifactId: string;
188
181
  content: string;
182
+ objectKey: string | null;
189
183
  duration: number;
190
184
  order: number;
191
185
  startTime: number;
@@ -239,17 +233,18 @@ export declare const podcast: import("@trpc/server").TRPCBuiltRouter<{
239
233
  id: string;
240
234
  createdAt: Date;
241
235
  updatedAt: Date;
242
- title: string;
243
- description: string | null;
244
236
  workspaceId: string;
245
237
  type: import("@prisma/client").$Enums.ArtifactType;
238
+ title: string;
246
239
  isArchived: boolean;
247
- generating: boolean;
248
- generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
249
240
  difficulty: import("@prisma/client").$Enums.Difficulty | null;
250
241
  estimatedTime: string | null;
251
- imageObjectKey: string | null;
252
242
  createdById: string | null;
243
+ description: string | null;
244
+ generating: boolean;
245
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
246
+ worksheetConfig: import("@prisma/client/runtime/library").JsonValue | null;
247
+ imageObjectKey: string | null;
253
248
  };
254
249
  meta: object;
255
250
  }>;