Code Review: Admin Dashboard Branch Merge

Date: $(date)
Reviewer: AI Code Reviewer
Branch: admin-dashboard → dev
Status: ⚠️ Issues Found - Review Required


Executive Summary

This review evaluates the admin dashboard branch merge for architecture compliance, business logic correctness, performance, security, and code quality. The implementation follows Hexagonal Architecture patterns and successfully implements read replicas for dashboard queries. However, several issues require attention before merging.

Overall Assessment:Good - Address critical and high-priority issues before merge.


Phase 1: Context ✓

Project Architecture

  • Pattern: Hexagonal Architecture (Ports & Adapters)
  • Framework: NestJS with TypeORM/PostgreSQL
  • Key Features:
    • Read replicas for dashboard queries
    • Redis caching (24-hour TTL)
    • Config-based lead rates (moved from constants to env vars)
    • Materialized views for performance (mv_property_analytics, mv_lead_summary)

Changes Overview

  • Split core-kpi endpoint into user-kpi and agency-kpi
  • Implemented read replicas for all dashboard queries
  • Moved lead rates to environment variables
  • Added percentage growth calculations for KPI metrics
  • Removed caching for rider count calculation

Phase 2: Architecture & Structure

✅ Positive Observations

  • Consistent use of read replicas via runRawQueryOnReadReplicaWithDataSource
  • Proper separation of concerns (repository → service → controller)
  • Good use of domain models (UserKpi, AgencyKpi, CoreKpiMetric)
  • Config externalization follows NestJS best practices

❌ Critical Issues

Issue 1: Naming Convention Violation

Severity: 🔴 High
Location: Multiple files

Problem: Classes use lowercase prefixes instead of PascalCase, violating NestJS conventions:

  • dashboardAbstractRepository → should be DashboardAbstractRepository
  • dashboardRelationalRepository → should be DashboardRelationalRepository
  • dashboardsService → should be DashboardsService
  • dashboardsController → should be DashboardsController

Files Affected:

  • src/dashboards/infrastructure/persistence/dashboard.abstract.repository.ts
  • src/dashboards/infrastructure/persistence/relational/repositories/dashboard.repository.ts
  • src/dashboards/dashboards.service.ts
  • src/dashboards/dashboards.controller.ts
  • src/dashboards/dashboards.module.ts

Impact:

  • Inconsistent with NestJS conventions
  • Inconsistent with project patterns (other modules use PascalCase)
  • Potential confusion for developers

Recommendation: Rename all classes to PascalCase to match project conventions.


Issue 2: Dead Code - Unused getCoreKpi Method

Severity: 🔴 High
Location:

  • src/dashboards/dashboards.service.ts:155-170
  • src/dashboards/infrastructure/persistence/dashboard.abstract.repository.ts:38
  • src/dashboards/infrastructure/persistence/relational/repositories/dashboard.repository.ts:128-185

Problem: The getCoreKpi method is fully implemented but not exposed via controller. It was replaced by getUserKpi and getAgencyKpi endpoints, but the old method remains.

Impact:

  • Dead code increases maintenance burden
  • Confusing for developers
  • Unnecessary code complexity

Recommendation: Remove getCoreKpi from:

  1. Service (dashboards.service.ts)
  2. Abstract repository interface (dashboard.abstract.repository.ts)
  3. Repository implementation (dashboard.repository.ts)

Phase 3: Business Logic & Correctness

❌ Critical Issues

Issue 3: Hardcoded Status Value in SQL Query

Severity: 🔴 Critical
Location: src/dashboards/infrastructure/persistence/relational/queries/dashboard-queries.const.ts:198

Problem:

WHERE a.status = 'Active'  // ❌ Hardcoded string

Other queries correctly use enum interpolation:

WHERE a.status = '${AgencyStatusEnum.ACTIVE}'  // ✅ Correct

Impact:

  • If enum value changes, query will break
  • Inconsistent with other queries
  • Potential runtime bug

Recommendation:

WHERE a.status = '${AgencyStatusEnum.ACTIVE}'

⚠️ Medium Issues

