Momentum LMS - Modularity Refactoring Guide

Version: 1.0 Date: 2025-12-06 Status: ✅ COMPLETED (2025-12-07)
Purpose: Systematic codebase optimization for AI agent development and long-term maintainability


Executive Summary

Current State Assessment

The Momentum LMS codebase is approximately 80% complete for MVP and demonstrates good architectural foundations with the repository pattern, separation of concerns, and TypeScript throughout. However, there are significant opportunities to improve modularity, reduce code duplication, and better align with SOLID principles to optimize for future AI agent-driven development.

Codebase Statistics:

  • Total TypeScript/JavaScript LOC: ~81,642 lines
  • Backend Functions: 10 Lambda handlers
  • Frontend Components: 20+ major component directories
  • Shared Backend Code: Repositories, utilities, services well-established
  • Test Coverage: Extensive (tests often larger than implementation)

Key Strengths: ✅ Repository pattern consistently implemented
✅ Shared backend utilities (validation, database, Redis)
✅ TypeScript strict mode throughout
✅ Good test coverage with dedicated test files
✅ Logical component organization by feature

Critical Issues Identified: ❌ Code Duplication: createResponse and getUserIdFromEvent duplicated across 10+ Lambda handlers
Large Components: Several 400-700 line components mixing presentation, state, and business logic
Business Logic in Components: Form validation, API orchestration, data transformation in React components
Monolithic Handlers: Lambda handlers mixing routing, validation, authorization, and business logic
Missing Abstraction Layers: No service layer between repositories and handlers
Inconsistent Error Handling: Different patterns across frontend and backend
Frontend API Duplication: Similar apiRequest pattern repeated across multiple API client files


Detailed Analysis by Layer

1. Backend Lambda Handlers (Critical Priority)

Problem: Monolithic Handler Pattern

Affected Files (10 files with ~300-350 lines each):

  • /backend/functions/courses/src/index.ts (351 lines)
  • /backend/functions/lessons/src/index.ts (351 lines)
  • /backend/functions/enrollments/src/index.ts (321 lines)
  • /backend/functions/badges/src/index.ts (349 lines)
  • /backend/functions/users/src/index.ts (estimated 300+ lines)
  • /backend/functions/progress/src/index.ts (estimated 300+ lines)
  • /backend/functions/analytics/src/index.ts (estimated 300+ lines)
  • /backend/functions/recommendations/src/index.ts (estimated 300+ lines)

Current Anti-Pattern:

// backend/functions/lessons/src/index.ts (351 lines)
// ❌ VIOLATES: Single Responsibility Principle

// Function 1: Duplicated across 10 handlers
function createResponse(statusCode: number, body: any): APIGatewayProxyResult {
  const allowedOrigin = process.env.ALLOWED_ORIGIN || '*';
  return {
    statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': allowedOrigin,
      'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
      'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
    },
    body: JSON.stringify(body),
  };
}

// Function 2: Duplicated across 4 handlers
async function getUserIdFromEvent(event: APIGatewayProxyEvent): Promise<string | null> {
  const claims = event.requestContext?.authorizer?.claims;
  const cognitoSub = claims?.sub;
  if (!cognitoSub) return null;
  const user = await userRepo.findByCognitoSub(cognitoSub);
  return user?.id || null;
}

// Function 3: Business logic mixed with routing
async function handleGet(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  try {
    const { id, course_id, limit = '100', offset = '0' } = event.queryStringParameters || {};
    const userId = await getUserIdFromEvent(event);

    // 50+ lines of business logic, validation, authorization
    if (id) {
      const lesson = await lessonRepo.findByIdWithCourse(id);
      if (!lesson) return createResponse(404, { error: 'Lesson not found' });
      
      const accessCheck = await canAccessLesson(userId, id, lesson.course_id);
      if (!accessCheck.allowed) {
        // Complex authorization logic
      }
      return createResponse(200, lesson);
    }

    // More routing logic...
  } catch (error) {
    return createResponse(500, { error: 'Internal server error' });
  }
}

// Function 4: Entry point mixing HTTP method routing
export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  if (event.httpMethod === 'OPTIONS') return handleOptions();
  if (event.httpMethod === 'GET') return handleGet(event);
  if (event.httpMethod === 'POST') return handlePost(event);
  if (event.httpMethod === 'PUT') return handlePut(event);
  if (event.httpMethod === 'DELETE') return handleDelete(event);
  return createResponse(405, { error: 'Method not allowed' });
}

SOLID Violations:

  • Single Responsibility: Handler does routing, validation, authorization, error handling
  • Open/Closed: Adding new endpoints requires modifying handler
  • Dependency Inversion: Direct repository coupling, hard to test

Recommended Refactoring:

Step 1: Extract shared Lambda utilities (P0 - Highest Impact)

// backend/shared/utils/lambda-response.ts
export function createSuccessResponse<T>(data: T, statusCode: number = 200): APIGatewayProxyResult {
  return createResponse(statusCode, { success: true, data });
}

export function createErrorResponse(error: string, statusCode: number = 500): APIGatewayProxyResult {
  return createResponse(statusCode, { success: false, error });
}

function createResponse(statusCode: number, body: any): APIGatewayProxyResult {
  const allowedOrigin = process.env.ALLOWED_ORIGIN || '*';
  return {
    statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': allowedOrigin,
      'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
      'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
    },
    body: JSON.stringify(body),
  };
}

Step 2: Extract authentication/authorization utilities

// backend/shared/utils/lambda-auth.ts
import { APIGatewayProxyEvent } from 'aws-lambda';
import { UserRepository } from '../repositories/UserRepository';

export interface AuthContext {
  userId: string;
  cognitoSub: string;
  email: string;
  role: string;
}

export async function getAuthContext(event: APIGatewayProxyEvent): Promise<AuthContext | null> {
  const claims = event.requestContext?.authorizer?.claims;
  const cognitoSub = claims?.sub;
  if (!cognitoSub) return null;

  const userRepo = new UserRepository();
  const user = await userRepo.findByCognitoSub(cognitoSub);
  if (!user) return null;

  return {
    userId: user.id,
    cognitoSub: user.cognito_sub,
    email: user.email,
    role: user.role,
  };
}

