# Backend Professionalization Plan - Math Platform ## Executive Summary This document outlines the complete migration plan to transform the Math Platform backend into an enterprise-grade system with strict TypeScript, clean architecture, and professional patterns. ## Current Status ### Errors Found (Initial) - **Total TypeScript Errors**: 159 - **Categories**: - TS6133: Unused variables/imports (~25) - TS2345: Type undefined not assignable to string (~40) - TS2375/TS2379: exactOptionalPropertyTypes issues (~60) - TS2322: Type null/undefined assignment errors (~20) - TS6196: Unused type declarations (~10) - Other misc errors (~4) ### Errors Fixed in This Session 1. ✓ Fixed `jwt.sign` type issues in auth.service.ts 2. ✓ Fixed `topicId` null handling in exercise.service.ts 3. ✓ Removed unused ValidationError import 4. ✓ Removed unused adminChatId variable 5. ✓ Removed unused getTelegramAdminChatId import 6. ✓ Fixed module.controller.ts imports ### Remaining: ~150 errors ## Architecture Implemented ### 1. Configuration System ✓ **Location**: `src/config/index.ts` **Features**: - Zod schema validation for all environment variables - Type-safe config object - Computed properties (isDevelopment, isProduction, etc.) - Centralized defaults and constants **Usage**: ```typescript import { config } from './config'; // Type-safe access const port = config.PORT; const isDev = config.isDevelopment; ``` ### 2. Enterprise Error System ✓ **Location**: `src/core/errors/index.ts` **Features**: - Hierarchical error classes (AppError base) - Error codes enum for frontend mapping - HTTP status code mapping - Structured logging support - Prisma error mapping - Production vs development responses **Error Types**: - ValidationError (400) - AuthenticationError (401) - AuthorizationError (403) - NotFoundError (404) - ConflictError (409) - RateLimitError (429) - DatabaseError (500) - ExternalServiceError (502) - ServiceUnavailableError (503) ### 3. Core Types ✓ **Location**: `src/core/types/index.ts` **Includes**: - API response types (ApiSuccessResponse, ApiErrorResponse) - Pagination types (PaginatedResult, PaginationMeta) - Service types (ServiceResult, ServiceContext) - Repository types (QueryOptions, RepositoryOptions) - Event types (DomainEvent, EventHandler) - Utility types (Nullable, Optional, DeepPartial) ### 4. Error Middleware ✓ **Location**: `src/shared/middleware/error.middleware.ts` **Features**: - Global error handling - Automatic Prisma error mapping - Structured logging with correlation IDs - Production-safe error messages - Async handler wrapper ### 5. Rate Limiting ✓ **Location**: `src/shared/middleware/rate-limit.middleware.ts` **Features**: - Redis-backed store - Multiple limiter configurations - Custom key generation (user-based or IP-based) - Standard HTTP headers - Professional error responses **Limiters**: - standardRateLimiter: 100 req/15min - authRateLimiter: 5 req/15min - exerciseRateLimiter: 30 req/5min - aiRateLimiter: 20 req/min - adminRateLimiter: 300 req/15min ### 6. Dependency Injection Container (In Progress) **Location**: `src/infrastructure/di/container.ts` **Dependencies to install**: ```bash npm install tsyringe reflect-metadata ``` **Update tsconfig.json**: ```json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } ``` ### 7. Repository Pattern (In Progress) **Location**: `src/repositories/` **Structure**: ``` repositories/ ├── interfaces/ │ ├── exercise.repository.interface.ts │ ├── user.repository.interface.ts │ └── ... ├── exercise.repository.ts ├── user.repository.ts └── ... ``` **Features**: - Interface-based design - Prisma implementation - Caching support hooks - Query optimization - Transaction support ### 8. Service Layer Refactor (TODO) **Pattern**: ```typescript export interface IExerciseService { create(data: CreateExerciseDTO): Promise; findById(id: string): Promise; update(id: string, data: UpdateExerciseDTO): Promise; delete(id: string): Promise; } @injectable() export class ExerciseService implements IExerciseService { constructor( @inject(TOKENS.ExerciseRepository) private readonly repository: IExerciseRepository, @inject(TOKENS.Logger) private readonly logger: Logger, @inject(TOKENS.CacheService) private readonly cache: CacheService ) {} } ``` ## Migration Plan ### Phase 1: Fix TypeScript Errors (Priority: High) #### 1.1 Fix exactOptionalPropertyTypes Issues **Pattern**: Replace `prop?: Type` with `prop: Type | undefined` **Example**: ```typescript // Before interface Options { userId?: string; moduleId?: string; } // After interface Options { userId: string | undefined; moduleId: string | undefined; } ``` **Files to fix** (40+ occurrences): - exercise.controller.ts (7 errors) - module.controller.ts (7 errors) - progress.controller.ts (3 errors) - ranking.controller.ts (3 errors) - user.controller.ts (3 errors) - All *service.ts files #### 1.2 Fix undefined to string assignments **Pattern**: Add null checks before function calls **Example**: ```typescript // Before const id = req.params.id; // string | undefined await service.findById(id); // Error // After const id = req.params.id; if (!id) { throw new ValidationError('ID is required'); } await service.findById(id); // OK ``` #### 1.3 Fix Prisma type issues **Pattern**: Handle null types from Prisma **Example**: ```typescript // Before topicId: string; // But Prisma returns string | null // After topicId: string | null; ``` #### 1.4 Remove unused imports and variables **Pattern**: Clean up imports and use `_` prefix for unused params **Example**: ```typescript // Before import { ValidationError } from '../types'; // Unused const handler = (req, res, next) => { // req unused res.json({ data }); }; // After const handler = (_req, res, _next) => { res.json({ data }); }; ``` ### Phase 2: Implement Repository Pattern (Priority: High) #### 2.1 Create Repository Interfaces For each entity (User, Exercise, Module, Progress, Ranking): - Define interface in `src/repositories/interfaces/` - Specify all CRUD operations - Include query options and filters #### 2.2 Implement Prisma Repositories - Create implementation in `src/repositories/` - Use dependency injection - Add caching hooks - Handle errors with AppError #### 2.3 Migrate Services - Update services to use repositories - Add proper typing - Implement logging ### Phase 3: Implement DI Container (Priority: Medium) #### 3.1 Setup tsyringe ```typescript // In server.ts import 'reflect-metadata'; import { container } from './infrastructure/di/container'; // Register dependencies container.register(TOKENS.PrismaClient, { useValue: prisma }); container.register(TOKENS.ExerciseRepository, { useClass: ExerciseRepository }); ``` #### 3.2 Decorate Services ```typescript @injectable() export class ExerciseService { constructor( @inject(TOKENS.ExerciseRepository) private readonly repository: IExerciseRepository ) {} } ``` ### Phase 4: Update Server Configuration (Priority: Medium) #### 4.1 Replace Old Middleware Update `src/server.ts`: - Use new error middleware - Use new rate limiters - Add correlation ID middleware - Use structured logging #### 4.2 Add Health Checks - Database health - Redis health - AI service health ### Phase 5: Testing (Priority: High) #### 5.1 Unit Tests Target: >80% coverage - Service layer tests - Repository tests - Error handling tests #### 5.2 Integration Tests - API endpoint tests - Database transaction tests - Redis integration tests #### 5.3 E2E Tests - Full user flows - Error scenarios ## Code Examples ### New Service Pattern ```typescript // src/modules/exercise/exercise.service.ts import { injectable, inject } from 'tsyringe'; import { TOKENS } from '../../infrastructure/di/container'; import { IExerciseRepository } from '../../repositories/interfaces/exercise.repository.interface'; import { AppError, NotFoundError, ValidationError } from '../../core/errors'; import type { CreateExerciseDTO, UpdateExerciseDTO } from './dtos'; import type { Exercise } from '@prisma/client'; export interface IExerciseService { create(data: CreateExerciseDTO): Promise; findById(id: string): Promise; update(id: string, data: UpdateExerciseDTO): Promise; delete(id: string): Promise; list(filters: ExerciseFilterOptions): Promise>; } @injectable() export class ExerciseService implements IExerciseService { constructor( @inject(TOKENS.ExerciseRepository) private readonly repository: IExerciseRepository, @inject(TOKENS.Logger) private readonly logger: Logger ) {} async create(data: CreateExerciseDTO): Promise { this.logger.info('Creating exercise', { title: data.statement }); try { const exercise = await this.repository.create(data); this.logger.info('Exercise created', { exerciseId: exercise.id }); return exercise; } catch (error) { this.logger.error('Failed to create exercise', { error, data }); throw new AppError('Failed to create exercise', ErrorCode.INTERNAL_ERROR); } } async findById(id: string): Promise { if (!id) { throw new ValidationError('Exercise ID is required'); } const exercise = await this.repository.findById(id); if (!exercise) { throw new NotFoundError('Exercise'); } return exercise; } // ... other methods } ``` ### New Controller Pattern ```typescript // src/modules/exercise/exercise.controller.ts import { Request, Response } from 'express'; import { injectable, inject } from 'tsyringe'; import { TOKENS } from '../../infrastructure/di/container'; import { IExerciseService } from './exercise.service'; import { asyncHandler } from '../../shared/middleware/error.middleware'; @injectable() export class ExerciseController { constructor( @inject(TOKENS.ExerciseService) private readonly service: IExerciseService ) {} getById = asyncHandler(async (req: Request, res: Response): Promise => { const id = req.params.id; if (!id) { throw new ValidationError('Exercise ID is required'); } const exercise = await this.service.findById(id); res.json({ success: true, data: exercise, }); }); // ... other methods } ``` ### Error Handling ```typescript // In controllers - just throw try { await service.doSomething(); } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { throw mapPrismaError(error, req.correlationId); } throw new AppError( 'Operation failed', ErrorCode.INTERNAL_ERROR, 500, false, { originalError: error }, req.correlationId ); } // Middleware handles the rest ``` ## Commands ### Check TypeScript Errors ```bash cd /home/ren/Documents/math2/backend npm run type-check ``` ### Fix Lint Issues ```bash npm run lint:fix ``` ### Run Tests ```bash npm run test:coverage ``` ### Build ```bash npm run build ``` ## Recommendations 1. **Enable strict mode in phases**: Fix errors in batches by directory 2. **Use type assertion sparingly**: Only for external library issues 3. **Add comprehensive tests**: Before refactoring, add tests for current behavior 4. **Use feature flags**: For gradual rollout of new architecture 5. **Document breaking changes**: Update API documentation 6. **Monitor error rates**: After deployment ## Timeline Estimate - Phase 1 (TypeScript fixes): 2-3 days - Phase 2 (Repositories): 3-4 days - Phase 3 (DI setup): 1-2 days - Phase 4 (Server update): 1-2 days - Phase 5 (Testing): 3-4 days **Total**: ~10-15 days for complete migration ## Next Steps 1. Continue fixing TypeScript errors using the patterns above 2. Create repository interfaces for all entities 3. Implement Prisma repositories 4. Refactor services one by one 5. Update server.ts with new middleware 6. Add comprehensive tests 7. Deploy and monitor ## Resources - [TypeScript Strict Mode Guide](https://www.typescriptlang.org/tsconfig#strict) - [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) - [tsyringe Documentation](https://github.com/microsoft/tsyringe) - [Prisma Best Practices](https://www.prisma.io/docs/guides/best-practices)