From 4bbb9c05867544efab854b27495e9a40ee306c2f Mon Sep 17 00:00:00 2001 From: Hammer Date: Tue, 27 Jan 2026 22:59:21 +0000 Subject: [PATCH] Add user profile API with name, title, company, phone, signature --- src/db/schema.ts | 12 ++++++ src/index.ts | 2 + src/routes/emails.ts | 23 +++++++++- src/routes/profile.ts | 99 +++++++++++++++++++++++++++++++++++++++++++ src/services/ai.ts | 24 +++++++++-- 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src/routes/profile.ts diff --git a/src/db/schema.ts b/src/db/schema.ts index 927d30f..b9deaa2 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -12,6 +12,18 @@ export const users = pgTable('users', { updatedAt: timestamp('updated_at').defaultNow().notNull(), }); +// User profile (additional settings beyond BetterAuth) +export const userProfiles = pgTable('user_profiles', { + id: uuid('id').primaryKey().defaultRandom(), + userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull().unique(), + title: text('title'), // e.g., "Senior Wealth Advisor" + company: text('company'), // e.g., "ABC Financial Group" + phone: text('phone'), + emailSignature: text('email_signature'), // Custom signature block + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + // BetterAuth session table export const sessions = pgTable('sessions', { id: text('id').primaryKey(), diff --git a/src/index.ts b/src/index.ts index d6ce1b8..f0faf90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { auth } from './lib/auth'; import { clientRoutes } from './routes/clients'; import { emailRoutes } from './routes/emails'; import { eventRoutes } from './routes/events'; +import { profileRoutes } from './routes/profile'; import type { User } from './lib/auth'; const app = new Elysia() @@ -41,6 +42,7 @@ const app = new Elysia() .use(clientRoutes) .use(emailRoutes) .use(eventRoutes) + .use(profileRoutes) ) // Error handler diff --git a/src/routes/emails.ts b/src/routes/emails.ts index 4679a8b..188f524 100644 --- a/src/routes/emails.ts +++ b/src/routes/emails.ts @@ -1,6 +1,6 @@ import { Elysia, t } from 'elysia'; import { db } from '../db'; -import { clients, communications } from '../db/schema'; +import { clients, communications, userProfiles } from '../db/schema'; import { eq, and } from 'drizzle-orm'; import { generateEmail, generateSubject, generateBirthdayMessage, type AIProvider } from '../services/ai'; import { sendEmail } from '../services/email'; @@ -26,6 +26,21 @@ export const emailRoutes = new Elysia({ prefix: '/emails' }) throw new Error('Client not found'); } + // Get user profile for signature + const [profile] = await db.select() + .from(userProfiles) + .where(eq(userProfiles.userId, user.id)) + .limit(1); + + // Build advisor info + const advisorInfo = { + name: user.name, + title: profile?.title || '', + company: profile?.company || '', + phone: profile?.phone || '', + signature: profile?.emailSignature || '', + }; + // Generate email content console.log(`[${new Date().toISOString()}] Generating email for client ${client.firstName}, purpose: ${body.purpose}`); let content: string; @@ -33,7 +48,11 @@ export const emailRoutes = new Elysia({ prefix: '/emails' }) try { content = await generateEmail({ - advisorName: user.name, + advisorName: advisorInfo.name, + advisorTitle: advisorInfo.title, + advisorCompany: advisorInfo.company, + advisorPhone: advisorInfo.phone, + advisorSignature: advisorInfo.signature, clientName: client.firstName, interests: client.interests || [], notes: client.notes || '', diff --git a/src/routes/profile.ts b/src/routes/profile.ts new file mode 100644 index 0000000..b5fb8f0 --- /dev/null +++ b/src/routes/profile.ts @@ -0,0 +1,99 @@ +import { Elysia, t } from 'elysia'; +import { db } from '../db'; +import { users, userProfiles } from '../db/schema'; +import { eq } from 'drizzle-orm'; +import type { User } from '../lib/auth'; + +export const profileRoutes = new Elysia({ prefix: '/profile' }) + // Get current user's profile + .get('/', async ({ user }: { user: User }) => { + // Get user and profile + const [profile] = await db.select() + .from(userProfiles) + .where(eq(userProfiles.userId, user.id)) + .limit(1); + + return { + id: user.id, + name: user.name, + email: user.email, + title: profile?.title || null, + company: profile?.company || null, + phone: profile?.phone || null, + emailSignature: profile?.emailSignature || null, + }; + }) + + // Update profile + .put('/', async ({ body, user }: { + body: { + name?: string; + title?: string; + company?: string; + phone?: string; + emailSignature?: string; + }; + user: User; + }) => { + // Update user name if provided + if (body.name) { + await db.update(users) + .set({ name: body.name, updatedAt: new Date() }) + .where(eq(users.id, user.id)); + } + + // Upsert profile + const profileData = { + title: body.title, + company: body.company, + phone: body.phone, + emailSignature: body.emailSignature, + updatedAt: new Date(), + }; + + const [existing] = await db.select() + .from(userProfiles) + .where(eq(userProfiles.userId, user.id)) + .limit(1); + + if (existing) { + await db.update(userProfiles) + .set(profileData) + .where(eq(userProfiles.userId, user.id)); + } else { + await db.insert(userProfiles).values({ + userId: user.id, + ...profileData, + createdAt: new Date(), + }); + } + + // Return updated profile + const [profile] = await db.select() + .from(userProfiles) + .where(eq(userProfiles.userId, user.id)) + .limit(1); + + const [updatedUser] = await db.select() + .from(users) + .where(eq(users.id, user.id)) + .limit(1); + + return { + id: user.id, + name: updatedUser?.name || user.name, + email: user.email, + title: profile?.title || null, + company: profile?.company || null, + phone: profile?.phone || null, + emailSignature: profile?.emailSignature || null, + }; + }, { + body: t.Object({ + name: t.Optional(t.String({ minLength: 1 })), + title: t.Optional(t.String()), + company: t.Optional(t.String()), + phone: t.Optional(t.String()), + emailSignature: t.Optional(t.String()), + }), + }); diff --git a/src/services/ai.ts b/src/services/ai.ts index 47ae160..1e00d06 100644 --- a/src/services/ai.ts +++ b/src/services/ai.ts @@ -29,18 +29,30 @@ const emailPrompt = ChatPromptTemplate.fromMessages([ ['system', `You are a professional wealth advisor writing to a valued client. Maintain a warm but professional tone. Incorporate personal details naturally. Keep emails concise (3-4 paragraphs max). -Do not include subject line - just the body.`], - ['human', `Advisor: {advisorName} +Do not include subject line - just the body. +Always sign off with the advisor's actual name and details provided. Never use placeholders like [Your Name].`], + ['human', `Advisor Info: +- Name: {advisorName} +- Title: {advisorTitle} +- Company: {advisorCompany} +- Phone: {advisorPhone} +- Custom signature: {advisorSignature} + Client: {clientName} Their interests: {interests} Recent notes: {notes} -Purpose: {purpose} -Generate a personalized email that feels genuine, not templated.`], +Purpose of email: {purpose} + +Generate a personalized email that feels genuine, not templated. End with an appropriate signature using the advisor's real name and details.`], ]); export interface GenerateEmailParams { advisorName: string; + advisorTitle?: string; + advisorCompany?: string; + advisorPhone?: string; + advisorSignature?: string; clientName: string; interests: string[]; notes: string; @@ -55,6 +67,10 @@ export async function generateEmail(params: GenerateEmailParams): Promise