export function requireAuth(authContext: AuthContext | null): AuthContext {
  if (!authContext) {
    throw new AuthenticationError('Authentication required');
  }
  return authContext;
}

export function requireRole(authContext: AuthContext, requiredRole: string): void {
  if (authContext.role !== requiredRole) {
    throw new AuthorizationError(`Role ${requiredRole} required`);
  }
}

export class AuthenticationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'AuthenticationError';
  }
}

export class AuthorizationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'AuthorizationError';
  }
}

Step 3: Create service layer (currently missing)

// backend/shared/services/LessonService.ts
import { LessonRepository } from '../repositories/LessonRepository';
import { EnrollmentRepository } from '../repositories/EnrollmentRepository';
import { ProgressRepository } from '../repositories/ProgressRepository';
import { Lesson, CreateLessonDTO, UpdateLessonDTO } from '../types/database';

export interface LessonAccessResult {
  allowed: boolean;
  reason?: 'not_authenticated' | 'not_enrolled' | 'previous_lesson_incomplete' | 'lesson_not_found';
}

export class LessonService {
  constructor(
    private lessonRepo: LessonRepository,
    private enrollmentRepo: EnrollmentRepository,
    private progressRepo: ProgressRepository
  ) {}

  async getLessonById(lessonId: string, userId: string | null): Promise<Lesson> {
    const lesson = await this.lessonRepo.findByIdWithCourse(lessonId);
    if (!lesson) {
      throw new NotFoundError('Lesson not found');
    }

    const accessCheck = await this.checkLessonAccess(userId, lessonId, lesson.course_id);
    if (!accessCheck.allowed) {
      throw new AccessDeniedError(accessCheck.reason || 'Access denied');
    }

    return lesson;
  }

  async getLessonsByCourse(courseId: string, userId: string | null, limit: number, offset: number): Promise<{ lessons: Lesson[], total: number }> {
    const lessons = await this.lessonRepo.findByCourse(courseId, limit, offset);
    const total = await this.lessonRepo.countByCourse(courseId);
    const lessonsWithLockStatus = await this.addLockedStatus(lessons, courseId, userId);
    return { lessons: lessonsWithLockStatus, total };
  }

  async createLesson(data: CreateLessonDTO): Promise<Lesson> {
    // Business logic for creating lessons
    return await this.lessonRepo.createLesson(data);
  }

  async updateLesson(lessonId: string, data: UpdateLessonDTO): Promise<Lesson> {
    // Business logic for updating lessons
    return await this.lessonRepo.updateLesson(lessonId, data);
  }

  async deleteLesson(lessonId: string): Promise<void> {
    // Business logic for deleting lessons
    await this.lessonRepo.deleteLesson(lessonId);
  }

  private async checkLessonAccess(userId: string | null, lessonId: string, courseId: string): Promise<LessonAccessResult> {
    // Extract complex access logic from handler
    // ... (implementation)
  }

  private async addLockedStatus(lessons: Lesson[], courseId: string, userId: string | null): Promise<Lesson[]> {
    // Extract locked status logic from handler
    // ... (implementation)
  }
}

export class NotFoundError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'NotFoundError';
  }
}

export class AccessDeniedError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'AccessDeniedError';
  }
}

Step 4: Refactored handler (thin controller)

// backend/functions/lessons/src/index.ts (target: <100 lines)
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { LessonService } from '../../../shared/services/LessonService';
import { LessonRepository } from '../../../shared/repositories/LessonRepository';
import { EnrollmentRepository } from '../../../shared/repositories/EnrollmentRepository';
import { ProgressRepository } from '../../../shared/repositories/ProgressRepository';
import { getAuthContext, requireAuth } from '../../../shared/utils/lambda-auth';
import { createSuccessResponse, createErrorResponse } from '../../../shared/utils/lambda-response';
import { validateCreateLessonInput, validateUpdateLessonInput } from '../../../shared/utils/validation';
import { CreateLessonDTO, UpdateLessonDTO } from '../../../shared/types/database';

const lessonService = new LessonService(
  new LessonRepository(),
  new EnrollmentRepository(),
  new ProgressRepository()
);

async function handleGet(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  try {
    const { id, course_id, limit = '100', offset = '0' } = event.queryStringParameters || {};
    const authContext = await getAuthContext(event);
    const userId = authContext?.userId || null;

    if (id) {
      const lesson = await lessonService.getLessonById(id, userId);
      return createSuccessResponse(lesson);
    }

    if (course_id) {
      const result = await lessonService.getLessonsByCourse(course_id, userId, parseInt(limit), parseInt(offset));
      return createSuccessResponse(result);
    }

    return createErrorResponse('Missing required parameters', 400);
  } catch (error) {
    return handleError(error);
  }
}

async function handlePost(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  try {
    const authContext = requireAuth(await getAuthContext(event));
    const data: CreateLessonDTO = JSON.parse(event.body || '{}');
    
    const validationErrors = validateCreateLessonInput(data);
    if (validationErrors.length > 0) {
      return createErrorResponse('Validation failed', 400);
    }

    const lesson = await lessonService.createLesson(data);
    return createSuccessResponse(lesson, 201);
  } catch (error) {
    return handleError(error);
  }
}

async function handlePut(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  try {
    const authContext = requireAuth(await getAuthContext(event));
    const lessonId = event.pathParameters?.id;
    if (!lessonId) return createErrorResponse('Lesson ID required', 400);

    const data: UpdateLessonDTO = JSON.parse(event.body || '{}');
    const validationErrors = validateUpdateLessonInput(data);
    if (validationErrors.length > 0) {
      return createErrorResponse('Validation failed', 400);
    }

    const lesson = await lessonService.updateLesson(lessonId, data);
    return createSuccessResponse(lesson);
  } catch (error) {
    return handleError(error);
  }
}

async function handleDelete(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  try {
    const authContext = requireAuth(await getAuthContext(event));
    const lessonId = event.pathParameters?.id;
    if (!lessonId) return createErrorResponse('Lesson ID required', 400);

    await lessonService.deleteLesson(lessonId);
    return createSuccessResponse({ success: true }, 204);
  } catch (error) {
    return handleError(error);
  }
}

