Assist_Design/CUSTOMER-DOMAIN-NAMING.md

256 lines
7.0 KiB
Markdown

# 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<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)
```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<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
```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<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)