Assist_Design/COMPREHENSIVE_CODE_REVIEW_2025.md
barsa 0c904f7944 Refactor CardBadge and InternetPlanCard components for improved styling and functionality
- Updated CardBadge to support additional size options and enhanced styling for better visual consistency.
- Refactored InternetPlanCard to utilize the new CardBadge size options and improved layout for badge display.
- Enhanced state management in InternetPlanCard to utilize Zustand for better performance and maintainability.
- Streamlined rendering logic in InternetConfigureContainer for improved step transitions and user experience.
- Updated catalog utility functions to remove deprecated filtering and sorting methods, focusing on server-side handling.
2025-10-28 15:55:46 +09:00

25 KiB

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.

// ❌ 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:

// ✅ 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.

// ❌ 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:

// ✅ 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.

// ❌ 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:

// ✅ 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.

// ❌ 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:

// ✅ 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.

// ❌ 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:

// ✅ 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.

// ❌ 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:

// ✅ 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:

// 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:

// ❌ 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:

// ✅ 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:

// 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:

// 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:

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:

// 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)

  1. Week 4-5: Introduce domain services layer
  2. Week 6: Standardize error handling across codebase

Phase 3: Quality Improvements (Weeks 7-9)

  1. Week 7: Refactor long service methods
  2. Week 8-9: Implement repository pattern

Phase 4: Long-term Excellence (Weeks 10+)

  1. Week 10+: Comprehensive testing strategy
  2. 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.