function handleError(error: unknown): APIGatewayProxyResult {
  if (error instanceof NotFoundError) return createErrorResponse(error.message, 404);
  if (error instanceof AccessDeniedError) return createErrorResponse(error.message, 403);
  if (error instanceof AuthenticationError) return createErrorResponse(error.message, 401);
  if (error instanceof AuthorizationError) return createErrorResponse(error.message, 403);
  
  console.error('Unhandled error:', error);
  return createErrorResponse('Internal server error', 500);
}

export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  if (event.httpMethod === 'GET') return handleGet(event);
  if (event.httpMethod === 'POST') return handlePost(event);
  if (event.httpMethod === 'PUT') return handlePut(event);
  if (event.httpMethod === 'DELETE') return handleDelete(event);
  return createErrorResponse('Method not allowed', 405);
}

Impact:

  • Reduces handler size from 351 lines to ~100 lines (71% reduction)
  • Eliminates duplication across 10 Lambda handlers (~3,000 lines saved)
  • Introduces testable service layer
  • Improves maintainability and AI agent comprehension

2. Frontend Large Components (High Priority)

Problem: Monolithic Components with Mixed Concerns

Affected Files (6 files over 400 lines):

  • /frontend/components/admin/ai-generation/CourseGenerationForm.tsx (673 lines) ⚠️
  • /frontend/components/admin/CourseForm.tsx (427 lines)
  • /frontend/components/admin/ai-generation/GeneratedCoursePreview.tsx (403 lines)
  • /frontend/components/admin/LessonForm.tsx (337 lines)
  • /frontend/components/dashboard/ProgressTab.tsx (317 lines)
  • /frontend/app/admin/users/[id]/edit/page.tsx (531 lines)

Current Anti-Pattern - CourseGenerationForm.tsx (673 lines):

// ❌ VIOLATES: Single Responsibility Principle
// Component mixes: Form state, validation, file upload, API calls, UI rendering

export function CourseGenerationForm({ onGenerationStart }: CourseGenerationFormProps) {
  // 1. State management (20+ useState hooks)
  const [formData, setFormData] = useState<FormData>({ /* ... */ });
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [categories, setCategories] = useState<Category[]>(FALLBACK_CATEGORIES);
  const [objectiveInput, setObjectiveInput] = useState('');
  const [pdfFile, setPdfFile] = useState<File | null>(null);
  // ... 15+ more state variables

  // 2. Data fetching logic
  useEffect(() => {
    async function loadCategories() {
      try {
        const response = await getCategories();
        setCategories(response.categories);
      } catch (err) {
        console.error('Failed to load categories:', err);
      }
    }
    loadCategories();
  }, []);

  // 3. Form validation logic (100+ lines)
  const validateForm = () => {
    // Complex validation logic inline
  };

  // 4. File upload logic
  const handleFileUpload = async (file: File) => {
    // PDF upload and processing logic
  };

  // 5. Form submission orchestration
  const handleSubmit = async (e: FormEvent) => {
    // 80+ lines of submission logic
  };

  // 6. UI rendering (400+ lines of JSX)
  return (
    <form onSubmit={handleSubmit}>
      {/* Massive form with inline styles and logic */}
    </form>
  );
}

Recommended Refactoring:

Step 1: Extract form state management to custom hook

// frontend/hooks/useCourseGenerationForm.ts
export interface CourseGenerationFormData {
  title: string;
  category_id: string;
  duration_days: 7 | 14 | 21;
  targetAudience: string;
  difficultyLevel: 'beginner' | 'intermediate' | 'advanced';
  learningObjectives: string[];
  tone: 'professional' | 'friendly' | 'academic';
  referenceMaterials: string;
  pdfFile: File | null;
  isAsync: boolean;
  generateVideo: boolean;
}

export function useCourseGenerationForm(initialData?: Partial<CourseGenerationFormData>) {
  const [formData, setFormData] = useState<CourseGenerationFormData>({
    title: initialData?.title || '',
    category_id: initialData?.category_id || '',
    duration_days: initialData?.duration_days || 14,
    // ... other fields
  });

  const [errors, setErrors] = useState<Record<string, string>>({});

  const updateField = (field: keyof CourseGenerationFormData, value: any) => {
    setFormData(prev => ({ ...prev, [field]: value }));
    // Clear error for this field when user types
    if (errors[field]) {
      setErrors(prev => ({ ...prev, [field]: '' }));
    }
  };

  const validate = (): boolean => {
    const newErrors: Record<string, string> = {};
    
    if (!formData.title.trim()) {
      newErrors.title = 'Title is required';
    }
    if (!formData.category_id) {
      newErrors.category_id = 'Category is required';
    }
    if (formData.learningObjectives.length < 3) {
      newErrors.learningObjectives = 'At least 3 learning objectives required';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const reset = () => {
    setFormData({ /* initial values */ });
    setErrors({});
  };

  return {
    formData,
    errors,
    updateField,
    validate,
    reset,
  };
}

Step 2: Extract PDF upload logic to service

// frontend/lib/services/pdf-upload-service.ts
const MAX_PDF_SIZE = 4 * 1024 * 1024; // 4MB

export interface PdfUploadResult {
  referenceId: string;
  fileName: string;
  fileSize: number;
}

export class PdfUploadError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'PdfUploadError';
  }
}