Issue 4: Inconsistent Cache Key Generation

Severity: 🟡 Medium
Location: src/cache/cache.keys.const.ts:4-7

Problem: Cache key function accepts optional timePeriod, but repository always provides a default value. This creates inconsistency:

  • When timePeriod is undefined: dashboard:top-agencies-by-listing:10
  • When provided: dashboard:top-agencies-by-listing:10:7d

Impact:

  • Low risk, but could cause cache misses if behavior changes
  • Unnecessary complexity

Recommendation: Since repository always provides a default, either:

  1. Make timePeriod required in cache key function, or
  2. Document that repository always provides a default

Phase 4: Performance

✅ Positive Observations

  • All queries use read replicas (offloads master database)
  • Parallel query execution using Promise.all in repository methods
  • Proper query runner cleanup in finally blocks
  • Materialized views used for performance (mv_property_analytics, mv_lead_summary)

⚠️ Minor Concerns

Issue 5: Potential Performance Bottleneck (Future)

Severity: 🟡 Low (Future consideration)
Location: src/dashboards/dashboards.service.ts:130

Problem: getTotalLeadRevenue() scans all leads in mv_lead_summary, which could be expensive at scale.

Impact:

  • Currently acceptable
  • May need optimization as data grows

Recommendation: Monitor query performance. Consider materialized view for aggregated lead counts if this becomes a bottleneck.


Phase 5: Security

❌ High Priority Issues

Issue 6: Input Validation Missing

Severity: 🔴 High
Location: src/dashboards/dashboards.controller.ts

Problem: Query parameters are parsed but not validated:

const limitNum = limit ? parseInt(limit, 10) : 10;
const timePeriodEnum = timePeriod ? (timePeriod as TimePeriodEnum) : undefined;

Issues:

  1. limit could be negative, zero, or NaN
  2. timePeriod is cast without validation (could be any string)
  3. No bounds checking

Impact:

  • Invalid values could cause errors
  • Potential for unexpected behavior
  • Security risk if values are used in SQL (though they are parameterized)

Recommendation: Add validation:

// Validate limit
const limitNum = limit 
  ? Math.max(1, Math.min(100, parseInt(limit, 10) || 10)) 
  : 10;

// Validate timePeriod
if (timePeriod && !Object.values(TimePeriodEnum).includes(timePeriod as TimePeriodEnum)) {
  throw new BadRequestException(`Invalid timePeriod. Must be one of: ${Object.values(TimePeriodEnum).join(', ')}`);
}

Or use class-validator DTOs:

class TopAgenciesQueryDto {
  @IsOptional()
  @IsInt()
  @Min(1)
  @Max(100)
  limit?: number;

  @IsOptional()
  @IsEnum(TimePeriodEnum)
  timePeriod?: TimePeriodEnum;
}

⚠️ Medium Issues

Issue 7: SQL Injection Risk (Low) - Enum Interpolation

Severity: 🟡 Low
Location: src/dashboards/infrastructure/persistence/relational/queries/dashboard-queries.const.ts

Problem: Enum values are interpolated directly into SQL strings:

ls.property_purpose = '${PurposeTypeEnum.RENT}'
a.status = '${AgencyStatusEnum.ACTIVE}'

Impact:

  • Low risk because enums are compile-time constants
  • Not parameterized (inconsistent with other parameters)
  • Could be problematic if enum values contain special characters

Recommendation:

  1. Option 1: Document that enum interpolation is safe because values are compile-time constants
  2. Option 2: Consider parameterizing enum values for consistency (though this may require query restructuring)

Note: This is acceptable given that enums are controlled, but worth documenting.


Phase 6: Style & Readability

⚠️ Medium Issues

Issue 8: Outdated Documentation

Severity: 🟡 Medium
Location: docs/dashboards.md

Problem: Documentation still references:

  • Removed core-kpi endpoint (line 13, 35)
  • Missing documentation for new user-kpi and agency-kpi endpoints

Impact:

  • Documentation drift
  • Confusing for API consumers
  • Missing API documentation

