# Customer Domain - Type Naming Clarity ## The Problem Current naming is confusing: - `PortalUser` - What is this? - `CustomerProfile` - Is this different from a user? - `UserProfile` - How is this different from the above? - `AuthenticatedUser` - Another alias? ## Proposed Clear Naming ### Core Principle: Same Entity, Different Data Sources A customer/user is **ONE business entity** with data from **TWO sources**: 1. **Portal Database** → Authentication & account state 2. **WHMCS** → Profile & billing information ## Recommended Schema Structure ```typescript // packages/domain/customer/types.ts // ============================================================================ // User Auth State (from Portal Database) // ============================================================================ /** * User authentication and account state stored in portal database * Source: Portal DB (Prisma) * Contains: Auth-related fields only */ export const userAuthSchema = z.object({ id: z.string().uuid(), // Portal user ID (primary key) email: z.string().email(), // Email (for login) role: z.enum(["USER", "ADMIN"]), // User role emailVerified: z.boolean(), // Email verification status mfaEnabled: z.boolean(), // MFA enabled flag lastLoginAt: z.string().optional(), // Last login timestamp createdAt: z.string(), // Account created date updatedAt: z.string(), // Account updated date }); export type UserAuth = z.infer; // ============================================================================ // Customer Profile Data (from WHMCS) // ============================================================================ /** * Customer profile and billing information from WHMCS * Source: WHMCS API * Contains: Personal info, address, preferences */ export const customerDataSchema = z.object({ whmcsClientId: z.number().int(), // WHMCS client ID firstname: z.string().nullable().optional(), lastname: z.string().nullable().optional(), fullname: z.string().nullable().optional(), companyname: z.string().nullable().optional(), phonenumber: z.string().nullable().optional(), address: customerAddressSchema.optional(), language: z.string().nullable().optional(), currencyCode: z.string().nullable().optional(), // ... other WHMCS fields }); export type CustomerData = z.infer; // ============================================================================ // Complete User (Auth + Profile Data) // ============================================================================ /** * Complete user profile combining auth state and customer data * Composition: UserAuth (portal) + CustomerData (WHMCS) * This is what gets returned in API responses */ export const userSchema = userAuthSchema.extend({ // Add customer data fields firstname: z.string().nullable().optional(), lastname: z.string().nullable().optional(), fullname: z.string().nullable().optional(), companyname: z.string().nullable().optional(), phonenumber: z.string().nullable().optional(), address: customerAddressSchema.optional(), language: z.string().nullable().optional(), currencyCode: z.string().nullable().optional(), }); /** * Complete user - exported as 'User' for simplicity * Use this in API responses and business logic */ export type User = z.infer; ``` ## Naming Comparison ### Option A: Auth/Data (Recommended) ```typescript UserAuth // Auth state from portal DB CustomerData // Profile data from WHMCS User // Complete entity ``` **Pros:** - Clear distinction: "Auth" = authentication, "Data" = profile information - Simple exports: `User` is the main type - Intuitive: "I need UserAuth for JWT" vs "I need User for profile API" **Cons:** - "CustomerData" might seem weird (but it's accurate - it's WHMCS customer data) ### Option B: Portal/Customer (Current-ish) ```typescript PortalUser // Auth state from portal DB CustomerProfile // Profile data from WHMCS User // Complete entity ``` **Pros:** - "Portal" clearly indicates source - "CustomerProfile" is business-domain language **Cons:** - "PortalUser" vs "User" - what's the difference? - Implies there are different "user" entities (there's only ONE user) ### Option C: Auth/Profile (Simple) ```typescript AuthUser // Auth state ProfileData // Profile info User // Complete entity ``` **Pros:** - Very simple and clear - Purpose-driven naming **Cons:** - Less obvious where data comes from ### Option D: Keep Descriptive (Verbose but Clear) ```typescript UserAuthState // Auth state from portal DB CustomerProfileData // Profile data from WHMCS UserWithProfile // Complete entity ``` **Pros:** - Extremely explicit - No ambiguity **Cons:** - Verbose - "UserWithProfile" is awkward ## Recommended: Option A ```typescript // Clear and intuitive import type { UserAuth, CustomerData, User } from "@customer-portal/domain/customer"; // In JWT strategy - only need auth state async validate(payload: JwtPayload): Promise { return this.getAuthState(payload.sub); } // In profile API - need complete user async getProfile(userId: string): Promise { const auth = await this.getAuthState(userId); const data = await this.getCustomerData(userId); return { ...auth, ...data, }; } ``` ## Provider Structure ``` packages/domain/customer/providers/ ├── portal/ │ ├── types.ts # PrismaUserRaw interface │ └── mapper.ts # Prisma → UserAuth │ └── whmcs/ ├── types.ts # WhmcsClientRaw interface └── mapper.ts # WHMCS → CustomerData ``` ## What Gets Exported ```typescript // packages/domain/customer/index.ts export type { // Core types (use these in your code) User, // Complete user (UserAuth + CustomerData) UserAuth, // Auth state only (for JWT, auth checks) CustomerData, // Profile data only (rarely used directly) // Supporting types CustomerAddress, // ... etc } from "./types"; export { // Schemas for validation userSchema, userAuthSchema, customerDataSchema, // ... etc } from "./types"; export * as Providers from "./providers"; ``` ## Auth Domain References User ```typescript // packages/domain/auth/types.ts import { userSchema } from "../customer/types"; export const authResponseSchema = z.object({ user: userSchema, // Reference the User schema from customer domain tokens: authTokensSchema, }); export type AuthResponse = z.infer; ``` ## Summary **Entity**: ONE user/customer entity **Data Sources**: - `UserAuth` - From portal database (auth state) - `CustomerData` - From WHMCS (profile data) **Combined**: - `User` - Complete entity (auth + profile) **Usage**: - JWT validation → `UserAuth` - API responses → `User` - Internal mapping → `CustomerData` (via providers)