export async function uploadPdfReference(file: File, jobId: string): Promise<PdfUploadResult> {
  // Validate file
  if (!file.name.toLowerCase().endsWith('.pdf')) {
    throw new PdfUploadError('Only PDF files are allowed');
  }

  if (file.size > MAX_PDF_SIZE) {
    throw new PdfUploadError(`File size must be less than ${MAX_PDF_SIZE / 1024 / 1024}MB`);
  }

  // Upload logic
  const formData = new FormData();
  formData.append('pdf', file);
  formData.append('jobId', jobId);

  const response = await fetch(`${API_BASE_URL}/ai-generation/upload-pdf`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${await getIdToken()}`,
      'x-api-key': API_KEY,
    },
    body: formData,
  });

  if (!response.ok) {
    throw new PdfUploadError('Failed to upload PDF');
  }

  return await response.json();
}

Step 3: Extract form sections into smaller components

// frontend/components/admin/ai-generation/sections/BasicInfoSection.tsx
interface BasicInfoSectionProps {
  formData: CourseGenerationFormData;
  errors: Record<string, string>;
  categories: Category[];
  isLoadingCategories: boolean;
  onFieldChange: (field: string, value: any) => void;
}

export function BasicInfoSection({ formData, errors, categories, isLoadingCategories, onFieldChange }: BasicInfoSectionProps) {
  return (
    <div className="space-y-4">
      <div>
        <label className="block text-sm font-medium text-gray-700 mb-2">
          Course Title *
        </label>
        <input
          type="text"
          value={formData.title}
          onChange={(e) => onFieldChange('title', e.target.value)}
          className="w-full px-4 py-2 border-2 border-gray-300 rounded-lg"
          placeholder="e.g., Master Public Speaking in 14 Days"
        />
        {errors.title && <p className="mt-1 text-sm text-red-600">{errors.title}</p>}
      </div>

      <div>
        <label className="block text-sm font-medium text-gray-700 mb-2">
          Category *
        </label>
        <select
          value={formData.category_id}
          onChange={(e) => onFieldChange('category_id', e.target.value)}
          className="w-full px-4 py-2 border-2 border-gray-300 rounded-lg"
          disabled={isLoadingCategories}
        >
          <option value="">Select a category</option>
          {categories.map(cat => (
            <option key={cat.id} value={cat.id}>{cat.name}</option>
          ))}
        </select>
        {errors.category_id && <p className="mt-1 text-sm text-red-600">{errors.category_id}</p>}
      </div>

      {/* Duration selector */}
      {/* ... */}
    </div>
  );
}

// frontend/components/admin/ai-generation/sections/LearningObjectivesSection.tsx
// frontend/components/admin/ai-generation/sections/AdvancedOptionsSection.tsx
// frontend/components/admin/ai-generation/sections/PdfUploadSection.tsx

Step 4: Refactored CourseGenerationForm (target: <200 lines)

// frontend/components/admin/ai-generation/CourseGenerationForm.tsx
import { useState } from 'react';
import { useCourseGenerationForm } from '@/hooks/useCourseGenerationForm';
import { useCategories } from '@/hooks/useCategories';
import { startCourseGeneration } from '@/lib/api/ai-generation';
import { uploadPdfReference } from '@/lib/services/pdf-upload-service';
import { BasicInfoSection } from './sections/BasicInfoSection';
import { LearningObjectivesSection } from './sections/LearningObjectivesSection';
import { AdvancedOptionsSection } from './sections/AdvancedOptionsSection';
import { PdfUploadSection } from './sections/PdfUploadSection';
import { Sparkles, Loader2 } from 'lucide-react';

interface CourseGenerationFormProps {
  onGenerationStart: (jobId: string) => void;
}

export function CourseGenerationForm({ onGenerationStart }: CourseGenerationFormProps) {
  const { formData, errors, updateField, validate, reset } = useCourseGenerationForm();
  const { categories, isLoading: isLoadingCategories } = useCategories();
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState<string | null>(null);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    
    if (!validate()) {
      return;
    }

    setIsSubmitting(true);
    setSubmitError(null);

    try {
      // Start generation
      const result = await startCourseGeneration(formData);
      
      // Upload PDF if provided
      if (formData.pdfFile) {
        await uploadPdfReference(formData.pdfFile, result.jobId);
      }

      onGenerationStart(result.jobId);
      reset();
    } catch (error) {
      setSubmitError(error instanceof Error ? error.message : 'Failed to start generation');
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-8">
      {submitError && (
        <ErrorAlert message={submitError} onDismiss={() => setSubmitError(null)} />
      )}

      <BasicInfoSection
        formData={formData}
        errors={errors}
        categories={categories}
        isLoadingCategories={isLoadingCategories}
        onFieldChange={updateField}
      />

      <LearningObjectivesSection
        objectives={formData.learningObjectives}
        errors={errors}
        onObjectivesChange={(objectives) => updateField('learningObjectives', objectives)}
      />

      <AdvancedOptionsSection
        formData={formData}
        errors={errors}
        onFieldChange={updateField}
      />

      <PdfUploadSection
        pdfFile={formData.pdfFile}
        onFileChange={(file) => updateField('pdfFile', file)}
      />

      <div className="flex gap-4">
        <button
          type="submit"
          disabled={isSubmitting}
          className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 text-white font-semibold rounded-lg"
        >
          {isSubmitting ? (
            <>
              <Loader2 className="w-5 h-5 animate-spin" />
              Generating...
            </>
          ) : (
            <>
              <Sparkles className="w-5 h-5" />
              Generate Course
            </>
          )}
        </button>
      </div>
    </form>
  );
}

Impact:

  • Reduces component size from 673 lines to ~150 lines (78% reduction)
  • Extracts reusable hooks and services
  • Improves testability (can test hook, service, and sections independently)
  • Better separation of concerns

3. Frontend API Client Layer (Medium Priority)

Problem: Duplicated API Request Logic

Affected Files (12 files with similar patterns):

  • /frontend/lib/api/courses.ts (118 lines)
  • /frontend/lib/api/lessons.ts (132 lines)
  • /frontend/lib/api/enrollment-client.ts (199 lines)
  • /frontend/lib/api/progress.ts (276 lines)
  • /frontend/lib/api/analytics.ts (295 lines)
  • /frontend/lib/api/admin-analytics.ts (236 lines)
  • /frontend/lib/api/recommendations.ts (254 lines)
  • /frontend/lib/api/ai-generation.ts (220 lines)
  • /frontend/lib/api/users.ts (135 lines)
  • /frontend/lib/api/categories.ts (75 lines)

Current Anti-Pattern:

// Duplicated in ALL 12 API client files
function handleAPIError(error: unknown): never {
  console.error('API Error:', error);
  const errorMessage = error instanceof Error ? error.message : 'An error occurred';
  throw new Error(errorMessage);
}

async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const url = `${API_BASE_URL}${endpoint}`;
  const token = await getIdToken();
  
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    'x-api-key': API_KEY,
    ...options.headers as Record<string, string>,
  };

  if (token) {
    headers['Authorization'] = `Bearer ${token}`;
  }

  try {
    const response = await fetch(url, { ...options, headers });
    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      throw new Error(errorData.error || `HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    return handleAPIError(error);
  }
}

