Add user profile API with name, title, company, phone, signature

This commit is contained in:
2026-01-27 22:59:21 +00:00
parent 3a5f7b2c17
commit 4bbb9c0586
5 changed files with 154 additions and 6 deletions

View File

@@ -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(),

View File

@@ -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

View File

@@ -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 || '',

99
src/routes/profile.ts Normal file
View File

@@ -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()),
}),
});

View File

@@ -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<string
const response = await chain.invoke({
advisorName: params.advisorName,
advisorTitle: params.advisorTitle || 'Wealth Advisor',
advisorCompany: params.advisorCompany || '',
advisorPhone: params.advisorPhone || '',
advisorSignature: params.advisorSignature || '',
clientName: params.clientName,
interests: params.interests.join(', ') || 'not specified',
notes: params.notes || 'No recent notes',