# 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 { // 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 = { 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 = {}; 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` 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.