Recommended Refactoring:

Step 1: Create centralized API client

// frontend/lib/api/api-client.ts
import { getIdToken } from '../auth/cognito-client';

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '';
const API_KEY = process.env.NEXT_PUBLIC_API_KEY || '';

export class ApiError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public response?: any
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

export class ApiClient {
  private baseUrl: string;
  private apiKey: string;

  constructor(baseUrl: string = API_BASE_URL, apiKey: string = API_KEY) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
  }

  async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;
    const token = await getIdToken();

    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'x-api-key': this.apiKey,
      ...options.headers as Record<string, string>,
    };

    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }

    try {
      const response = await fetch(url, { ...options, headers });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new ApiError(
          errorData.error || `HTTP ${response.status}: ${response.statusText}`,
          response.status,
          errorData
        );
      }

      // Handle 204 No Content
      if (response.status === 204) {
        return {} as T;
      }

      return await response.json();
    } catch (error) {
      if (error instanceof ApiError) {
        throw error;
      }
      throw new ApiError(
        error instanceof Error ? error.message : 'Network error',
        0
      );
    }
  }

  async get<T>(endpoint: string, params?: Record<string, string>): Promise<T> {
    const queryString = params
      ? '?' + new URLSearchParams(params).toString()
      : '';
    return this.request<T>(`${endpoint}${queryString}`, { method: 'GET' });
  }

  async post<T>(endpoint: string, data: any): Promise<T> {
    return this.request<T>(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }

  async put<T>(endpoint: string, data: any): Promise<T> {
    return this.request<T>(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
  }

  async delete<T>(endpoint: string): Promise<T> {
    return this.request<T>(endpoint, { method: 'DELETE' });
  }
}

// Singleton instance
export const apiClient = new ApiClient();

Step 2: Refactored API client files

// frontend/lib/api/courses.ts (target: <50 lines)
import { apiClient } from './api-client';
import { Course, CreateCourseDTO, UpdateCourseDTO, CoursesListResponse } from './types';

export async function getCourses(params?: {
  category?: string;
  search?: string;
  duration?: 7 | 14 | 21;
  limit?: number;
  offset?: number;
}): Promise<CoursesListResponse> {
  return apiClient.get<CoursesListResponse>('/courses', params as Record<string, string>);
}

export async function getCourseById(id: string): Promise<Course> {
  return apiClient.get<Course>(`/courses/${id}`);
}

export async function createCourse(data: CreateCourseDTO): Promise<Course> {
  return apiClient.post<Course>('/courses', data);
}

export async function updateCourse(id: string, data: UpdateCourseDTO): Promise<Course> {
  return apiClient.put<Course>(`/courses/${id}`, data);
}

export async function deleteCourse(id: string): Promise<void> {
  return apiClient.delete(`/courses/${id}`);
}

Impact:

  • Eliminates ~1,200 lines of duplicated code across 12 files
  • Centralized error handling and retry logic
  • Easier to add interceptors, caching, or request/response transformation

4. Backend AI Generation Handlers (Medium Priority)

Problem: Large Handler with Mixed Responsibilities

Affected File:

  • /backend/functions/ai-generation/src/handlers/save-course.ts (485 lines)

Current Issues:

  • Mixes validation, business logic, database operations
  • 80+ lines just for action routing (updateStatus, handleError, saveCourse)
  • Complex nested logic for course and lesson creation
  • Embedded quality scoring algorithm

Recommended Refactoring:

Step 1: Extract action handlers to separate files

// backend/functions/ai-generation/src/handlers/save-course/update-status-action.ts
// backend/functions/ai-generation/src/handlers/save-course/handle-error-action.ts
// backend/functions/ai-generation/src/handlers/save-course/save-course-action.ts

Step 2: Extract quality scoring to service

// backend/functions/ai-generation/src/services/course-quality-service.ts
export class CourseQualityService {
  calculateQualityScore(outline: CourseOutline, prompts: LessonPromptsResult): number {
    // Move 60-line quality calculation logic here
  }
}

Step 3: Extract course creation to service

// backend/functions/ai-generation/src/services/course-creation-service.ts
export class CourseCreationService {
  async createCourseWithLessons(data: CourseCreationData): Promise<CourseCreationResult> {
    // Transaction-based course + lessons creation
  }
}

5. Frontend Authentication Client (Medium Priority)

Problem: Large Authentication Utility

Affected File:

  • /frontend/lib/auth/cognito-client.ts (710 lines)

Issues:

  • Mixes sign-up, sign-in, session management, password reset
  • Complex Cognito SDK interactions scattered throughout
  • Difficult to test individual auth flows

Recommended Refactoring:

Step 1: Split into focused modules

// frontend/lib/auth/sign-up.ts
// frontend/lib/auth/sign-in.ts
// frontend/lib/auth/session-management.ts
// frontend/lib/auth/password-management.ts
// frontend/lib/auth/cognito-config.ts

Step 2: Create AuthService facade

// frontend/lib/auth/auth-service.ts
export class AuthService {
  constructor(
    private signUpService: SignUpService,
    private signInService: SignInService,
    private sessionService: SessionService
  ) {}

  // Delegate to specific services
  async signUp(params: SignUpParams) { return this.signUpService.signUp(params); }
  async signIn(params: SignInParams) { return this.signInService.signIn(params); }
  async getCurrentSession() { return this.sessionService.getCurrentSession(); }
}

Proposed Folder Structure Improvements

Backend Structure (After Refactoring)

backend/
├── shared/                              # Shared code across all functions
│   ├── repositories/                   # ✅ Already well-organized
│   │   ├── BaseRepository.ts
│   │   ├── CourseRepository.ts
│   │   ├── LessonRepository.ts
│   │   ├── UserRepository.ts
│   │   └── ...
│   ├── services/                       # 🆕 NEW: Business logic layer
│   │   ├── CourseService.ts
│   │   ├── LessonService.ts
│   │   ├── EnrollmentService.ts
│   │   ├── ProgressService.ts
│   │   ├── BadgeService.ts
│   │   ├── AnalyticsService.ts
│   │   └── RecommendationService.ts
│   ├── utils/                          # Enhanced utilities
│   │   ├── database.ts                # ✅ Existing
│   │   ├── validation.ts              # ✅ Existing
│   │   ├── redis.ts                   # ✅ Existing
│   │   ├── lambda-response.ts         # 🆕 NEW: Extracted response utilities
│   │   ├── lambda-auth.ts             # 🆕 NEW: Extracted auth utilities
│   │   ├── lambda-errors.ts           # 🆕 NEW: Custom error classes
│   │   └── recommendation-engine.ts   # ✅ Existing
│   └── types/                         # ✅ Already well-organized
│       ├── database.ts
│       └── api.ts
├── functions/
│   ├── courses/
│   │   └── src/
│   │       └── index.ts               # 📉 Reduced to <100 lines (thin controller)
│   ├── lessons/
│   │   └── src/
│   │       └── index.ts               # 📉 Reduced to <100 lines
│   ├── ai-generation/
│   │   └── src/
│   │       ├── handlers/
│   │       │   ├── save-course/       # 🆕 Split into sub-handlers
│   │       │   │   ├── index.ts
│   │       │   │   ├── save-course-action.ts
│   │       │   │   ├── update-status-action.ts
│   │       │   │   └── handle-error-action.ts
│   │       │   └── ...
│   │       └── services/
│   │           ├── course-quality-service.ts     # 🆕 NEW
│   │           ├── course-creation-service.ts    # 🆕 NEW
│   │           └── ...

Frontend Structure (After Refactoring)

frontend/
├── lib/
│   ├── api/
│   │   ├── api-client.ts              # 🆕 NEW: Centralized API client
│   │   ├── courses.ts                 # 📉 Reduced to <50 lines
│   │   ├── lessons.ts                 # 📉 Reduced to <50 lines
│   │   ├── enrollment-client.ts       # 📉 Reduced to <50 lines
│   │   ├── progress.ts                # 📉 Reduced to <50 lines
│   │   └── ...
│   ├── auth/
│   │   ├── auth-service.ts            # 🆕 NEW: Facade for auth operations
│   │   ├── sign-up.ts                 # 🆕 NEW: Split from cognito-client
│   │   ├── sign-in.ts                 # 🆕 NEW: Split from cognito-client
│   │   ├── session-management.ts      # 🆕 NEW: Split from cognito-client
│   │   ├── password-management.ts     # 🆕 NEW: Split from cognito-client
│   │   ├── cognito-config.ts          # 🆕 NEW: Configuration only
│   │   └── auth-context.tsx           # ✅ Existing
│   ├── services/                      # 🆕 NEW: Frontend business logic
│   │   ├── pdf-upload-service.ts
│   │   ├── form-validation-service.ts
│   │   └── analytics-service.ts
│   └── hooks/                         # Enhanced custom hooks
│       ├── useCourseGenerationForm.ts # 🆕 NEW
│       ├── useCategories.ts           # 🆕 NEW
│       ├── useCourseFilters.ts        # ✅ Existing
│       └── ...
├── components/
│   ├── admin/
│   │   ├── ai-generation/
│   │   │   ├── CourseGenerationForm.tsx    # 📉 Reduced to <200 lines
│   │   │   ├── sections/                   # 🆕 NEW: Form sections
│   │   │   │   ├── BasicInfoSection.tsx
│   │   │   │   ├── LearningObjectivesSection.tsx
│   │   │   │   ├── AdvancedOptionsSection.tsx
│   │   │   │   └── PdfUploadSection.tsx
│   │   │   └── ...
│   │   ├── CourseForm.tsx                  # 📉 Target: <250 lines
│   │   └── LessonForm.tsx                  # 📉 Target: <200 lines
│   ├── ui/                                 # 🆕 NEW: Reusable UI components
│   │   ├── ErrorAlert.tsx
│   │   ├── LoadingSpinner.tsx
│   │   ├── FormField.tsx
│   │   └── ...

Prioritized Refactoring Tasks

Priority 0 (P0) - Critical: Eliminate Code Duplication

Target: 1-2 weeks | Impact: Massive (3,000+ lines eliminated)

  1. Extract Lambda Response Utilities (2 days)
    • Create /backend/shared/utils/lambda-response.ts
    • Migrate all 10 handlers to use shared createResponse
    • Files affected: 10 Lambda handler files
    • Lines saved: ~150 lines
  2. Extract Lambda Auth Utilities (2 days)
    • Create /backend/shared/utils/lambda-auth.ts
    • Create getAuthContext, requireAuth, requireRole helpers
    • Migrate 4 handlers using getUserIdFromEvent
    • Files affected: 4 Lambda handler files
    • Lines saved: ~80 lines
  3. Create Centralized Frontend API Client (3 days)
    • Create /frontend/lib/api/api-client.ts
    • Refactor all 12 API client files to use centralized client
    • Files affected: 12 API client files
    • Lines saved: ~1,200 lines
  4. Create Backend Service Layer (5 days)
    • Create /backend/shared/services/ directory
    • Extract service classes for: Courses, Lessons, Enrollments, Progress
    • Move business logic from repositories and handlers to services
    • Files affected: 10+ files
    • Lines saved: ~800 lines (but more importantly, better separation)

Total P0 Impact: ~2,230 lines eliminated, critical architectural improvements


Priority 1 (P1) - High: Refactor Large Components

Target: 2-3 weeks | Impact: High (2,000+ lines refactored)

  1. Refactor CourseGenerationForm (4 days)
    • Extract useCourseGenerationForm hook
    • Extract form sections (4 components)
    • Extract PDF upload service
    • Current: 673 lines → Target: <200 lines
  2. Refactor CourseForm (3 days)
    • Extract useCourseForm hook
    • Extract form sections (3 components)
    • Current: 427 lines → Target: <250 lines
  3. Refactor LessonForm (3 days)
    • Extract useLessonForm hook
    • Extract rich text editor component
    • Current: 337 lines → Target: <200 lines
  4. Refactor Admin User Edit Page (3 days)
    • Extract useUserEditForm hook
    • Extract user profile sections
    • Current: 531 lines → Target: <250 lines
  5. Refactor ProgressTab (2 days)
    • Extract analytics calculation to service
    • Extract chart components
    • Current: 317 lines → Target: <200 lines

Total P1 Impact: ~1,300 lines reduction, improved component testability


Priority 2 (P2) - Medium: Architectural Improvements

Target: 2-3 weeks | Impact: Medium (maintainability and scalability)

  1. Split Authentication Client (4 days)
    • Split cognito-client.ts into 5 focused modules
    • Create AuthService facade
    • Current: 710 lines → Target: <150 lines per module
  2. Refactor save-course Handler (3 days)
    • Split action handlers into separate files
    • Extract quality scoring service
    • Extract course creation service
    • Current: 485 lines → Target: <150 lines
  3. Create Reusable UI Components Library (5 days)
    • Extract common UI patterns (FormField, ErrorAlert, LoadingSpinner, etc.)
    • Centralize in /frontend/components/ui/
    • Impact: Consistent UI, reduced duplication across components
  4. Implement Error Boundary Pattern (2 days)
    • Create consistent error handling across all pages
    • Implement error boundaries for component error recovery
    • Impact: Better user experience, easier debugging

Priority 3 (P3) - Low: Polish and Optimization

Target: Ongoing | Impact: Low (incremental improvements)

  1. Extract Constants and Configuration (1 week)
    • Move magic numbers and hardcoded values to constants
    • Centralize configuration in /frontend/lib/config/ and /backend/shared/config/
  2. Improve Type Safety (Ongoing)
    • Add stricter TypeScript configurations
    • Remove any types where possible
    • Create more specific domain types
  3. Documentation Improvements (Ongoing)
    • Add JSDoc comments to all public functions
    • Create architecture decision records (ADRs)
    • Update component documentation

Code Examples: Before/After Patterns

Pattern 1: Backend Handler Refactoring

Before (351 lines):

// ❌ Monolithic handler with mixed responsibilities
export async function handler(event: APIGatewayProxyEvent) {
  // Routing, validation, authorization, business logic, error handling all mixed
  if (event.httpMethod === 'GET') {
    // 80 lines of GET logic
  } else if (event.httpMethod === 'POST') {
    // 80 lines of POST logic
  }
  // ...
}

After (~80 lines total):

// ✅ Thin controller delegating to service layer
import { LessonService } from '../../../shared/services/LessonService';
import { getAuthContext, requireAuth } from '../../../shared/utils/lambda-auth';
import { createSuccessResponse, createErrorResponse } from '../../../shared/utils/lambda-response';

const lessonService = new LessonService();

async function handleGet(event: APIGatewayProxyEvent) {
  const authContext = await getAuthContext(event);
  const userId = authContext?.userId || null;
  const result = await lessonService.getLessonsByCourse(courseId, userId, limit, offset);
  return createSuccessResponse(result);
}

export async function handler(event: APIGatewayProxyEvent) {
  try {
    if (event.httpMethod === 'GET') return handleGet(event);
    if (event.httpMethod === 'POST') return handlePost(event);
    return createErrorResponse('Method not allowed', 405);
  } catch (error) {
    return handleError(error);
  }
}

Pattern 2: Frontend Component Refactoring

Before (673 lines):

// ❌ Massive component with mixed concerns
export function CourseGenerationForm() {
  // 20+ useState hooks
  // Data fetching logic
  // Validation logic
  // File upload logic
  // Form submission logic
  // 400+ lines of JSX
}

After (~150 lines):

// ✅ Focused component delegating to hooks and services
export function CourseGenerationForm({ onGenerationStart }) {
  const { formData, errors, updateField, validate } = useCourseGenerationForm();
  const { categories, isLoading } = useCategories();

  const handleSubmit = async (e) => {
    if (!validate()) return;
    const result = await startCourseGeneration(formData);
    if (formData.pdfFile) await uploadPdfReference(formData.pdfFile, result.jobId);
    onGenerationStart(result.jobId);
  };

  return (
    <form onSubmit={handleSubmit}>
      <BasicInfoSection formData={formData} errors={errors} onFieldChange={updateField} />
      <LearningObjectivesSection objectives={formData.learningObjectives} />
      <AdvancedOptionsSection formData={formData} onFieldChange={updateField} />
      <PdfUploadSection pdfFile={formData.pdfFile} />
      <SubmitButton isSubmitting={isSubmitting} />
    </form>
  );
}

Pattern 3: API Client Refactoring

Before (duplicated in 12 files):

// ❌ Duplicated apiRequest function in every API client file
async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const url = `${API_BASE_URL}${endpoint}`;
  const token = await getIdToken();
  const headers = { /* ... */ };
  const response = await fetch(url, { ...options, headers });
  // Error handling...
  return await response.json();
}

