Add user profile API with name, title, company, phone, signature
This commit is contained in:
@@ -12,6 +12,18 @@ export const users = pgTable('users', {
|
|||||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
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
|
// BetterAuth session table
|
||||||
export const sessions = pgTable('sessions', {
|
export const sessions = pgTable('sessions', {
|
||||||
id: text('id').primaryKey(),
|
id: text('id').primaryKey(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { auth } from './lib/auth';
|
|||||||
import { clientRoutes } from './routes/clients';
|
import { clientRoutes } from './routes/clients';
|
||||||
import { emailRoutes } from './routes/emails';
|
import { emailRoutes } from './routes/emails';
|
||||||
import { eventRoutes } from './routes/events';
|
import { eventRoutes } from './routes/events';
|
||||||
|
import { profileRoutes } from './routes/profile';
|
||||||
import type { User } from './lib/auth';
|
import type { User } from './lib/auth';
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
@@ -41,6 +42,7 @@ const app = new Elysia()
|
|||||||
.use(clientRoutes)
|
.use(clientRoutes)
|
||||||
.use(emailRoutes)
|
.use(emailRoutes)
|
||||||
.use(eventRoutes)
|
.use(eventRoutes)
|
||||||
|
.use(profileRoutes)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error handler
|
// Error handler
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Elysia, t } from 'elysia';
|
import { Elysia, t } from 'elysia';
|
||||||
import { db } from '../db';
|
import { db } from '../db';
|
||||||
import { clients, communications } from '../db/schema';
|
import { clients, communications, userProfiles } from '../db/schema';
|
||||||
import { eq, and } from 'drizzle-orm';
|
import { eq, and } from 'drizzle-orm';
|
||||||
import { generateEmail, generateSubject, generateBirthdayMessage, type AIProvider } from '../services/ai';
|
import { generateEmail, generateSubject, generateBirthdayMessage, type AIProvider } from '../services/ai';
|
||||||
import { sendEmail } from '../services/email';
|
import { sendEmail } from '../services/email';
|
||||||
@@ -26,6 +26,21 @@ export const emailRoutes = new Elysia({ prefix: '/emails' })
|
|||||||
throw new Error('Client not found');
|
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
|
// Generate email content
|
||||||
console.log(`[${new Date().toISOString()}] Generating email for client ${client.firstName}, purpose: ${body.purpose}`);
|
console.log(`[${new Date().toISOString()}] Generating email for client ${client.firstName}, purpose: ${body.purpose}`);
|
||||||
let content: string;
|
let content: string;
|
||||||
@@ -33,7 +48,11 @@ export const emailRoutes = new Elysia({ prefix: '/emails' })
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
content = await generateEmail({
|
content = await generateEmail({
|
||||||
advisorName: user.name,
|
advisorName: advisorInfo.name,
|
||||||
|
advisorTitle: advisorInfo.title,
|
||||||
|
advisorCompany: advisorInfo.company,
|
||||||
|
advisorPhone: advisorInfo.phone,
|
||||||
|
advisorSignature: advisorInfo.signature,
|
||||||
clientName: client.firstName,
|
clientName: client.firstName,
|
||||||
interests: client.interests || [],
|
interests: client.interests || [],
|
||||||
notes: client.notes || '',
|
notes: client.notes || '',
|
||||||
|
|||||||
99
src/routes/profile.ts
Normal file
99
src/routes/profile.ts
Normal 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()),
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -29,18 +29,30 @@ const emailPrompt = ChatPromptTemplate.fromMessages([
|
|||||||
['system', `You are a professional wealth advisor writing to a valued client.
|
['system', `You are a professional wealth advisor writing to a valued client.
|
||||||
Maintain a warm but professional tone. Incorporate personal details naturally.
|
Maintain a warm but professional tone. Incorporate personal details naturally.
|
||||||
Keep emails concise (3-4 paragraphs max).
|
Keep emails concise (3-4 paragraphs max).
|
||||||
Do not include subject line - just the body.`],
|
Do not include subject line - just the body.
|
||||||
['human', `Advisor: {advisorName}
|
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}
|
Client: {clientName}
|
||||||
Their interests: {interests}
|
Their interests: {interests}
|
||||||
Recent notes: {notes}
|
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 {
|
export interface GenerateEmailParams {
|
||||||
advisorName: string;
|
advisorName: string;
|
||||||
|
advisorTitle?: string;
|
||||||
|
advisorCompany?: string;
|
||||||
|
advisorPhone?: string;
|
||||||
|
advisorSignature?: string;
|
||||||
clientName: string;
|
clientName: string;
|
||||||
interests: string[];
|
interests: string[];
|
||||||
notes: string;
|
notes: string;
|
||||||
@@ -55,6 +67,10 @@ export async function generateEmail(params: GenerateEmailParams): Promise<string
|
|||||||
|
|
||||||
const response = await chain.invoke({
|
const response = await chain.invoke({
|
||||||
advisorName: params.advisorName,
|
advisorName: params.advisorName,
|
||||||
|
advisorTitle: params.advisorTitle || 'Wealth Advisor',
|
||||||
|
advisorCompany: params.advisorCompany || '',
|
||||||
|
advisorPhone: params.advisorPhone || '',
|
||||||
|
advisorSignature: params.advisorSignature || '',
|
||||||
clientName: params.clientName,
|
clientName: params.clientName,
|
||||||
interests: params.interests.join(', ') || 'not specified',
|
interests: params.interests.join(', ') || 'not specified',
|
||||||
notes: params.notes || 'No recent notes',
|
notes: params.notes || 'No recent notes',
|
||||||
|
|||||||
Reference in New Issue
Block a user