import type { Pool, PoolClient } from 'pg'; import type { AccountPageData, AccountWorkspace, AppUser, WorkspaceType, WorkspaceRole } from '../../../shared/types.js'; import { getWorkspaceBillingState } from '../billing/service.js'; type DbClient = Pool | PoolClient; type WorkspaceRow = { id: string; name: string; workspace_type: WorkspaceType; role: WorkspaceRole; member_count: string; }; type SummaryRow = { total_search_jobs: string; total_deep_research_batches: string; total_businesses: string; }; export function buildDefaultWorkspaceName(user: { displayName?: string | null; email: string }) { const baseName = user.displayName?.trim() || user.email.split('@')[0] || 'User'; return `${baseName}'s Workspace`; } export async function createDefaultWorkspaceForUser( db: DbClient, user: { id: string; email: string; displayName?: string | null }, ) { const workspaceResult = await db.query<{ id: string }>( ` insert into public.workspaces (name, workspace_type) values ($1, 'personal') returning id `, [buildDefaultWorkspaceName(user)], ); const workspaceId = workspaceResult.rows[0].id; await db.query( ` insert into public.workspace_memberships (workspace_id, user_id, role) values ($1, $2, 'owner') `, [workspaceId, user.id], ); return workspaceId; } export async function ensureWorkspaceForUser( db: DbClient, user: { id: string; email: string; displayName?: string | null }, ) { const existingWorkspace = await getPrimaryWorkspaceForUser(db, user.id); if (existingWorkspace) { return existingWorkspace; } await createDefaultWorkspaceForUser(db, user); return getPrimaryWorkspaceForUser(db, user.id); } export async function getPrimaryWorkspaceForUser(db: DbClient, userId: string): Promise { const result = await db.query( ` select w.id, w.name, w.workspace_type, wm.role, ( select count(*)::text from public.workspace_memberships member where member.workspace_id = w.id ) as member_count from public.workspace_memberships wm join public.workspaces w on w.id = wm.workspace_id where wm.user_id = $1 order by wm.created_at asc limit 1 `, [userId], ); if (result.rowCount === 0) { return null; } return mapWorkspaceRow(result.rows[0]); } export async function getAccountSummaryForUser(db: DbClient, userId: string) { const result = await db.query( ` select (select count(*)::text from public.search_jobs where user_id = $1) as total_search_jobs, (select count(*)::text from public.deep_research_batches where user_id = $1) as total_deep_research_batches, (select count(*)::text from public.businesses where user_id = $1) as total_businesses `, [userId], ); const row = result.rows[0]; return { totalSearchJobs: Number(row.total_search_jobs), totalDeepResearchBatches: Number(row.total_deep_research_batches), totalBusinesses: Number(row.total_businesses), }; } export async function updateUserProfile( db: DbClient, userId: string, input: { displayName?: string; avatarUrl?: string | null }, ): Promise { const result = await db.query<{ id: string; email: string; display_name: string | null; avatar_url: string | null; created_at: string; updated_at: string; }>( ` update public.users set display_name = coalesce($2, display_name), avatar_url = case when $3::boolean then nullif($4, '') else avatar_url end where id = $1 returning id, email, display_name, avatar_url, created_at, updated_at `, [userId, input.displayName?.trim() || null, Object.prototype.hasOwnProperty.call(input, 'avatarUrl'), input.avatarUrl?.trim() || null], ); return mapUserRow(result.rows[0]); } export async function updateWorkspaceName(db: DbClient, workspaceId: string, name: string) { await db.query( ` update public.workspaces set name = $2 where id = $1 `, [workspaceId, name.trim()], ); } export async function buildAccountPageData(db: DbClient, user: AppUser): Promise { const workspace = await ensureWorkspaceForUser(db, user); if (!workspace) { throw new Error('Failed to load workspace.'); } const summary = await getAccountSummaryForUser(db, user.id); const billing = await getWorkspaceBillingState(db, workspace.id); return { profile: user, workspace, summary, billing, team: { canManageMembers: workspace.role === 'owner' || workspace.role === 'admin', message: 'Workspace member management is coming soon.', }, }; } function mapWorkspaceRow(row: WorkspaceRow): AccountWorkspace { return { id: row.id, name: row.name, workspaceType: row.workspace_type, role: row.role, memberCount: Number(row.member_count), }; } function mapUserRow(row: { id: string; email: string; display_name: string | null; avatar_url: string | null; created_at: string; updated_at: string; }): AppUser { return { id: row.id, email: row.email, displayName: row.display_name || row.email.split('@')[0] || 'User', avatarUrl: row.avatar_url, createdAt: row.created_at, updatedAt: row.updated_at, }; }