export async function getCourses(params) {
  const queryParams = new URLSearchParams(params);
  return apiRequest(`/courses?${queryParams}`);
}

After (centralized):

// ✅ Centralized API client used by all modules
import { apiClient } from './api-client';

export async function getCourses(params) {
  return apiClient.get('/courses', params);
}

Guidelines for Future Development

1. Component Size Guidelines

  • Page components: <300 lines
  • Feature components: <200 lines
  • UI components: <100 lines
  • If exceeding limits: Extract hooks, services, or sub-components

2. Function Size Guidelines

  • Lambda handlers: <150 lines (thin controllers)
  • Service methods: <50 lines each
  • Utility functions: <30 lines
  • If exceeding limits: Extract helper functions or services

3. File Organization Rules

  • One primary export per file (exceptions: types, constants)
  • Group related functions in the same directory
  • Use index.ts for public API (barrel exports)
  • Keep private helpers in separate files or at bottom

4. SOLID Principles Checklist

Single Responsibility Principle (SRP):

  • Each class/function has ONE reason to change
  • Components focus on presentation, not business logic
  • Services handle business logic, not data access
  • Repositories handle data access, not business logic

Open/Closed Principle (OCP):

  • Use dependency injection for extensibility
  • Prefer composition over inheritance
  • Use strategy pattern for algorithm variations

