7.0 KiB
7.0 KiB
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:
- Portal Database → Authentication & account state
- WHMCS → Profile & billing information
Recommended Schema Structure
// 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<typeof userAuthSchema>;
// ============================================================================
// 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<typeof customerDataSchema>;
// ============================================================================
// 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<typeof userSchema>;
Naming Comparison
Option A: Auth/Data (Recommended)
UserAuth // Auth state from portal DB
CustomerData // Profile data from WHMCS
User // Complete entity
Pros:
- Clear distinction: "Auth" = authentication, "Data" = profile information
- Simple exports:
Useris 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)
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)
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)
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
// 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<UserAuth> {
return this.getAuthState(payload.sub);
}
// In profile API - need complete user
async getProfile(userId: string): Promise<User> {
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
// 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
// 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<typeof authResponseSchema>;
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)