@goscribe/server 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/context.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
2
2
  import { prisma } from "./lib/prisma.js";
3
+ import cookie from "cookie";
3
4
  export declare function createContext({ req, res }: CreateExpressContextOptions): Promise<{
4
5
  db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
5
6
  session: any;
6
7
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
7
8
  res: import("express").Response<any, Record<string, any>>;
8
- cookies: Record<string, string | undefined>;
9
+ cookies: cookie.Cookies;
9
10
  }>;
10
11
  /** Use `typeof prisma` for `db` so TS keeps generated model delegates (not a bare PrismaClient). */
11
12
  export type Context = Omit<Awaited<ReturnType<typeof createContext>>, "db"> & {
package/dist/lib/env.js CHANGED
@@ -33,7 +33,7 @@ const envSchema = z.object({
33
33
  SMTP_USER: z.string().optional(),
34
34
  SMTP_PASSWORD: z.string().optional(),
35
35
  SMTP_SECURE: z.enum(['true', 'false']).default('false').transform((v) => v === 'true'),
36
- EMAIL_FROM: z.string().default('Scribe <noreply@goscribe.app>'),
36
+ EMAIL_FROM: z.string().default('Scribe <hello@scribe.study>'),
37
37
  // Stripe
38
38
  STRIPE_SECRET_KEY: z.string().startsWith('sk_').optional(),
39
39
  STRIPE_SUCCESS_URL: z.string().url().default('http://localhost:3000/payment-success'),
@@ -1,4 +1,5 @@
1
- export declare const stripe: any;
1
+ import Stripe from 'stripe';
2
+ export declare const stripe: Stripe | null;
2
3
  /**
3
4
  * Creates a Stripe customer for a user.
4
5
  *
@@ -522,7 +522,7 @@ export async function getUserStorageLimit(userId) {
522
522
  return Number(activeSub.plan.limit.maxStorageBytes);
523
523
  }
524
524
  // Default limit (fallback for users with no active subscription)
525
- return 0;
525
+ return 1024 * 1024 * 1024; // 1GB
526
526
  }
527
527
  /**
528
528
  * Core logic to sync Stripe subscription state with Prisma database
@@ -268,6 +268,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
268
268
  name: string;
269
269
  description?: string | undefined;
270
270
  parentId?: string | undefined;
271
+ markerColor?: string | null | undefined;
271
272
  };
272
273
  output: {
273
274
  id: string;
@@ -2612,7 +2613,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
2612
2613
  planId: string;
2613
2614
  };
2614
2615
  output: {
2615
- url: any;
2616
+ url: string | null;
2616
2617
  };
2617
2618
  meta: object;
2618
2619
  }>;
@@ -2641,7 +2642,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
2641
2642
  quantity?: number | undefined;
2642
2643
  };
2643
2644
  output: {
2644
- url: any;
2645
+ url: string | null;
2645
2646
  };
2646
2647
  meta: object;
2647
2648
  }>;
@@ -10,6 +10,17 @@ import { supabaseClient } from '../lib/storage.js';
10
10
  import { sendVerificationEmail, sendAccountDeletionScheduledEmail, sendAccountRestoredEmail, sendPasswordResetEmail, } from '../lib/email.js';
11
11
  import { createStripeCustomer } from '../lib/stripe.js';
12
12
  import { notifyAdminsAccountDeletionScheduled, notifyAdminsOnSignup, } from '../lib/notification-service.js';
13
+ function getAuthCookieConfig(isProduction) {
14
+ return {
15
+ httpOnly: true,
16
+ secure: isProduction,
17
+ sameSite: (isProduction ? 'none' : 'lax'),
18
+ path: '/',
19
+ maxAge: 60 * 60 * 24 * 30,
20
+ // Use parent domain in production so scribe.study and api.scribe.study share auth state.
21
+ domain: isProduction ? '.scribe.study' : undefined,
22
+ };
23
+ }
13
24
  // Helper to create custom auth token
14
25
  const passwordFieldSchema = z
15
26
  .string()
@@ -269,14 +280,7 @@ export const auth = router({
269
280
  // Create custom auth token
270
281
  const authToken = createCustomAuthToken(user.id);
271
282
  const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
272
- const cookieValue = serialize("auth_token", authToken, {
273
- httpOnly: true,
274
- secure: isProduction, // true for production/HTTPS, false for localhost
275
- sameSite: isProduction ? "none" : "lax", // none for cross-origin, lax for same-origin
276
- path: "/",
277
- domain: isProduction ? "server-w8mz.onrender.com" : undefined,
278
- maxAge: 60 * 60 * 24 * 30, // 30 days
279
- });
283
+ const cookieValue = serialize("auth_token", authToken, getAuthCookieConfig(isProduction));
280
284
  ctx.res.setHeader("Set-Cookie", cookieValue);
281
285
  return {
282
286
  id: user.id,
@@ -425,11 +429,10 @@ export const auth = router({
425
429
  sendAccountDeletionScheduledEmail(user.email, token).catch(() => { });
426
430
  }
427
431
  // Log out user by clearing cookie
432
+ const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
433
+ const clearCookieConfig = getAuthCookieConfig(isProduction);
428
434
  ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
429
- httpOnly: true,
430
- secure: process.env.NODE_ENV === "production",
431
- sameSite: "lax",
432
- path: "/",
435
+ ...clearCookieConfig,
433
436
  maxAge: 0,
434
437
  }));
435
438
  return { success: true, message: 'Account scheduled for deletion' };
@@ -468,11 +471,10 @@ export const auth = router({
468
471
  }
469
472
  // We don't need to delete from db.session because we use a stateless
470
473
  // custom HMAC auth system (auth_token cookie).
474
+ const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
475
+ const clearCookieConfig = getAuthCookieConfig(isProduction);
471
476
  ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
472
- httpOnly: true,
473
- secure: process.env.NODE_ENV === "production",
474
- sameSite: "lax",
475
- path: "/",
477
+ ...clearCookieConfig,
476
478
  maxAge: 0, // Expire immediately
477
479
  }));
478
480
  return { success: true };
@@ -24,7 +24,7 @@ export declare const paymentRouter: import("@trpc/server").TRPCBuiltRouter<{
24
24
  planId: string;
25
25
  };
26
26
  output: {
27
- url: any;
27
+ url: string | null;
28
28
  };
29
29
  meta: object;
30
30
  }>;
@@ -53,7 +53,7 @@ export declare const paymentRouter: import("@trpc/server").TRPCBuiltRouter<{
53
53
  quantity?: number | undefined;
54
54
  };
55
55
  output: {
56
- url: any;
56
+ url: string | null;
57
57
  };
58
58
  meta: object;
59
59
  }>;
@@ -94,6 +94,7 @@ export declare const workspace: import("@trpc/server").TRPCBuiltRouter<{
94
94
  name: string;
95
95
  description?: string | undefined;
96
96
  parentId?: string | undefined;
97
+ markerColor?: string | null | undefined;
97
98
  };
98
99
  output: {
99
100
  id: string;
@@ -153,6 +153,7 @@ export const workspace = router({
153
153
  name: z.string().min(1).max(100),
154
154
  description: z.string().max(500).optional(),
155
155
  parentId: z.string().optional(),
156
+ markerColor: z.string().nullable().optional(),
156
157
  }))
157
158
  .mutation(async ({ ctx, input }) => {
158
159
  const ws = await ctx.db.workspace.create({
@@ -161,6 +162,7 @@ export const workspace = router({
161
162
  description: input.description,
162
163
  ownerId: ctx.session.user.id,
163
164
  folderId: input.parentId ?? null,
165
+ ...(input.markerColor !== undefined ? { markerColor: input.markerColor } : {}),
164
166
  artifacts: {
165
167
  create: {
166
168
  type: ArtifactType.FLASHCARD_SET,
package/dist/trpc.d.ts CHANGED
@@ -24,7 +24,7 @@ export declare const authedProcedure: import("@trpc/server").TRPCProcedureBuilde
24
24
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
25
25
  res: import("express").Response<any, Record<string, any>>;
26
26
  db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
27
- cookies: Record<string, string | undefined>;
27
+ cookies: import("cookie").Cookies;
28
28
  }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
29
29
  export declare const verifiedProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
30
30
  userId: any;
@@ -32,7 +32,7 @@ export declare const verifiedProcedure: import("@trpc/server").TRPCProcedureBuil
32
32
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
33
33
  res: import("express").Response<any, Record<string, any>>;
34
34
  db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
35
- cookies: Record<string, string | undefined>;
35
+ cookies: import("cookie").Cookies;
36
36
  }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
37
37
  export declare const adminProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
38
38
  userId: any;
@@ -40,7 +40,7 @@ export declare const adminProcedure: import("@trpc/server").TRPCProcedureBuilder
40
40
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
41
41
  res: import("express").Response<any, Record<string, any>>;
42
42
  db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
43
- cookies: Record<string, string | undefined>;
43
+ cookies: import("cookie").Cookies;
44
44
  }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
45
45
  export declare const limitedProcedure: import("@trpc/server").TRPCProcedureBuilder<Context, object, {
46
46
  userId: any;
@@ -48,5 +48,5 @@ export declare const limitedProcedure: import("@trpc/server").TRPCProcedureBuild
48
48
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
49
49
  res: import("express").Response<any, Record<string, any>>;
50
50
  db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
51
- cookies: Record<string, string | undefined>;
51
+ cookies: import("cookie").Cookies;
52
52
  }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goscribe/server",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,7 +16,9 @@
16
16
  "build": "npx prisma generate && tsc -p .",
17
17
  "start": "node --experimental-specifier-resolution=node dist/server.js",
18
18
  "test": "tsx --test src/lib/worksheet-generation.test.ts",
19
- "test:activity": "tsx --test src/lib/activity_log_service.test.ts src/lib/activity_human_description.test.ts"
19
+ "test:activity": "tsx --test src/lib/activity_log_service.test.ts src/lib/activity_human_description.test.ts",
20
+ "generate": "npx prisma generate",
21
+ "prepublishOnly": "npm run generate && npm run build"
20
22
  },
21
23
  "prisma": {
22
24
  "seed": "node --experimental-specifier-resolution=node prisma/seed.mjs"
package/src/lib/env.ts CHANGED
@@ -42,7 +42,7 @@ const envSchema = z.object({
42
42
  SMTP_USER: z.string().optional(),
43
43
  SMTP_PASSWORD: z.string().optional(),
44
44
  SMTP_SECURE: z.enum(['true', 'false']).default('false').transform((v) => v === 'true'),
45
- EMAIL_FROM: z.string().default('Scribe <noreply@goscribe.app>'),
45
+ EMAIL_FROM: z.string().default('Scribe <hello@scribe.study>'),
46
46
  // Stripe
47
47
  STRIPE_SECRET_KEY: z.string().startsWith('sk_').optional(),
48
48
  STRIPE_SUCCESS_URL: z.string().url().default('http://localhost:3000/payment-success'),
@@ -574,7 +574,7 @@ export async function getUserStorageLimit(userId: string): Promise<number> {
574
574
  }
575
575
 
576
576
  // Default limit (fallback for users with no active subscription)
577
- return 0;
577
+ return 1024 * 1024 * 1024; // 1GB
578
578
  }
579
579
 
580
580
  /**
@@ -19,6 +19,18 @@ import {
19
19
  notifyAdminsOnSignup,
20
20
  } from '../lib/notification-service.js';
21
21
 
22
+ function getAuthCookieConfig(isProduction: boolean) {
23
+ return {
24
+ httpOnly: true,
25
+ secure: isProduction,
26
+ sameSite: (isProduction ? 'none' : 'lax') as 'none' | 'lax',
27
+ path: '/',
28
+ maxAge: 60 * 60 * 24 * 30,
29
+ // Use parent domain in production so scribe.study and api.scribe.study share auth state.
30
+ domain: isProduction ? '.scribe.study' : undefined,
31
+ };
32
+ }
33
+
22
34
  // Helper to create custom auth token
23
35
  const passwordFieldSchema = z
24
36
  .string()
@@ -322,14 +334,7 @@ export const auth = router({
322
334
 
323
335
  const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER) as boolean;
324
336
 
325
- const cookieValue = serialize("auth_token", authToken, {
326
- httpOnly: true,
327
- secure: isProduction, // true for production/HTTPS, false for localhost
328
- sameSite: isProduction ? "none" : "lax", // none for cross-origin, lax for same-origin
329
- path: "/",
330
- domain: isProduction ? "server-w8mz.onrender.com" : undefined,
331
- maxAge: 60 * 60 * 24 * 30, // 30 days
332
- });
337
+ const cookieValue = serialize("auth_token", authToken, getAuthCookieConfig(isProduction));
333
338
 
334
339
  ctx.res.setHeader("Set-Cookie", cookieValue);
335
340
 
@@ -513,11 +518,10 @@ export const auth = router({
513
518
  }
514
519
 
515
520
  // Log out user by clearing cookie
521
+ const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER) as boolean;
522
+ const clearCookieConfig = getAuthCookieConfig(isProduction);
516
523
  ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
517
- httpOnly: true,
518
- secure: process.env.NODE_ENV === "production",
519
- sameSite: "lax",
520
- path: "/",
524
+ ...clearCookieConfig,
521
525
  maxAge: 0,
522
526
  }));
523
527
 
@@ -570,11 +574,10 @@ export const auth = router({
570
574
  // custom HMAC auth system (auth_token cookie).
571
575
 
572
576
 
577
+ const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER) as boolean;
578
+ const clearCookieConfig = getAuthCookieConfig(isProduction);
573
579
  ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
574
- httpOnly: true,
575
- secure: process.env.NODE_ENV === "production",
576
- sameSite: "lax",
577
- path: "/",
580
+ ...clearCookieConfig,
578
581
  maxAge: 0, // Expire immediately
579
582
  }));
580
583
 
@@ -11,6 +11,8 @@ import {
11
11
  } from '../lib/notification-service.js';
12
12
  import { upsertSubscriptionFromStripe } from '../lib/subscription_service.js';
13
13
  import { ArtifactType } from '../lib/prisma.js';
14
+ import { PrismaClient } from '@prisma/client';
15
+ import { Stripe } from 'stripe';
14
16
 
15
17
  const ArtifactTypeUnion = z.enum(['STUDY_GUIDE', 'FLASHCARD_SET', 'WORKSHEET', 'MEETING_SUMMARY', 'PODCAST_EPISODE', 'STORAGE']);
16
18
 
@@ -193,6 +193,7 @@ export const workspace = router({
193
193
  name: z.string().min(1).max(100),
194
194
  description: z.string().max(500).optional(),
195
195
  parentId: z.string().optional(),
196
+ markerColor: z.string().nullable().optional(),
196
197
  }))
197
198
  .mutation(async ({ ctx, input }) => {
198
199
  const ws = await ctx.db.workspace.create({
@@ -201,6 +202,7 @@ export const workspace = router({
201
202
  description: input.description,
202
203
  ownerId: ctx.session.user.id,
203
204
  folderId: input.parentId ?? null,
205
+ ...(input.markerColor !== undefined ? { markerColor: input.markerColor } : {}),
204
206
  artifacts: {
205
207
  create: {
206
208
  type: ArtifactType.FLASHCARD_SET,
File without changes
@@ -1,377 +0,0 @@
1
- "use strict";
2
- // import { z } from 'zod';
3
- // import { TRPCError } from '@trpc/server';
4
- // import { router, authedProcedure } from '../trpc.js';
5
- // import OpenAI from 'openai';
6
- // import fs from 'fs';
7
- // import path from 'path';
8
- // import { v4 as uuidv4 } from 'uuid';
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
- // // Initialize OpenAI client
18
- // const openai = new OpenAI({
19
- // apiKey: process.env.OPENAI_API_KEY,
20
- // });
21
- // // Meeting summary schema for structured data
22
- // const meetingSchema = z.object({
23
- // title: z.string(),
24
- // participants: z.array(z.string()),
25
- // date: z.string(),
26
- // duration: z.string().optional(),
27
- // agenda: z.array(z.string()).optional(),
28
- // transcript: z.string().optional(),
29
- // notes: z.string().optional(),
30
- // });
31
- // export const meetingSummarize = router({
32
- // // List all meeting summaries for a workspace
33
- // listSummaries: authedProcedure
34
- // .input(z.object({ workspaceId: z.string() }))
35
- // .query(async ({ ctx, input }) => {
36
- // const workspace = await ctx.db.workspace.findFirst({
37
- // where: { id: input.workspaceId, ownerId: ctx.session.user.id },
38
- // });
39
- // if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
40
- // return ctx.db.artifact.findMany({
41
- // where: {
42
- // workspaceId: input.workspaceId,
43
- // type: ArtifactType.MEETING_SUMMARY
44
- // },
45
- // include: {
46
- // versions: {
47
- // orderBy: { version: 'desc' },
48
- // take: 1, // Get only the latest version
49
- // },
50
- // },
51
- // orderBy: { updatedAt: 'desc' },
52
- // });
53
- // }),
54
- // // Get a specific meeting summary
55
- // getSummary: authedProcedure
56
- // .input(z.object({ summaryId: z.string() }))
57
- // .query(async ({ ctx, input }) => {
58
- // const summary = await ctx.db.artifact.findFirst({
59
- // where: {
60
- // id: input.summaryId,
61
- // type: ArtifactType.MEETING_SUMMARY,
62
- // workspace: { ownerId: ctx.session.user.id }
63
- // },
64
- // include: {
65
- // versions: {
66
- // orderBy: { version: 'desc' },
67
- // take: 1,
68
- // },
69
- // },
70
- // });
71
- // if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
72
- // return summary;
73
- // }),
74
- // // Upload and process audio/video file
75
- // uploadFile: authedProcedure
76
- // .input(z.object({
77
- // workspaceId: z.string(),
78
- // fileName: z.string(),
79
- // fileBuffer: z.string(), // Base64 encoded file
80
- // mimeType: z.string(),
81
- // title: z.string().optional(),
82
- // }))
83
- // .mutation(async ({ ctx, input }) => {
84
- // const workspace = await ctx.db.workspace.findFirst({
85
- // where: { id: input.workspaceId, ownerId: ctx.session.user.id },
86
- // });
87
- // if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
88
- // // Validate file type
89
- // const allowedTypes = ['audio/mpeg', 'audio/mp3', 'video/mp4', 'audio/wav', 'audio/m4a'];
90
- // if (!allowedTypes.includes(input.mimeType)) {
91
- // throw new TRPCError({
92
- // code: 'BAD_REQUEST',
93
- // message: 'Unsupported file type. Please upload MP3, MP4, WAV, or M4A files.'
94
- // });
95
- // }
96
- // try {
97
- // // Create temporary file
98
- // const tempDir = path.join(process.cwd(), 'temp');
99
- // if (!fs.existsSync(tempDir)) {
100
- // fs.mkdirSync(tempDir, { recursive: true });
101
- // }
102
- // const fileId = uuidv4();
103
- // const fileExtension = path.extname(input.fileName);
104
- // const tempFilePath = path.join(tempDir, `${fileId}${fileExtension}`);
105
- // // Write buffer to temporary file
106
- // const fileBuffer = Buffer.from(input.fileBuffer, 'base64');
107
- // fs.writeFileSync(tempFilePath, fileBuffer);
108
- // // Transcribe audio using OpenAI Whisper
109
- // const transcript = await openai.audio.transcriptions.create({
110
- // file: fs.createReadStream(tempFilePath),
111
- // model: 'whisper-1',
112
- // response_format: 'text',
113
- // });
114
- // // Generate meeting summary using GPT
115
- // const summaryResponse = await openai.chat.completions.create({
116
- // model: 'gpt-4-turbo-preview',
117
- // messages: [
118
- // {
119
- // role: 'system',
120
- // content: `You are a meeting summarizer. Given a meeting transcript, create a comprehensive summary that includes:
121
- // 1. Meeting title/topic
122
- // 2. Key participants mentioned
123
- // 3. Main discussion points
124
- // 4. Action items
125
- // 5. Decisions made
126
- // 6. Next steps
127
- // Format the response as JSON with the following structure:
128
- // {
129
- // "title": "Meeting title",
130
- // "participants": ["participant1", "participant2"],
131
- // "keyPoints": ["point1", "point2"],
132
- // "actionItems": ["action1", "action2"],
133
- // "decisions": ["decision1", "decision2"],
134
- // "nextSteps": ["step1", "step2"],
135
- // "summary": "Overall meeting summary"
136
- // }`
137
- // },
138
- // {
139
- // role: 'user',
140
- // content: `Please summarize this meeting transcript: ${transcript}`
141
- // }
142
- // ],
143
- // });
144
- // let summaryData;
145
- // try {
146
- // summaryData = JSON.parse(summaryResponse.choices[0]?.message?.content || '{}');
147
- // } catch (parseError) {
148
- // // Fallback if JSON parsing fails
149
- // summaryData = {
150
- // title: input.title || 'Meeting Summary',
151
- // participants: [],
152
- // keyPoints: [],
153
- // actionItems: [],
154
- // decisions: [],
155
- // nextSteps: [],
156
- // summary: summaryResponse.choices[0]?.message?.content || 'Unable to generate summary'
157
- // };
158
- // }
159
- // // Create artifact in database
160
- // const artifact = await ctx.db.artifact.create({
161
- // data: {
162
- // workspaceId: input.workspaceId,
163
- // type: ArtifactType.MEETING_SUMMARY,
164
- // title: summaryData.title || input.title || 'Meeting Summary',
165
- // content: JSON.stringify({
166
- // originalFileName: input.fileName,
167
- // transcript: transcript,
168
- // ...summaryData
169
- // }),
170
- // },
171
- // });
172
- // // Create initial version
173
- // await ctx.db.artifactVersion.create({
174
- // data: {
175
- // artifactId: artifact.id,
176
- // version: 1,
177
- // content: artifact.content,
178
- // },
179
- // });
180
- // // Clean up temporary file
181
- // fs.unlinkSync(tempFilePath);
182
- // return {
183
- // id: artifact.id,
184
- // title: artifact.title,
185
- // summary: summaryData,
186
- // transcript: transcript,
187
- // };
188
- // } catch (error) {
189
- // console.error('Error processing meeting file:', error);
190
- // throw new TRPCError({
191
- // code: 'INTERNAL_SERVER_ERROR',
192
- // message: 'Failed to process meeting file'
193
- // });
194
- // }
195
- // }),
196
- // // Process meeting data from schema
197
- // processSchema: authedProcedure
198
- // .input(z.object({
199
- // workspaceId: z.string(),
200
- // meetingData: meetingSchema,
201
- // }))
202
- // .mutation(async ({ ctx, input }) => {
203
- // const workspace = await ctx.db.workspace.findFirst({
204
- // where: { id: input.workspaceId, ownerId: ctx.session.user.id },
205
- // });
206
- // if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
207
- // try {
208
- // // Create content for AI processing
209
- // const meetingContent = `
210
- // Meeting Title: ${input.meetingData.title}
211
- // Participants: ${input.meetingData.participants.join(', ')}
212
- // Date: ${input.meetingData.date}
213
- // Duration: ${input.meetingData.duration || 'Not specified'}
214
- // Agenda: ${input.meetingData.agenda?.join(', ') || 'Not provided'}
215
- // Notes: ${input.meetingData.notes || 'Not provided'}
216
- // Transcript: ${input.meetingData.transcript || 'Not provided'}
217
- // `;
218
- // // Generate enhanced summary using GPT
219
- // const summaryResponse = await openai.chat.completions.create({
220
- // model: 'gpt-4-turbo-preview',
221
- // messages: [
222
- // {
223
- // role: 'system',
224
- // content: `You are a meeting summarizer. Given meeting information, create a comprehensive summary that includes:
225
- // 1. Key discussion points
226
- // 2. Action items
227
- // 3. Decisions made
228
- // 4. Next steps
229
- // 5. Important insights
230
- // Format the response as JSON with the following structure:
231
- // {
232
- // "keyPoints": ["point1", "point2"],
233
- // "actionItems": ["action1", "action2"],
234
- // "decisions": ["decision1", "decision2"],
235
- // "nextSteps": ["step1", "step2"],
236
- // "insights": ["insight1", "insight2"],
237
- // "summary": "Overall meeting summary"
238
- // }`
239
- // },
240
- // {
241
- // role: 'user',
242
- // content: `Please analyze and summarize this meeting information: ${meetingContent}`
243
- // }
244
- // ],
245
- // });
246
- // let summaryData;
247
- // try {
248
- // summaryData = JSON.parse(summaryResponse.choices[0]?.message?.content || '{}');
249
- // } catch (parseError) {
250
- // // Fallback if JSON parsing fails
251
- // summaryData = {
252
- // keyPoints: [],
253
- // actionItems: [],
254
- // decisions: [],
255
- // nextSteps: [],
256
- // insights: [],
257
- // summary: summaryResponse.choices[0]?.message?.content || 'Unable to generate summary'
258
- // };
259
- // }
260
- // // Create artifact in database
261
- // const artifact = await ctx.db.artifact.create({
262
- // data: {
263
- // workspaceId: input.workspaceId,
264
- // type: ArtifactType.MEETING_SUMMARY,
265
- // title: input.meetingData.title,
266
- // content: JSON.stringify({
267
- // originalData: input.meetingData,
268
- // ...summaryData
269
- // }),
270
- // },
271
- // });
272
- // // Create initial version
273
- // await ctx.db.artifactVersion.create({
274
- // data: {
275
- // artifactId: artifact.id,
276
- // version: 1,
277
- // content: artifact.content,
278
- // },
279
- // });
280
- // return {
281
- // id: artifact.id,
282
- // title: artifact.title,
283
- // summary: summaryData,
284
- // originalData: input.meetingData,
285
- // };
286
- // } catch (error) {
287
- // console.error('Error processing meeting schema:', error);
288
- // throw new TRPCError({
289
- // code: 'INTERNAL_SERVER_ERROR',
290
- // message: 'Failed to process meeting data'
291
- // });
292
- // }
293
- // }),
294
- // // Update an existing meeting summary
295
- // updateSummary: authedProcedure
296
- // .input(z.object({
297
- // summaryId: z.string(),
298
- // title: z.string().optional(),
299
- // content: z.string().optional(),
300
- // }))
301
- // .mutation(async ({ ctx, input }) => {
302
- // const summary = await ctx.db.artifact.findFirst({
303
- // where: {
304
- // id: input.summaryId,
305
- // type: ArtifactType.MEETING_SUMMARY,
306
- // workspace: { ownerId: ctx.session.user.id }
307
- // },
308
- // include: {
309
- // versions: {
310
- // orderBy: { version: 'desc' },
311
- // take: 1,
312
- // },
313
- // },
314
- // });
315
- // if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
316
- // // Update artifact
317
- // const updatedArtifact = await ctx.db.artifact.update({
318
- // where: { id: input.summaryId },
319
- // data: {
320
- // title: input.title ?? summary.title,
321
- // content: input.content ?? summary.content,
322
- // updatedAt: new Date(),
323
- // },
324
- // });
325
- // // Create new version if content changed
326
- // if (input.content && input.content !== summary.content) {
327
- // const latestVersion = summary.versions[0]?.version || 0;
328
- // await ctx.db.artifactVersion.create({
329
- // data: {
330
- // artifactId: input.summaryId,
331
- // version: latestVersion + 1,
332
- // content: input.content,
333
- // },
334
- // });
335
- // }
336
- // return updatedArtifact;
337
- // }),
338
- // // Delete a meeting summary
339
- // deleteSummary: authedProcedure
340
- // .input(z.object({ summaryId: z.string() }))
341
- // .mutation(async ({ ctx, input }) => {
342
- // const summary = await ctx.db.artifact.findFirst({
343
- // where: {
344
- // id: input.summaryId,
345
- // type: ArtifactType.MEETING_SUMMARY,
346
- // workspace: { ownerId: ctx.session.user.id }
347
- // },
348
- // });
349
- // if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
350
- // // Delete associated versions first
351
- // await ctx.db.artifactVersion.deleteMany({
352
- // where: { artifactId: input.summaryId },
353
- // });
354
- // // Delete the artifact
355
- // await ctx.db.artifact.delete({
356
- // where: { id: input.summaryId },
357
- // });
358
- // return true;
359
- // }),
360
- // // Get meeting versions/history
361
- // getVersions: authedProcedure
362
- // .input(z.object({ summaryId: z.string() }))
363
- // .query(async ({ ctx, input }) => {
364
- // const summary = await ctx.db.artifact.findFirst({
365
- // where: {
366
- // id: input.summaryId,
367
- // type: ArtifactType.MEETING_SUMMARY,
368
- // workspace: { ownerId: ctx.session.user.id }
369
- // },
370
- // });
371
- // if (!summary) throw new TRPCError({ code: 'NOT_FOUND' });
372
- // return ctx.db.artifactVersion.findMany({
373
- // where: { artifactId: input.summaryId },
374
- // orderBy: { version: 'desc' },
375
- // });
376
- // }),
377
- // });