Liskov Substitution Principle (LSP):

  • Derived classes can replace base classes
  • Interfaces are properly abstracted
  • No unexpected behavior in overrides

Interface Segregation Principle (ISP):

  • Interfaces are focused and specific
  • No “fat interfaces” with unused methods
  • Props interfaces are minimal

Dependency Inversion Principle (DIP):

  • Depend on abstractions, not concretions
  • Use dependency injection
  • High-level modules don’t depend on low-level modules

5. Code Duplication Rules

  • DRY Threshold: If code appears 3+ times, extract it
  • Shared utilities: Place in /shared/utils/
  • Shared services: Place in /shared/services/
  • Shared components: Place in /components/ui/

6. Testing Guidelines

  • Unit tests: For services, utilities, hooks (80% coverage target)
  • Integration tests: For API endpoints, repositories
  • Component tests: For UI components with React Testing Library
  • E2E tests: For critical user flows

AI Agent Refactoring Checklist

When refactoring code, AI agents should follow this systematic checklist:

Phase 1: Analysis

  • Read the entire file to understand current responsibilities
  • Identify violations of SOLID principles
  • List all dependencies (imports, external calls)
  • Find code duplication within file and across codebase
  • Measure complexity (lines, cyclomatic complexity)

Phase 2: Planning

  • Define clear boundaries for each responsibility
  • Identify extraction candidates (hooks, services, utilities, components)
  • Plan file structure (what goes where)
  • Determine dependencies (what needs to be created first)
  • Estimate impact (files affected, lines changed)