Recommendation: Update docs/dashboards.md to:

  1. Remove core-kpi endpoint documentation
  2. Add user-kpi endpoint documentation
  3. Add agency-kpi endpoint documentation
  4. Update summary table

⚠️ Low Priority Issues

Issue 9: Redundant Transform Decorator

Severity: 🟢 Low
Location:

  • src/dashboards/domain/user-kpi.ts:18
  • src/dashboards/domain/agency-kpi.ts:18

Problem:

@Transform(({ value }) => Number(value))
newUsers: CoreKpiMetric;

CoreKpiMetric already has @Transform decorators on its properties (value and percentageGrowth).

Impact:

  • Harmless but redundant
  • May cause confusion

Recommendation: Remove if redundant, or document why it's needed.


Issue 10: Inconsistent Error Handling

Severity: 🟢 Low
Location: Repository methods

Problem: Repository methods don't handle potential query errors (e.g., read replica unavailable). Errors bubble up without context.

Impact:

  • Acceptable for now
  • Could improve debugging with better error messages

Recommendation: Consider adding error context:

try {
  return await runRawQueryOnReadReplicaWithDataSource(...);
} catch (error) {
  throw new Error(`Failed to fetch top agencies: ${error.message}`);
}

Summary of Issues

Critical (Must Fix Before Merge)

  1. Issue 3: Hardcoded 'Active' string → Use AgencyStatusEnum.ACTIVE
  2. Issue 2: Remove dead getCoreKpi code

High Priority (Should Fix)

  1. Issue 1: Fix naming conventions (PascalCase)
  2. Issue 6: Add input validation for query parameters

Medium Priority (Consider Fixing)

  1. Issue 8: Update documentation
  2. Issue 7: Document enum interpolation safety (or parameterize)
  3. Issue 4: Simplify cache key generation

Low Priority (Nice to Have)

  1. Issue 9: Remove redundant Transform decorators
  2. Issue 10: Improve error handling
  3. Issue 5: Monitor performance (future optimization)

Positive Observations

✅ Architecture

  • Consistent read replica usage across all queries
  • Proper separation of concerns
  • Good use of domain models
  • Config externalization follows best practices

✅ Performance

  • Parallel query execution (Promise.all)
  • Proper query runner cleanup
  • Materialized views for complex aggregations
  • Caching strategy implemented correctly

✅ Code Quality

  • Clean SQL queries with CTEs
  • Good use of TypeScript types
  • Consistent error handling patterns
  • Proper dependency injection

Recommendations

Before Merging

  1. Fix critical issues (Issues 2, 3)
  2. Fix high-priority issues (Issues 1, 6)
  3. Update documentation (Issue 8)

After Merging

  1. Monitor query performance, especially getTotalLeadRevenue()
  2. Consider adding integration tests for new endpoints
  3. Update API documentation for frontend team

Future Improvements

  1. Add input validation DTOs for all endpoints
  2. Consider parameterizing enum values in SQL (if needed)
  3. Add error context to repository methods
  4. Performance monitoring and optimization as data grows

Testing Recommendations

Unit Tests

  • Test repository methods with various time periods
  • Test input validation for query parameters
  • Test cache key generation

Integration Tests

  • Test read replica failover behavior
  • Test endpoint responses with various parameters
  • Test cache invalidation

Performance Tests

  • Load test dashboard endpoints
  • Monitor read replica performance
  • Test with large datasets

Approval Status

Status: ⚠️ CONDITIONAL APPROVAL

Conditions:

  • Fix all Critical and High Priority issues
  • Update documentation
  • Verify read replica behavior in staging

Estimated Fix Time: 2-4 hours


Review Checklist

  • [x] Architecture compliance reviewed
  • [x] Business logic correctness verified
  • [x] Performance implications assessed
  • [x] Security concerns identified
  • [x] Code style and readability checked
  • [x] Documentation reviewed
  • [ ] Critical issues fixed
  • [ ] High-priority issues fixed
  • [ ] Documentation updated
  • [ ] Tests passing
  • [ ] Ready for merge

Review Completed: $(date)
Next Review: After critical issues are addressed