856 lines
25 KiB
Markdown
856 lines
25 KiB
Markdown
|
|
# Comprehensive Code Review Report
|
||
|
|
**Customer Portal - Architecture & Code Quality Analysis**
|
||
|
|
|
||
|
|
**Date**: October 28, 2025
|
||
|
|
**Reviewer**: AI Code Reviewer
|
||
|
|
**Scope**: Full codebase review for separation of concerns, domain design, and code quality
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
The customer portal codebase demonstrates **good overall architecture** with a clear BFF (Backend for Frontend) pattern and a dedicated domain package. However, there are **several critical architectural violations** and **technical debt** that need to be addressed to achieve true separation of concerns and maintainable code.
|
||
|
|
|
||
|
|
### Key Metrics
|
||
|
|
- **Architecture Score**: 7/10 (Good foundation, needs refinement)
|
||
|
|
- **Separation of Concerns**: 6/10 (Domain exists but inconsistently used)
|
||
|
|
- **Code Quality**: 7.5/10 (Generally clean with some issues)
|
||
|
|
- **File Structure**: 8/10 (Well-organized, some redundancy)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Strengths (What's Done Well)
|
||
|
|
|
||
|
|
### 1. **Domain Package Architecture**
|
||
|
|
- **Well-structured domain package** at `packages/domain/` with clear separation by business domain (catalog, orders, sim, billing, etc.)
|
||
|
|
- **Schema-first approach** using Zod for runtime validation with TypeScript type inference
|
||
|
|
- **Provider adapters** properly isolated (Salesforce, WHMCS, Freebit) with clean mapping logic
|
||
|
|
- **Centralized validation** schemas in domain package
|
||
|
|
|
||
|
|
### 2. **BFF Layer Implementation**
|
||
|
|
- **Clean controller layer** with proper use of NestJS decorators and guards
|
||
|
|
- **Service-oriented architecture** with clear separation (orchestrators, validators, builders)
|
||
|
|
- **Proper dependency injection** patterns throughout
|
||
|
|
- **Structured logging** using nestjs-pino
|
||
|
|
- **Rate limiting** on sensitive endpoints (orders, catalog)
|
||
|
|
|
||
|
|
### 3. **Frontend Architecture**
|
||
|
|
- **Feature-based organization** in `apps/portal/src/features/`
|
||
|
|
- **Component hierarchy** (atoms, molecules, organisms) following atomic design
|
||
|
|
- **Zustand state management** for complex state (auth, catalog configuration)
|
||
|
|
- **React Query integration** for data fetching
|
||
|
|
- **Proper hooks abstraction** for business logic encapsulation
|
||
|
|
|
||
|
|
### 4. **Type Safety**
|
||
|
|
- **Strong TypeScript usage** throughout the codebase
|
||
|
|
- **Type inference from Zod schemas** (schema-first approach)
|
||
|
|
- **Minimal use of `any` types**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ❌ Critical Issues (Must Fix)
|
||
|
|
|
||
|
|
### 1. **Business Logic in Frontend** ⚠️ CRITICAL
|
||
|
|
|
||
|
|
**Location**: `apps/portal/src/features/catalog/utils/catalog.utils.ts`
|
||
|
|
|
||
|
|
**Issue**: Frontend contains business logic for filtering, sorting, and calculating product values.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ BAD: Frontend has business logic
|
||
|
|
export function filterProducts(
|
||
|
|
products: CatalogProduct[],
|
||
|
|
filters: { category?: string; priceMin?: number; priceMax?: number; search?: string; }
|
||
|
|
): CatalogProduct[] {
|
||
|
|
return products.filter(product => {
|
||
|
|
if (typeof filters.priceMin === "number") {
|
||
|
|
const price = product.monthlyPrice ?? product.oneTimePrice ?? 0;
|
||
|
|
if (price < filters.priceMin) return false;
|
||
|
|
}
|
||
|
|
// ... more filtering logic
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export function sortProducts(products: CatalogProduct[], sortBy: "name" | "price" = "name"): CatalogProduct[] {
|
||
|
|
// ... sorting logic
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why This Is Wrong**:
|
||
|
|
- **Separation of concerns violated**: Frontend should only display data, not transform it
|
||
|
|
- **Inconsistent business rules**: Client-side logic can diverge from server-side
|
||
|
|
- **Security risk**: Business rules can be bypassed by modifying frontend code
|
||
|
|
- **Performance**: Heavy operations on client-side
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// ✅ GOOD: Move to BFF
|
||
|
|
// apps/bff/src/modules/catalog/services/catalog.service.ts
|
||
|
|
async getFilteredProducts(
|
||
|
|
filters: CatalogFilter
|
||
|
|
): Promise<CatalogProduct[]> {
|
||
|
|
// Apply filtering server-side
|
||
|
|
// Return pre-filtered, pre-calculated results
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: HIGH - Violates fundamental architectural principles
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. **Business Validation Logic Split Between Layers** ⚠️ HIGH
|
||
|
|
|
||
|
|
**Location**:
|
||
|
|
- `apps/portal/src/features/checkout/hooks/useCheckout.ts` (lines 51-59, 211-213)
|
||
|
|
- `apps/bff/src/modules/orders/services/order-validator.service.ts`
|
||
|
|
|
||
|
|
**Issue**: Business validation duplicated in frontend and backend.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ BAD: Business validation in frontend hook
|
||
|
|
const hasActiveInternetSubscription = useMemo(() => {
|
||
|
|
if (!Array.isArray(activeSubs)) return false;
|
||
|
|
return activeSubs.some(
|
||
|
|
subscription =>
|
||
|
|
String(subscription.groupName || subscription.productName || "")
|
||
|
|
.toLowerCase()
|
||
|
|
.includes("internet") &&
|
||
|
|
String(subscription.status || "").toLowerCase() === "active"
|
||
|
|
);
|
||
|
|
}, [activeSubs]);
|
||
|
|
|
||
|
|
// Client-side guard: prevent Internet orders if an Internet subscription already exists
|
||
|
|
if (orderType === ORDER_TYPE.INTERNET && hasActiveInternetSubscription && !isDevEnvironment) {
|
||
|
|
throw new Error(ACTIVE_INTERNET_WARNING_MESSAGE);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why This Is Wrong**:
|
||
|
|
- **Duplicate validation**: Same rule exists in BFF (`validateInternetDuplication`)
|
||
|
|
- **Inconsistency risk**: Frontend and backend rules can diverge
|
||
|
|
- **Security**: Client-side checks can be bypassed
|
||
|
|
- **Development mode override**: Business rules shouldn't be environment-dependent
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// ✅ GOOD: Single source of truth in BFF
|
||
|
|
// Frontend only displays warnings/hints
|
||
|
|
// BFF enforces business rules absolutely
|
||
|
|
// Remove all business rule enforcement from frontend
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: HIGH - Security and consistency risk
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. **Frontend Stores Contain Business Logic** ⚠️ MEDIUM-HIGH
|
||
|
|
|
||
|
|
**Location**: `apps/portal/src/features/catalog/services/catalog.store.ts`
|
||
|
|
|
||
|
|
**Issue**: Store contains complex business logic for building checkout parameters, validating forms, and transforming data.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ BAD: Complex transformation logic in frontend store (lines 196-304)
|
||
|
|
buildSimCheckoutParams: () => {
|
||
|
|
const { sim } = get();
|
||
|
|
if (!sim.planSku) return null;
|
||
|
|
|
||
|
|
const rawSelections: Record<string, string> = {
|
||
|
|
plan: sim.planSku,
|
||
|
|
simType: sim.simType,
|
||
|
|
activationType: sim.activationType,
|
||
|
|
};
|
||
|
|
|
||
|
|
// 50+ lines of complex transformation logic
|
||
|
|
// Building MNP data, normalizing selections, etc.
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why This Is Wrong**:
|
||
|
|
- **Business logic in presentation layer**: Stores should only manage state, not transform it
|
||
|
|
- **Complex transformations**: Should be in domain package
|
||
|
|
- **Tight coupling**: Frontend tightly coupled to domain implementation details
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// ✅ GOOD: Move to domain package
|
||
|
|
// packages/domain/orders/helpers.ts
|
||
|
|
export function buildSimCheckoutSelections(config: SimConfigState): OrderSelections {
|
||
|
|
// Transformation logic here
|
||
|
|
}
|
||
|
|
|
||
|
|
// Frontend store only calls helper
|
||
|
|
buildSimCheckoutParams: () => {
|
||
|
|
const { sim } = get();
|
||
|
|
return buildSimCheckoutSelections(sim);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: MEDIUM-HIGH - Maintainability and testability issues
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. **Validation Schema Duplication** ⚠️ MEDIUM
|
||
|
|
|
||
|
|
**Location**: Multiple files
|
||
|
|
|
||
|
|
**Issue**: Validation patterns duplicated across layers instead of using domain schemas.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ BAD: Local validation schema in BFF
|
||
|
|
// apps/bff/src/integrations/salesforce/utils/soql.util.ts
|
||
|
|
const nonEmptyStringSchema = z.string().min(1, "Value cannot be empty").trim();
|
||
|
|
const soqlFieldNameSchema = z.string().trim().regex(/^[A-Za-z0-9_.]+$/, "Invalid SOQL field name");
|
||
|
|
|
||
|
|
// ❌ BAD: Controller-specific response schema
|
||
|
|
// apps/bff/src/modules/orders/orders.controller.ts
|
||
|
|
private readonly createOrderResponseSchema = apiSuccessResponseSchema(
|
||
|
|
z.object({
|
||
|
|
sfOrderId: z.string(),
|
||
|
|
status: z.string(),
|
||
|
|
message: z.string(),
|
||
|
|
})
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why This Is Wrong**:
|
||
|
|
- **Duplication**: Same validation logic defined in multiple places
|
||
|
|
- **Inconsistency**: Validation rules can diverge
|
||
|
|
- **Not DRY**: Violates Don't Repeat Yourself principle
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// ✅ GOOD: Define once in domain
|
||
|
|
// packages/domain/common/validation.ts
|
||
|
|
export const nonEmptyStringSchema = z.string().min(1).trim();
|
||
|
|
export const salesforceIdSchema = z.string().length(18);
|
||
|
|
|
||
|
|
// packages/domain/orders/schema.ts
|
||
|
|
export const orderCreateResponseSchema = z.object({
|
||
|
|
sfOrderId: z.string(),
|
||
|
|
status: z.string(),
|
||
|
|
message: z.string(),
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: MEDIUM - Code duplication and maintenance burden
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 5. **Infrastructure Types in Business Layer** ⚠️ MEDIUM
|
||
|
|
|
||
|
|
**Location**: Various service files
|
||
|
|
|
||
|
|
**Issue**: Infrastructure-specific types defined in business services instead of proper boundaries.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ BAD: Infrastructure type in service
|
||
|
|
// apps/bff/src/modules/invoices/services/invoice-retrieval.service.ts
|
||
|
|
interface UserMappingInfo {
|
||
|
|
userId: string;
|
||
|
|
whmcsClientId: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ❌ BAD: BFF-specific interface that could be domain type
|
||
|
|
// apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts
|
||
|
|
export interface OrderFulfillmentValidationResult {
|
||
|
|
sfOrder: SalesforceOrderRecord;
|
||
|
|
clientId: number;
|
||
|
|
isAlreadyProvisioned: boolean;
|
||
|
|
whmcsOrderId?: string;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why This Is Wrong**:
|
||
|
|
- **Blurred boundaries**: Infrastructure concerns mixed with business logic
|
||
|
|
- **Tight coupling**: Services coupled to specific infrastructure details
|
||
|
|
- **Hard to test**: Infrastructure types make unit testing harder
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// ✅ GOOD: Define in appropriate domain or create proper abstraction
|
||
|
|
// packages/domain/orders/types.ts
|
||
|
|
export interface OrderFulfillmentValidation {
|
||
|
|
orderId: string;
|
||
|
|
customerAccountId: string;
|
||
|
|
isProvisioned: boolean;
|
||
|
|
externalOrderId?: string;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: MEDIUM - Architectural clarity and testability
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 6. **Complex Business Logic in Frontend Hooks** ⚠️ MEDIUM
|
||
|
|
|
||
|
|
**Location**: `apps/portal/src/features/checkout/hooks/useCheckout.ts` (lines 90-123)
|
||
|
|
|
||
|
|
**Issue**: Complex parsing and configuration building in frontend hooks.
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ BAD: Complex business logic in hook (lines 90-123)
|
||
|
|
const { selections, configurations } = useMemo(() => {
|
||
|
|
const rawSelections: Record<string, string> = {};
|
||
|
|
params.forEach((value, key) => {
|
||
|
|
if (key !== "type") {
|
||
|
|
rawSelections[key] = value;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
const normalizedSelections = normalizeOrderSelections(rawSelections);
|
||
|
|
let configuration: OrderConfigurations | undefined;
|
||
|
|
|
||
|
|
try {
|
||
|
|
configuration = buildOrderConfigurations(normalizedSelections);
|
||
|
|
} catch (error) {
|
||
|
|
logger.warn("Failed to derive order configurations from selections", {
|
||
|
|
error: error instanceof Error ? error.message : String(error),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
selections: normalizedSelections,
|
||
|
|
configurations: configuration,
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
// ... error handling
|
||
|
|
}
|
||
|
|
}, [params]);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why This Is Wrong**:
|
||
|
|
- **Complex logic in UI layer**: Hooks should be simple state managers
|
||
|
|
- **Silent error swallowing**: Try-catch with fallbacks hide issues
|
||
|
|
- **Hard to test**: Logic tied to React hooks ecosystem
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// ✅ GOOD: Move to service layer
|
||
|
|
// apps/portal/src/features/checkout/services/checkout-params.service.ts
|
||
|
|
export class CheckoutParamsService {
|
||
|
|
static parseSelections(params: URLSearchParams): OrderSelections {
|
||
|
|
// Parsing logic
|
||
|
|
}
|
||
|
|
|
||
|
|
static buildConfigurations(selections: OrderSelections): OrderConfigurations {
|
||
|
|
// Building logic
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Hook becomes simple
|
||
|
|
const selections = CheckoutParamsService.parseSelections(params);
|
||
|
|
const configurations = CheckoutParamsService.buildConfigurations(selections);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: MEDIUM - Maintainability and testability
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔴 Architectural Issues
|
||
|
|
|
||
|
|
### 1. **Inconsistent Domain Package Usage**
|
||
|
|
|
||
|
|
**Issue**: Not all business types are in the domain package. Some are scattered across BFF and Portal.
|
||
|
|
|
||
|
|
**Examples**:
|
||
|
|
- `CheckoutItem` used in Portal but not defined in domain
|
||
|
|
- `PricingTier` defined in UI components
|
||
|
|
- `CatalogFilter` has TODO comment indicating missing domain type
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
- Move ALL business types to domain package
|
||
|
|
- Frontend and BFF should ONLY import from domain
|
||
|
|
- Domain package should be the single source of truth
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. **Lack of Domain Services**
|
||
|
|
|
||
|
|
**Issue**: Business logic spread across BFF services without clear domain service layer.
|
||
|
|
|
||
|
|
**Observation**:
|
||
|
|
```
|
||
|
|
packages/domain/
|
||
|
|
catalog/
|
||
|
|
schema.ts ✅
|
||
|
|
contract.ts ✅
|
||
|
|
providers/ ✅
|
||
|
|
index.ts ✅
|
||
|
|
services/ ❌ MISSING
|
||
|
|
validation.ts ❌ MISSING
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// packages/domain/orders/services/order-validation.service.ts
|
||
|
|
export class OrderValidationService {
|
||
|
|
static validateOrderFulfillment(order: Order): ValidationResult {
|
||
|
|
// Pure business logic here
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Impact**: Architectural clarity and testability
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. **Mixed Responsibility in Controllers**
|
||
|
|
|
||
|
|
**Issue**: Controllers doing more than HTTP request/response handling.
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```typescript
|
||
|
|
// ❌ BAD: Controller handles parsing logic
|
||
|
|
@Get("internet/plans")
|
||
|
|
async getInternetPlans(@Request() req: RequestWithUser) {
|
||
|
|
const userId = req.user?.id;
|
||
|
|
if (!userId) {
|
||
|
|
const catalog = await this.internetCatalog.getCatalogData();
|
||
|
|
return parseInternetCatalog(catalog); // Parsing in controller
|
||
|
|
}
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```typescript
|
||
|
|
// ✅ GOOD: Service handles all logic
|
||
|
|
@Get("internet/plans")
|
||
|
|
async getInternetPlans(@Request() req: RequestWithUser) {
|
||
|
|
return this.catalogService.getInternetPlansForUser(req.user?.id);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. **Tight Coupling to External Services**
|
||
|
|
|
||
|
|
**Issue**: Business logic services directly coupled to integration services (WHMCS, Salesforce).
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```typescript
|
||
|
|
// apps/bff/src/modules/orders/services/order-validator.service.ts
|
||
|
|
constructor(
|
||
|
|
private readonly whmcs: WhmcsConnectionOrchestratorService,
|
||
|
|
private readonly mappings: MappingsService,
|
||
|
|
// ... directly depends on infrastructure
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**: Introduce repository pattern or ports/adapters to decouple business logic from infrastructure.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🟡 Code Quality Issues
|
||
|
|
|
||
|
|
### 1. **Inconsistent Error Handling**
|
||
|
|
|
||
|
|
**Issue**: Mix of generic `throw new Error()` and typed exceptions.
|
||
|
|
|
||
|
|
**Found**:
|
||
|
|
- Some services use typed exceptions (`SimActivationException`, `OrderValidationException`)
|
||
|
|
- Many still use generic `throw new Error()`
|
||
|
|
- Inconsistent error message formatting
|
||
|
|
|
||
|
|
**Solution**: Standardize on typed exception hierarchy across all services.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. **Missing Input Validation in Some Services**
|
||
|
|
|
||
|
|
**Issue**: Some service methods don't validate inputs before processing.
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```typescript
|
||
|
|
// apps/bff/src/modules/orders/services/sim-fulfillment.service.ts (lines 28-34)
|
||
|
|
const simType = this.readEnum(configurations.simType, ["eSIM", "Physical SIM"]) ?? "eSIM";
|
||
|
|
const eid = this.readString(configurations.eid);
|
||
|
|
// ... no validation that configurations is not null/undefined
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**: Add input validation at service boundaries.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. **Complex Service Methods**
|
||
|
|
|
||
|
|
**Issue**: Some service methods are very long (100+ lines) with complex logic.
|
||
|
|
|
||
|
|
**Examples**:
|
||
|
|
- `OrderFulfillmentOrchestrator.fulfillOrder()` (likely 200+ lines)
|
||
|
|
- `SimFulfillmentService.fulfillSimOrder()` (100 lines with nested conditionals)
|
||
|
|
|
||
|
|
**Solution**: Break down into smaller, focused methods with single responsibility.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. **Inadequate Logging Context**
|
||
|
|
|
||
|
|
**Issue**: Some log statements lack sufficient context for debugging.
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```typescript
|
||
|
|
this.logger.log("SIM fulfillment completed successfully", {
|
||
|
|
orderId: orderDetails.id,
|
||
|
|
account: phoneNumber,
|
||
|
|
planSku,
|
||
|
|
});
|
||
|
|
// Missing: simType, activationType, whether MNP was used, etc.
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**: Add comprehensive context to all log statements.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 5. **Magic Strings and Numbers**
|
||
|
|
|
||
|
|
**Issue**: Various magic strings and numbers scattered throughout code.
|
||
|
|
|
||
|
|
**Examples**:
|
||
|
|
```typescript
|
||
|
|
// apps/portal/src/features/checkout/hooks/useCheckout.ts
|
||
|
|
const ACTIVE_INTERNET_WARNING_MESSAGE = "You already have an active..."; // Should be in constants
|
||
|
|
const isDevEnvironment = process.env.NODE_ENV === "development"; // Direct env check
|
||
|
|
|
||
|
|
// apps/bff/src/modules/orders/controllers/checkout.service.ts (line 55)
|
||
|
|
if (simType === "eSIM" && (!eid || eid.length < 15)) { // Magic number 15
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**: Extract to constants or configuration.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📁 File Structure Issues
|
||
|
|
|
||
|
|
### 1. **Inconsistent Module Organization**
|
||
|
|
|
||
|
|
**Issue**: Some modules have deep nesting, others are flat.
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```
|
||
|
|
modules/auth/
|
||
|
|
application/
|
||
|
|
infra/
|
||
|
|
workflows/
|
||
|
|
workflows/ ← Redundant nesting
|
||
|
|
presentation/
|
||
|
|
services/
|
||
|
|
|
||
|
|
vs.
|
||
|
|
|
||
|
|
modules/catalog/
|
||
|
|
services/
|
||
|
|
catalog.controller.ts
|
||
|
|
catalog.module.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**: Standardize on consistent depth and organization.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. **Mixed Concerns in Directories**
|
||
|
|
|
||
|
|
**Issue**: Some directories contain both business logic and infrastructure code.
|
||
|
|
|
||
|
|
**Example**:
|
||
|
|
```
|
||
|
|
modules/orders/
|
||
|
|
controllers/ ← HTTP layer
|
||
|
|
services/ ← Mix of business logic and orchestration
|
||
|
|
queue/ ← Infrastructure
|
||
|
|
types/ ← Shared types
|
||
|
|
```
|
||
|
|
|
||
|
|
**Solution**: Separate by architectural layer (domain, application, infrastructure, presentation).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. **Redundant Type Directories**
|
||
|
|
|
||
|
|
**Issue**: Types scattered across multiple locations.
|
||
|
|
|
||
|
|
**Found**:
|
||
|
|
- `apps/bff/src/types/`
|
||
|
|
- `apps/bff/src/modules/*/types/`
|
||
|
|
- `packages/domain/*/`
|
||
|
|
|
||
|
|
**Solution**: All business types in domain, infrastructure types in their respective modules.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Technical Debt
|
||
|
|
|
||
|
|
### 1. **Build Artifacts in Source** ⚠️ RESOLVED
|
||
|
|
|
||
|
|
**Status**: Fixed (per CODEBASE_ANALYSIS.md)
|
||
|
|
- Previously: 224 generated files in source tree
|
||
|
|
- Now: Build outputs isolated to `dist/`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. **Console.log Statements**
|
||
|
|
|
||
|
|
**Issue**: 3 console.log instances found in portal (should use logger)
|
||
|
|
|
||
|
|
**Found**: `apps/portal/src/lib/logger.ts:2`
|
||
|
|
|
||
|
|
**Solution**: Replace with proper logging infrastructure.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. **Incomplete Type Safety**
|
||
|
|
|
||
|
|
**Issue**: Some areas still have loose typing.
|
||
|
|
|
||
|
|
**Examples**:
|
||
|
|
- `unknown` and `any` usage in error handling
|
||
|
|
- Generic `Record<string, unknown>` for complex objects
|
||
|
|
- Missing discriminated unions for variant types
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. **Missing Tests**
|
||
|
|
|
||
|
|
**Observation**: No test files found in search (likely in separate test directories).
|
||
|
|
|
||
|
|
**Recommendation**: Ensure comprehensive test coverage, especially for:
|
||
|
|
- Domain validation logic
|
||
|
|
- Business rules
|
||
|
|
- Order fulfillment workflows
|
||
|
|
- Integration adapters
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 Detailed Findings by Domain
|
||
|
|
|
||
|
|
### Authentication & Authorization
|
||
|
|
|
||
|
|
**Strengths**:
|
||
|
|
- ✅ Clean store implementation with Zustand
|
||
|
|
- ✅ Proper JWT handling with httpOnly cookies
|
||
|
|
- ✅ Session management with refresh tokens
|
||
|
|
|
||
|
|
**Issues**:
|
||
|
|
- ⚠️ Complex workflow logic in frontend store (auth.store.ts)
|
||
|
|
- ⚠️ Nested directory structure (`infra/workflows/workflows/`)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Catalog & Product Management
|
||
|
|
|
||
|
|
**Strengths**:
|
||
|
|
- ✅ Domain types well-defined (CatalogProduct, InternetPlan, SimProduct, etc.)
|
||
|
|
- ✅ Provider adapters cleanly separated
|
||
|
|
- ✅ Caching implemented (CatalogCacheService)
|
||
|
|
|
||
|
|
**Issues**:
|
||
|
|
- ❌ Filtering and sorting logic in frontend utils
|
||
|
|
- ❌ Business logic in catalog store
|
||
|
|
- ⚠️ Parsing logic in controllers instead of services
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Orders & Checkout
|
||
|
|
|
||
|
|
**Strengths**:
|
||
|
|
- ✅ Clear orchestration pattern (OrderOrchestrator)
|
||
|
|
- ✅ Separate validation service (OrderValidator)
|
||
|
|
- ✅ Domain schemas for order types
|
||
|
|
- ✅ Business validation in domain package
|
||
|
|
|
||
|
|
**Issues**:
|
||
|
|
- ❌ Frontend validation duplicating backend validation
|
||
|
|
- ❌ Complex checkout hook with business logic
|
||
|
|
- ⚠️ Long service methods that could be broken down
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Subscriptions & Billing
|
||
|
|
|
||
|
|
**Strengths**:
|
||
|
|
- ✅ Clean data fetching with React Query
|
||
|
|
- ✅ Proper type definitions
|
||
|
|
|
||
|
|
**Issues**:
|
||
|
|
- ⚠️ Minimal business logic encapsulation
|
||
|
|
- ⚠️ Direct WHMCS coupling in some areas
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Recommendations
|
||
|
|
|
||
|
|
### Immediate Actions (High Priority)
|
||
|
|
|
||
|
|
#### 1. **Remove Business Logic from Frontend** ⚠️ CRITICAL
|
||
|
|
- **Timeline**: 1-2 weeks
|
||
|
|
- **Impact**: HIGH
|
||
|
|
- **Action**:
|
||
|
|
- Move `filterProducts`, `sortProducts` from `catalog.utils.ts` to BFF
|
||
|
|
- Remove client-side business validation from `useCheckout` hook
|
||
|
|
- Simplify frontend stores to only manage UI state
|
||
|
|
|
||
|
|
#### 2. **Consolidate Validation Schemas** ⚠️ HIGH
|
||
|
|
- **Timeline**: 1 week
|
||
|
|
- **Impact**: MEDIUM
|
||
|
|
- **Action**:
|
||
|
|
- Move all validation schemas to domain package
|
||
|
|
- Remove duplicate schemas from BFF and Portal
|
||
|
|
- Update imports across codebase
|
||
|
|
|
||
|
|
#### 3. **Extract Business Types to Domain** ⚠️ HIGH
|
||
|
|
- **Timeline**: 1 week
|
||
|
|
- **Impact**: MEDIUM
|
||
|
|
- **Action**:
|
||
|
|
- Move `OrderFulfillmentValidationResult`, `PricingTier`, `CheckoutItem` to domain
|
||
|
|
- Define missing types (`CatalogFilter`, etc.)
|
||
|
|
- Update all imports
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Medium Priority Actions
|
||
|
|
|
||
|
|
#### 4. **Introduce Domain Services Layer**
|
||
|
|
- **Timeline**: 2-3 weeks
|
||
|
|
- **Impact**: MEDIUM
|
||
|
|
- **Action**:
|
||
|
|
- Create `packages/domain/*/services/` directories
|
||
|
|
- Move pure business logic from BFF services to domain services
|
||
|
|
- BFF services become thin orchestration layer
|
||
|
|
|
||
|
|
#### 5. **Standardize Error Handling**
|
||
|
|
- **Timeline**: 1-2 weeks
|
||
|
|
- **Impact**: MEDIUM
|
||
|
|
- **Action**:
|
||
|
|
- Complete typed exception hierarchy
|
||
|
|
- Replace all generic `throw new Error()` with typed exceptions
|
||
|
|
- Add proper error context
|
||
|
|
|
||
|
|
#### 6. **Refactor Long Service Methods**
|
||
|
|
- **Timeline**: 2 weeks
|
||
|
|
- **Impact**: LOW-MEDIUM
|
||
|
|
- **Action**:
|
||
|
|
- Break down methods >50 lines into smaller functions
|
||
|
|
- Apply Single Responsibility Principle
|
||
|
|
- Improve testability
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Long-term Improvements
|
||
|
|
|
||
|
|
#### 7. **Implement Repository Pattern**
|
||
|
|
- **Timeline**: 3-4 weeks
|
||
|
|
- **Impact**: MEDIUM
|
||
|
|
- **Action**:
|
||
|
|
- Introduce repositories to decouple from Salesforce/WHMCS
|
||
|
|
- Define ports/adapters architecture
|
||
|
|
- Improve testability with mocking
|
||
|
|
|
||
|
|
#### 8. **Comprehensive Testing Strategy**
|
||
|
|
- **Timeline**: Ongoing
|
||
|
|
- **Impact**: HIGH
|
||
|
|
- **Action**:
|
||
|
|
- Unit tests for all domain services
|
||
|
|
- Integration tests for BFF services
|
||
|
|
- E2E tests for critical user flows
|
||
|
|
|
||
|
|
#### 9. **Documentation Improvements**
|
||
|
|
- **Timeline**: 1-2 weeks
|
||
|
|
- **Impact**: LOW
|
||
|
|
- **Action**:
|
||
|
|
- Architecture Decision Records (ADRs)
|
||
|
|
- API documentation
|
||
|
|
- Code comments for complex business logic
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 Implementation Plan
|
||
|
|
|
||
|
|
### Phase 1: Critical Fixes (Weeks 1-3)
|
||
|
|
1. **Week 1**: Remove business logic from frontend utilities
|
||
|
|
2. **Week 2**: Consolidate validation schemas in domain
|
||
|
|
3. **Week 3**: Extract business types to domain package
|
||
|
|
|
||
|
|
### Phase 2: Architectural Improvements (Weeks 4-6)
|
||
|
|
4. **Week 4-5**: Introduce domain services layer
|
||
|
|
5. **Week 6**: Standardize error handling across codebase
|
||
|
|
|
||
|
|
### Phase 3: Quality Improvements (Weeks 7-9)
|
||
|
|
6. **Week 7**: Refactor long service methods
|
||
|
|
7. **Week 8-9**: Implement repository pattern
|
||
|
|
|
||
|
|
### Phase 4: Long-term Excellence (Weeks 10+)
|
||
|
|
8. **Week 10+**: Comprehensive testing strategy
|
||
|
|
9. **Ongoing**: Documentation improvements
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📈 Success Metrics
|
||
|
|
|
||
|
|
### Code Quality Metrics
|
||
|
|
- **Type Safety**: 95%+ of code strongly typed
|
||
|
|
- **Business Logic in Domain**: 90%+ of business logic in domain package
|
||
|
|
- **Validation Consistency**: Single validation schema per business rule
|
||
|
|
- **Test Coverage**: 80%+ for domain and business logic
|
||
|
|
|
||
|
|
### Architectural Metrics
|
||
|
|
- **Separation of Concerns**: Clear boundaries between layers
|
||
|
|
- **Coupling**: Low coupling between modules
|
||
|
|
- **Cohesion**: High cohesion within modules
|
||
|
|
- **Import Clarity**: All business types imported from domain
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎓 Learning & Best Practices
|
||
|
|
|
||
|
|
### What to Continue
|
||
|
|
1. **Schema-first approach**: Continue using Zod schemas as source of truth
|
||
|
|
2. **Provider adapters**: Continue clean separation of external service integration
|
||
|
|
3. **Feature-based organization**: Frontend feature structure is good
|
||
|
|
4. **Dependency injection**: Continue using DI in BFF
|
||
|
|
|
||
|
|
### What to Stop
|
||
|
|
1. **Business logic in frontend**: Stop putting business rules in UI components/hooks
|
||
|
|
2. **Duplicate validation**: Stop defining validation schemas in multiple places
|
||
|
|
3. **Direct external service coupling**: Stop directly coupling business logic to infrastructure
|
||
|
|
4. **Magic values**: Stop using magic strings/numbers without constants
|
||
|
|
|
||
|
|
### What to Start
|
||
|
|
1. **Domain services**: Start creating pure business logic services in domain
|
||
|
|
2. **Repository pattern**: Start abstracting data access
|
||
|
|
3. **Typed exceptions**: Start using typed exceptions everywhere
|
||
|
|
4. **Comprehensive logging**: Start adding rich context to all logs
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔍 Conclusion
|
||
|
|
|
||
|
|
The customer portal has a **solid architectural foundation** with good separation between BFF and Portal, and a well-structured domain package. However, there are **critical violations of separation of concerns**, particularly business logic leaking into the frontend.
|
||
|
|
|
||
|
|
### Summary Assessment
|
||
|
|
- **Architecture**: 7/10 - Good foundation but needs refinement
|
||
|
|
- **Domain Design**: 6/10 - Well-structured but underutilized
|
||
|
|
- **Code Quality**: 7.5/10 - Generally clean with some technical debt
|
||
|
|
- **Maintainability**: 6.5/10 - Will improve significantly after addressing issues
|
||
|
|
|
||
|
|
### Priority Focus Areas
|
||
|
|
1. **Remove all business logic from frontend** (Critical)
|
||
|
|
2. **Consolidate validation in domain** (High)
|
||
|
|
3. **Complete domain type migration** (High)
|
||
|
|
4. **Introduce domain services layer** (Medium)
|
||
|
|
|
||
|
|
By addressing these issues systematically, the codebase will achieve:
|
||
|
|
- ✅ True separation of concerns
|
||
|
|
- ✅ Single source of truth for business logic
|
||
|
|
- ✅ Improved testability
|
||
|
|
- ✅ Better maintainability
|
||
|
|
- ✅ Cleaner architecture
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Next Steps**: Prioritize Phase 1 actions and create detailed task tickets for implementation.
|
||
|
|
|
||
|
|
**Review Schedule**: Re-assess after Phase 1 completion (3 weeks) to measure progress.
|
||
|
|
|