Phase 3: Execution

  • Create shared utilities first (foundation)
  • Extract services next (business logic)
  • Refactor handlers/components last (consumers)
  • Update imports throughout codebase
  • Maintain backward compatibility during transition

Phase 4: Validation

  • Run all tests to ensure no regressions
  • Check TypeScript compilation for type errors
  • Verify functionality with manual testing
  • Review code coverage to ensure maintained or improved
  • Update documentation to reflect changes

Phase 5: Cleanup

  • Remove dead code and unused imports
  • Consolidate similar functions where appropriate
  • Update comments and JSDoc
  • Format code consistently
  • Commit with descriptive message following conventional commits

Refactoring Safety Guidelines

Rule 1: Test-Driven Refactoring

  1. Ensure tests exist and pass before refactoring
  2. Run tests after each extraction to catch regressions early
  3. Add tests for new services/hooks before using them
  4. Never refactor without tests (if tests missing, add them first)

Rule 2: Incremental Changes

  1. One responsibility at a time (don’t refactor everything at once)
  2. Small commits (each commit should be independently reviewable)
  3. Validate after each step (run tests, check compilation)
  4. Rollback if issues arise (git revert is your friend)

Rule 3: Backward Compatibility

  1. Deprecate before removing (give consumers time to migrate)
  2. Use adapter pattern if API changes are necessary
  3. Maintain exports from original locations during transition
  4. Document breaking changes clearly

Rule 4: Communication

  1. Document architectural decisions in ADRs (Architecture Decision Records)
  2. Update README with new patterns and guidelines
  3. Add comments explaining complex extractions
  4. Notify team of major structural changes

Measuring Success

Quantitative Metrics

Code Size Reduction:

  • Backend handlers: Target 50% reduction (3,500 lines → 1,750 lines)
  • Frontend components: Target 40% reduction (2,285 lines → 1,371 lines)
  • API clients: Target 60% reduction (2,000 lines → 800 lines)
  • Total target reduction: ~3,000 lines

Duplication Elimination:

  • createResponse duplication: 10 instances → 1 shared utility
  • getUserIdFromEvent duplication: 4 instances → 1 shared utility
  • apiRequest duplication: 12 instances → 1 centralized client
  • Total duplication eliminated: ~1,500 lines

Test Coverage:

  • Maintain or improve from current levels (target: 80%+)
  • Service layer test coverage: Target 90%+
  • Utilities test coverage: Target 95%+

Qualitative Metrics

Maintainability:

  • Easier to locate code by responsibility
  • Clearer separation of concerns
  • More predictable code organization

Testability:

  • Services can be tested in isolation
  • Hooks can be tested independently
  • Components have fewer dependencies

AI Agent Comprehension:

  • Smaller files are easier to analyze
  • Clear boundaries reduce confusion
  • Consistent patterns improve code generation

Next Steps for AI Agents

Immediate Actions (This Week)

  1. Start with P0 Tasks: Extract Lambda response and auth utilities
  2. Create centralized API client: Biggest quick win for frontend
  3. Set up service layer foundation: Create directory and base service class

Short-term Goals (Next 2 Weeks)

  1. Complete P0 tasks: Eliminate all critical code duplication
  2. Begin P1 tasks: Start with CourseGenerationForm refactoring
  3. Establish patterns: Create examples for other agents to follow

Medium-term Goals (Next 4-6 Weeks)

  1. Complete P1 tasks: Refactor all large components
  2. Begin P2 tasks: Split authentication client, refactor AI handlers
  3. Documentation: Update all docs to reflect new architecture

Long-term Goals (Next 2-3 Months)

  1. Complete P2 tasks: All architectural improvements done
  2. Continuous improvement: Apply guidelines to new code
  3. Monitor metrics: Track code size, duplication, test coverage

Conclusion

This refactoring guide provides a comprehensive roadmap for transforming the Momentum LMS codebase into a highly modular, maintainable, and AI-agent-friendly architecture. By systematically addressing code duplication, SOLID principle violations, and architectural debt, we can reduce the codebase by approximately 3,000 lines while significantly improving quality and comprehension.

The prioritized task list ensures that high-impact changes (eliminating duplication) are addressed first, followed by component-level improvements, and finally architectural polish. Each phase builds on the previous, creating a sustainable path to a cleaner codebase.

AI agents working on this codebase should follow the guidelines, checklists, and patterns outlined here to ensure consistent, safe, and effective refactoring. The goal is not perfection, but continuous, measurable improvement that makes the codebase easier to understand, test, and extend.

Remember: Refactoring is not about rewriting everything—it’s about incremental, safe transformations that leave the code better than you found it.


Document Version: 1.0
Last Updated: 2025-12-06
Maintained By: Development Team
Review Cycle: Monthly


Back to top

Momentum LMS © 2025. Distributed under the MIT license.