Files
picpeak/docs/REFACTORING_PLAN.md
T
Paul Nothaft 77a4bfd499 feat: implement 4 new features with bug fixes and refactoring plan
## Features Implemented

### 1. Event Rename Functionality
- Add EventRenameDialog component with live slug preview
- Create eventRenameService for safe event renaming
- Add slug_redirects table for old URL redirects
- Support optional email notification on rename
- Fix date formatting in slug (YYYY-MM-DD format)

### 2. Optional Event Contact Fields
- Add settings to make customer name/email/admin email optional
- Create migration for field requirement settings
- Update CreateEventPage forms to show "(optional)" labels
- Fix boolean parsing in publicSettings.js

### 3. Photo Filtering & Export
- Add PhotoFilterPanel with rating/likes/favorites/comments filters
- Create PhotoExportMenu with ZIP/metadata/XMP export options
- Add photoExportService with Lightroom XMP sidecar generation
- Create photoFilterBuilder utility for query construction
- Wire up photo selection to export button via onSelectionChange

### 4. Custom CSS Gallery Templates
- Add CssTemplateEditor component with 3 template slots
- Create cssSanitizer utility blocking XSS vectors
- Add gallery CSS endpoint for template delivery
- Integrate Custom CSS tab into Settings page
- Include default "Elegant Dark" template

## Bug Fixes
- Fix event rename date formatting (was showing full Date string)
- Fix common.optional translation key missing in locales
- Fix photo export button staying disabled when photos selected
- Fix authService import missing in SettingsPage

## Documentation
- Add comprehensive REFACTORING_PLAN.md for codebase improvement
- Add test specification documents for all features
- Add feature documentation for CSS templates

## Database Migrations
- 049_add_slug_redirects.js
- 050_add_optional_event_fields_settings.js
- 051_add_photo_filter_indexes.js
- 052_add_css_templates.js
2026-01-02 09:56:19 +01:00

60 KiB

PicPeak Comprehensive Refactoring Plan

Version: 1.0 Created: January 2026 Status: Approved for Implementation Architecture Goal: Clean Architecture with Domain-Driven Design principles


Table of Contents

  1. Executive Summary
  2. Current Architecture Analysis
  3. Target Architecture Vision
  4. Phase 1: Code Duplication Elimination
  5. Phase 2: Backend Route & Service Refactoring
  6. Phase 3: Frontend Page Decomposition
  7. Phase 4: Cross-Cutting Concerns
  8. Testing Strategy
  9. Migration & Rollback Plan
  10. Implementation Checklist

Executive Summary

Scope

This refactoring addresses 20+ files totaling over 15,000 lines of code that have grown beyond maintainable sizes or contain duplicated logic. The goal is to achieve world-class architecture following Clean Architecture and SOLID principles.

Key Outcomes

  • 50%+ reduction in average file size for targeted files
  • Zero code duplication for utility functions
  • Clear separation of concerns between routes, services, and domain logic
  • Improved testability with dependency injection and smaller units
  • Better developer experience with intuitive file organization

Risk Mitigation

  • All changes are backwards-compatible
  • Each phase can be deployed independently
  • Comprehensive test coverage required before each merge
  • Feature flags for gradual rollout where applicable

Current Architecture Analysis

Backend Issues

┌─────────────────────────────────────────────────────────────┐
│                    CURRENT STATE                            │
├─────────────────────────────────────────────────────────────┤
│  Routes (1000+ lines each)                                  │
│  ┌─────────────────┐  ┌─────────────────┐                  │
│  │ adminEvents.js  │  │ adminSettings.js│                  │
│  │   1,088 lines   │  │   1,056 lines   │                  │
│  │ - HTTP handlers │  │ - HTTP handlers │                  │
│  │ - Business logic│  │ - Business logic│                  │
│  │ - DB queries    │  │ - File uploads  │                  │
│  │ - Validation    │  │ - Validation    │                  │
│  │ - Helpers       │  │ - Multer config │                  │
│  └─────────────────┘  └─────────────────┘                  │
│                                                             │
│  Services (1000+ lines each)                                │
│  ┌─────────────────┐  ┌─────────────────┐                  │
│  │ backupService   │  │ restoreService  │                  │
│  │   1,120 lines   │  │   1,220 lines   │                  │
│  │ - Scheduling    │  │ - Validation    │                  │
│  │ - Execution     │  │ - Execution     │                  │
│  │ - S3 upload     │  │ - Rollback      │                  │
│  │ - Notifications │  │ - Logging       │                  │
│  └─────────────────┘  └─────────────────┘                  │
│                                                             │
│  PROBLEMS:                                                  │
│  ✗ Business logic in route handlers                         │
│  ✗ No service layer abstraction for events/photos          │
│  ✗ Duplicate utility functions across files                │
│  ✗ God objects with too many responsibilities              │
└─────────────────────────────────────────────────────────────┘

Frontend Issues

┌─────────────────────────────────────────────────────────────┐
│                    CURRENT STATE                            │
├─────────────────────────────────────────────────────────────┤
│  Pages (1000+ lines each)                                   │
│  ┌─────────────────┐  ┌─────────────────┐                  │
│  │ SettingsPage    │  │EventDetailsPage │                  │
│  │   1,839 lines   │  │   1,479 lines   │                  │
│  │ - 27 hooks      │  │ - 33 hooks      │                  │
│  │ - 8 tab sections│  │ - Photo grid    │                  │
│  │ - All mutations │  │ - Event editing │                  │
│  │ - All queries   │  │ - Theme config  │                  │
│  └─────────────────┘  └─────────────────┘                  │
│                                                             │
│  Duplicate Files                                            │
│  ┌─────────────────┐  ┌─────────────────┐                  │
│  │CreateEventPage  │  │ CMSPage.tsx     │                  │
│  │CreateEventPage  │  │ CMSPageEnhanced │                  │
│  │   Enhanced      │  │                 │                  │
│  └─────────────────┘  └─────────────────┘                  │
│                                                             │
│  PROBLEMS:                                                  │
│  ✗ Mega-components with 20+ hooks                          │
│  ✗ Multiple duplicate page implementations                 │
│  ✗ No custom hooks for shared logic                        │
│  ✗ Mixed concerns in single components                     │
└─────────────────────────────────────────────────────────────┘

Code Duplication Map

Function/Pattern Locations Lines Duplicated
parseBooleanInput 3 files ~60 lines
Auth routes 3 versions ~900 lines
CreateEvent pages 2 versions ~1,400 lines
CMS pages 2 versions ~1,100 lines
Multer config 4+ files ~200 lines

Target Architecture Vision

Backend Target Architecture

┌─────────────────────────────────────────────────────────────┐
│                    TARGET STATE                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    ROUTES LAYER                      │   │
│  │  (Thin controllers - HTTP handling only)             │   │
│  │  ~100-200 lines per file                             │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   SERVICES LAYER                     │   │
│  │  (Business logic orchestration)                      │   │
│  │  ~200-400 lines per file                             │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │EventService │ │PhotoService │ │SettingsServ │    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                  DOMAIN LAYER                        │   │
│  │  (Pure business logic, no I/O)                       │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │ Validators  │ │ Transformers│ │  Factories  │    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │               INFRASTRUCTURE LAYER                   │   │
│  │  (Database, File System, External APIs)              │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │Repositories │ │ FileStorage │ │ S3Adapter   │    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                  SHARED UTILITIES                    │   │
│  │  (Pure functions, zero side effects)                 │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │ parsers.js  │ │validators.js│ │formatters.js│    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Frontend Target Architecture

┌─────────────────────────────────────────────────────────────┐
│                    TARGET STATE                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                     PAGES                            │   │
│  │  (Route components - composition only)               │   │
│  │  ~100-200 lines per file                             │   │
│  │  ┌─────────────────────────────────────────────┐    │   │
│  │  │ SettingsPage.tsx                             │    │   │
│  │  │   <SettingsLayout>                           │    │   │
│  │  │     <GeneralSettingsTab />                   │    │   │
│  │  │     <EventSettingsTab />                     │    │   │
│  │  │     <SecuritySettingsTab />                  │    │   │
│  │  │     ...                                      │    │   │
│  │  │   </SettingsLayout>                          │    │   │
│  │  └─────────────────────────────────────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   FEATURES                           │   │
│  │  (Feature-specific components with hooks)            │   │
│  │  ~200-400 lines per file                             │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │ settings/   │ │  events/    │ │  photos/    │    │   │
│  │  │ ├─ tabs/    │ │ ├─ editor/  │ │ ├─ grid/    │    │   │
│  │  │ ├─ hooks/   │ │ ├─ viewer/  │ │ ├─ viewer/  │    │   │
│  │  │ └─ index.ts │ │ └─ hooks/   │ │ └─ export/  │    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                 SHARED COMPONENTS                    │   │
│  │  (Reusable UI primitives)                            │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │   common/   │ │   forms/    │ │   layout/   │    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   SHARED HOOKS                       │   │
│  │  (Reusable stateful logic)                           │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │   │
│  │  │useSettings  │ │ usePhotos   │ │ useEvents   │    │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Phase 1: Code Duplication Elimination

Priority: CRITICAL Estimated Effort: 2-3 days Risk: Low Dependencies: None

1.1 Extract Shared Parsers Utility

Problem: parseBooleanInput function duplicated in 3 files

Files Affected:

  • backend/src/routes/adminEvents.js (lines 61-81)
  • backend/src/routes/events.js (lines 14-34)
  • frontend/src/pages/admin/SettingsPage.tsx (lines 33-52, named toBoolean)

Solution: Create unified parser utilities

Backend Implementation

Create: backend/src/utils/parsers.js

/**
 * Shared Parser Utilities
 * Pure functions for parsing and transforming input values
 */

/**
 * Parse any input value to boolean with configurable default
 * @param {*} value - Input value to parse
 * @param {boolean} defaultValue - Default if value is undefined/null
 * @returns {boolean}
 */
const parseBooleanInput = (value, defaultValue = true) => {
  if (value === undefined || value === null) {
    return defaultValue;
  }
  if (typeof value === 'boolean') {
    return value;
  }
  if (typeof value === 'number') {
    if (Number.isNaN(value)) return defaultValue;
    return value !== 0;
  }
  if (typeof value === 'string') {
    const normalized = value.trim().toLowerCase();
    if (['false', '0', 'no', 'off', ''].includes(normalized)) {
      return false;
    }
    if (['true', '1', 'yes', 'on'].includes(normalized)) {
      return true;
    }
  }
  return defaultValue;
};

/**
 * Parse numeric input with validation
 * @param {*} value - Input value to parse
 * @param {number} defaultValue - Default if invalid
 * @param {Object} options - Min/max bounds
 * @returns {number}
 */
const parseNumberInput = (value, defaultValue, { min, max } = {}) => {
  if (value === undefined || value === null || value === '') {
    return defaultValue;
  }
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) {
    return defaultValue;
  }
  if (min !== undefined && parsed < min) return min;
  if (max !== undefined && parsed > max) return max;
  return parsed;
};

/**
 * Parse string input with trimming and null handling
 * @param {*} value - Input value
 * @param {string|null} defaultValue - Default if empty
 * @returns {string|null}
 */
const parseStringInput = (value, defaultValue = null) => {
  if (value === undefined || value === null) {
    return defaultValue;
  }
  if (typeof value === 'string') {
    const trimmed = value.trim();
    return trimmed || defaultValue;
  }
  return String(value);
};

/**
 * Parse JSON string safely
 * @param {*} value - JSON string or already parsed value
 * @param {*} defaultValue - Default if parsing fails
 * @returns {*}
 */
const parseJsonInput = (value, defaultValue = null) => {
  if (value === undefined || value === null) {
    return defaultValue;
  }
  if (typeof value !== 'string') {
    return value; // Already parsed
  }
  try {
    return JSON.parse(value);
  } catch {
    return defaultValue;
  }
};

module.exports = {
  parseBooleanInput,
  parseNumberInput,
  parseStringInput,
  parseJsonInput
};

Frontend Implementation

Create: frontend/src/utils/parsers.ts

/**
 * Shared Parser Utilities for Frontend
 */

/**
 * Parse any input value to boolean with configurable default
 */
export const toBoolean = (value: unknown, defaultValue = false): boolean => {
  if (value === undefined || value === null) {
    return defaultValue;
  }
  if (typeof value === 'boolean') {
    return value;
  }
  if (typeof value === 'number') {
    if (Number.isNaN(value)) return defaultValue;
    return value !== 0;
  }
  if (typeof value === 'string') {
    const normalized = value.toLowerCase().trim();
    if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
    if (['false', '0', 'no', 'off', ''].includes(normalized)) return false;
  }
  return defaultValue;
};

/**
 * Parse numeric input with validation
 */
export const toNumber = (
  value: unknown,
  defaultValue: number,
  options?: { min?: number; max?: number }
): number => {
  if (value === undefined || value === null || value === '') {
    return defaultValue;
  }
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) {
    return defaultValue;
  }
  if (options?.min !== undefined && parsed < options.min) return options.min;
  if (options?.max !== undefined && parsed > options.max) return options.max;
  return parsed;
};

Migration Steps

  1. Create new utility files
  2. Add comprehensive unit tests for all parser functions
  3. Update imports in affected files one at a time:
    • adminEvents.js: Replace inline function with import
    • events.js: Replace inline function with import
    • SettingsPage.tsx: Replace inline function with import
  4. Remove duplicate function definitions
  5. Run full test suite
  6. Deploy

1.2 Consolidate Auth Routes

Problem: Three versions of auth routes exist

Files:

  • backend/src/routes/auth.js (136 lines) - Basic version
  • backend/src/routes/auth-enhanced.js (395 lines) - Enhanced v1
  • backend/src/routes/auth-enhanced-v2.js (393 lines) - Enhanced v2

Analysis Required:

# Determine which version is actively used
grep -r "auth-enhanced" backend/src/ --include="*.js"
grep -r "auth.js" backend/src/ --include="*.js"

Solution: Consolidate into single auth.js

Implementation Plan

  1. Audit Current Usage

    • Identify which auth file is mounted in server.js
    • Document all endpoints from each version
    • Create feature comparison matrix
  2. Merge Strategy

    auth.js (NEW - Consolidated)
    ├── All endpoints from auth-enhanced-v2.js (active version)
    ├── Any unique endpoints from other versions
    └── Deprecated endpoints marked for removal
    
  3. Create Feature Flags (if needed)

    // config/features.js
    module.exports = {
      AUTH_USE_ENHANCED_TOKENS: true,
      AUTH_REQUIRE_EMAIL_VERIFICATION: false,
    };
    
  4. Migration Steps

    • Create new consolidated auth.js
    • Update server.js to use new auth routes
    • Keep old files temporarily with deprecation notices
    • Monitor for errors in production
    • Remove deprecated files after 2 weeks

1.3 Consolidate CreateEvent Pages

Problem: Two nearly identical event creation pages

Files:

  • frontend/src/pages/admin/CreateEventPage.tsx (709 lines)
  • frontend/src/pages/admin/CreateEventPageEnhanced.tsx (709 lines)

Solution: Single CreateEventPage.tsx with all features

Difference Analysis

Feature Standard Enhanced
Basic form fields
Theme customizer
Password generator
Advanced validation ?
Auto-save draft ?

Implementation Plan

  1. Diff the files to identify exact differences:

    diff -u CreateEventPage.tsx CreateEventPageEnhanced.tsx > event_pages_diff.txt
    
  2. Merge enhanced features into standard page

  3. Extract shared logic into custom hooks:

    // hooks/useEventForm.ts
    export function useEventForm(initialData?: Partial<Event>) {
      // Form state management
      // Validation logic
      // Submit handlers
    }
    
    // hooks/useEventDraft.ts
    export function useEventDraft(eventData: Partial<Event>) {
      // Auto-save logic
      // Draft recovery
    }
    
  4. Update router to use single page

  5. Delete duplicate file


1.4 Consolidate CMS Pages

Problem: Two CMS page implementations

Files:

  • frontend/src/pages/admin/CMSPage.tsx (538 lines)
  • frontend/src/pages/admin/CMSPageEnhanced.tsx (618 lines)

Enhanced Features:

  • Auto-save functionality
  • Unsaved changes warning
  • Enhanced editor toolbar

Solution: Single CMSPage.tsx with optional features

Implementation Plan

  1. Create feature-complete CMSPage.tsx:

    interface CMSPageProps {
      enableAutoSave?: boolean;
      enableUnsavedWarning?: boolean;
    }
    
    export const CMSPage: React.FC<CMSPageProps> = ({
      enableAutoSave = true,
      enableUnsavedWarning = true
    }) => {
      // Merged implementation
    };
    
  2. Extract reusable hooks:

    // hooks/useAutoSave.ts
    export function useAutoSave<T>(
      data: T,
      saveFn: (data: T) => Promise<void>,
      debounceMs = 2000
    ) { ... }
    
    // hooks/useUnsavedChanges.ts
    export function useUnsavedChanges(hasChanges: boolean) { ... }
    
  3. Delete duplicate file


1.5 Extract Multer Configuration Factory

Problem: Multer storage configuration duplicated across multiple route files

Files with Multer configs:

  • adminPhotos.js
  • adminSettings.js
  • adminEvents.js
  • protectedImages.js

Solution: Centralized multer configuration factory

Implementation

Create: backend/src/config/multerConfig.js

const multer = require('multer');
const path = require('path');
const crypto = require('crypto');
const fs = require('fs').promises;

/**
 * Create disk storage configuration
 * @param {Object} options
 * @param {string} options.destination - Upload directory path
 * @param {Function} options.filenameGenerator - Custom filename generator
 */
const createDiskStorage = ({ destination, filenameGenerator }) => {
  return multer.diskStorage({
    destination: async (req, file, cb) => {
      try {
        await fs.mkdir(destination, { recursive: true });
        cb(null, destination);
      } catch (error) {
        cb(error);
      }
    },
    filename: filenameGenerator || ((req, file, cb) => {
      const uniqueSuffix = `${Date.now()}-${crypto.randomBytes(6).toString('hex')}`;
      const ext = path.extname(file.originalname).toLowerCase();
      cb(null, `${uniqueSuffix}${ext}`);
    })
  });
};

/**
 * Create multer upload instance with standard limits
 * @param {Object} options
 * @param {multer.StorageEngine} options.storage - Multer storage engine
 * @param {number} options.maxFileSize - Max file size in bytes (default 50MB)
 * @param {string[]} options.allowedMimeTypes - Allowed MIME types
 * @param {number} options.maxFiles - Max files per request (default 100)
 */
const createUpload = ({
  storage,
  maxFileSize = 50 * 1024 * 1024,
  allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
  maxFiles = 100
}) => {
  return multer({
    storage,
    limits: {
      fileSize: maxFileSize,
      files: maxFiles
    },
    fileFilter: (req, file, cb) => {
      if (allowedMimeTypes.includes(file.mimetype)) {
        cb(null, true);
      } else {
        cb(new Error(`File type ${file.mimetype} not allowed`));
      }
    }
  });
};

/**
 * Pre-configured uploads for common use cases
 */
const uploads = {
  photos: (storagePath) => createUpload({
    storage: createDiskStorage({ destination: storagePath }),
    allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'video/mp4', 'video/webm', 'video/quicktime'],
    maxFiles: 500
  }),

  logos: (storagePath) => createUpload({
    storage: createDiskStorage({ destination: storagePath }),
    allowedMimeTypes: ['image/jpeg', 'image/png', 'image/svg+xml', 'image/webp'],
    maxFileSize: 5 * 1024 * 1024,
    maxFiles: 1
  }),

  general: (storagePath) => createUpload({
    storage: createDiskStorage({ destination: storagePath })
  })
};

module.exports = {
  createDiskStorage,
  createUpload,
  uploads
};

Phase 2: Backend Route & Service Refactoring

Priority: HIGH Estimated Effort: 5-7 days Risk: Medium Dependencies: Phase 1 complete

2.1 Create Event Service Layer

Current: adminEvents.js (1,088 lines) with business logic in route handlers

Target: Thin routes + dedicated EventService

New File Structure

backend/src/
├── routes/
│   └── adminEvents.js          # ~200 lines (HTTP only)
├── services/
│   └── events/
│       ├── index.js            # Public exports
│       ├── eventService.js     # Main service (~300 lines)
│       ├── eventValidator.js   # Validation logic (~100 lines)
│       ├── eventMapper.js      # Data transformation (~80 lines)
│       └── eventRepository.js  # Database queries (~150 lines)
└── utils/
    └── parsers.js              # Shared parsers

EventService Implementation

Create: backend/src/services/events/eventService.js

const { db, logActivity } = require('../../database/db');
const eventRepository = require('./eventRepository');
const eventValidator = require('./eventValidator');
const eventMapper = require('./eventMapper');
const { queueEmail } = require('../emailProcessor');
const { buildShareLinkVariants } = require('../shareLinkService');
const logger = require('../../utils/logger');

class EventService {
  /**
   * Create a new event
   * @param {Object} eventData - Event creation data
   * @param {Object} admin - Admin user creating the event
   * @returns {Promise<Object>} Created event
   */
  async createEvent(eventData, admin) {
    // Validate input
    const validation = await eventValidator.validateCreate(eventData);
    if (!validation.valid) {
      throw new ValidationError(validation.errors);
    }

    // Transform data
    const eventRecord = eventMapper.toDatabase(eventData);

    // Generate slug and share link
    const slug = this.generateSlug(eventData);
    const shareToken = crypto.randomBytes(16).toString('hex');
    const { shareLinkToStore } = await buildShareLinkVariants({ slug, shareToken });

    // Create in database
    const [event] = await eventRepository.create({
      ...eventRecord,
      slug,
      share_token: shareToken,
      share_link: shareLinkToStore,
      created_by: admin.id
    });

    // Create storage folder
    await this.createEventFolder(slug);

    // Queue welcome email
    if (eventData.customer_email) {
      await queueEmail(event.id, eventData.customer_email, 'event_created', {
        event_name: event.event_name,
        gallery_link: shareLinkToStore
      });
    }

    // Log activity
    await logActivity('event_created', 'admin', admin.id, admin.username, {
      event_id: event.id,
      event_name: event.event_name
    });

    return eventMapper.toApi(event);
  }

  /**
   * Update an existing event
   */
  async updateEvent(eventId, updateData, admin) {
    const event = await eventRepository.findById(eventId);
    if (!event) {
      throw new NotFoundError('Event not found');
    }

    const validation = await eventValidator.validateUpdate(updateData, event);
    if (!validation.valid) {
      throw new ValidationError(validation.errors);
    }

    const updatedEvent = await eventRepository.update(eventId,
      eventMapper.toDatabase(updateData)
    );

    await logActivity('event_updated', 'admin', admin.id, admin.username, {
      event_id: eventId,
      changes: updateData
    });

    return eventMapper.toApi(updatedEvent);
  }

  /**
   * Archive an event
   */
  async archiveEvent(eventId, admin) {
    // Implementation
  }

  /**
   * Delete an event
   */
  async deleteEvent(eventId, admin) {
    // Implementation
  }

  /**
   * List events with filtering
   */
  async listEvents(filters, pagination) {
    const events = await eventRepository.findMany(filters, pagination);
    return events.map(eventMapper.toApi);
  }
}

module.exports = new EventService();

Refactored Route Handler

Update: backend/src/routes/adminEvents.js

const express = require('express');
const { body, query, validationResult } = require('express-validator');
const { adminAuth } = require('../middleware/auth-enhanced-v2');
const eventService = require('../services/events');
const { handleAsync } = require('../utils/routeHelpers');

const router = express.Router();

// Apply admin auth to all routes
router.use(adminAuth);

/**
 * GET /api/admin/events
 * List all events with optional filtering
 */
router.get('/', [
  query('status').optional().isIn(['active', 'archived', 'expiring']),
  query('search').optional().isString(),
  query('page').optional().isInt({ min: 1 }),
  query('limit').optional().isInt({ min: 1, max: 100 })
], handleAsync(async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const events = await eventService.listEvents(req.query, {
    page: req.query.page || 1,
    limit: req.query.limit || 20
  });

  res.json(events);
}));

/**
 * POST /api/admin/events
 * Create a new event
 */
router.post('/', [
  body('event_name').isString().trim().isLength({ min: 3, max: 200 }),
  body('event_type').isIn(['wedding', 'birthday', 'corporate', 'other']),
  body('event_date').isISO8601(),
  body('customer_email').optional().isEmail(),
  body('password').optional().isLength({ min: 6 })
], handleAsync(async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const event = await eventService.createEvent(req.body, req.admin);
  res.status(201).json(event);
}));

/**
 * PUT /api/admin/events/:id
 * Update an event
 */
router.put('/:id', handleAsync(async (req, res) => {
  const event = await eventService.updateEvent(
    parseInt(req.params.id),
    req.body,
    req.admin
  );
  res.json(event);
}));

/**
 * DELETE /api/admin/events/:id
 * Delete an event
 */
router.delete('/:id', handleAsync(async (req, res) => {
  await eventService.deleteEvent(parseInt(req.params.id), req.admin);
  res.status(204).send();
}));

/**
 * POST /api/admin/events/:id/archive
 * Archive an event
 */
router.post('/:id/archive', handleAsync(async (req, res) => {
  const result = await eventService.archiveEvent(
    parseInt(req.params.id),
    req.admin
  );
  res.json(result);
}));

module.exports = router;

2.2 Create Photo Service Layer

Current: adminPhotos.js (1,005 lines)

Target: Similar structure to EventService

New File Structure

backend/src/services/photos/
├── index.js
├── photoService.js        # Main service
├── photoProcessor.js      # Image/video processing
├── photoRepository.js     # Database queries
├── photoStorage.js        # File system operations
└── photoValidator.js      # Validation

2.3 Create Settings Service Layer

Current: adminSettings.js (1,056 lines)

Target: Domain-specific services

New File Structure

backend/src/services/settings/
├── index.js
├── settingsService.js     # Main orchestrator
├── brandingService.js     # Logo, favicon, company info
├── themeService.js        # Theme configuration
├── securityService.js     # Security settings
└── settingsRepository.js  # Database queries

2.4 Decompose Backup/Restore Services

Current:

  • backupService.js (1,120 lines)
  • restoreService.js (1,220 lines)

Target: Smaller, focused classes

New File Structure

backend/src/services/backup/
├── index.js
├── BackupOrchestrator.js    # Main coordinator (~200 lines)
├── BackupScheduler.js       # Cron job management (~150 lines)
├── BackupExecutor.js        # Backup creation (~300 lines)
├── BackupUploader.js        # S3/remote upload (~200 lines)
├── BackupValidator.js       # Validation logic (~100 lines)
└── BackupNotifier.js        # Email notifications (~100 lines)

backend/src/services/restore/
├── index.js
├── RestoreOrchestrator.js   # Main coordinator (~200 lines)
├── RestoreValidator.js      # Pre-restore validation (~200 lines)
├── RestoreExecutor.js       # Actual restore logic (~300 lines)
├── RestoreRollback.js       # Rollback on failure (~150 lines)
└── RestoreLogger.js         # Detailed logging (~100 lines)

Phase 3: Frontend Page Decomposition

Priority: HIGH Estimated Effort: 5-7 days Risk: Medium Dependencies: Phase 1 complete

3.1 Decompose SettingsPage

Current: SettingsPage.tsx (1,839 lines) with 8 tabs and 27 hooks

Target: Composition-based page with tab components

New File Structure

frontend/src/features/settings/
├── index.ts                           # Public exports
├── SettingsPage.tsx                   # Main page (~100 lines)
├── SettingsLayout.tsx                 # Tab layout component
├── tabs/
│   ├── index.ts
│   ├── GeneralSettingsTab.tsx         # ~200 lines
│   ├── EventSettingsTab.tsx           # ~150 lines
│   ├── StatusTab.tsx                  # ~200 lines
│   ├── SecuritySettingsTab.tsx        # ~200 lines
│   ├── CategoriesTab.tsx              # ~100 lines
│   ├── AnalyticsSettingsTab.tsx       # ~150 lines
│   ├── ModerationTab.tsx              # ~100 lines
│   └── StylingTab.tsx                 # ~100 lines
├── hooks/
│   ├── index.ts
│   ├── useGeneralSettings.ts          # Settings query/mutation
│   ├── useSecuritySettings.ts
│   ├── useAnalyticsSettings.ts
│   ├── useSystemStatus.ts
│   └── useAdminProfile.ts
└── components/
    ├── AccountForm.tsx
    ├── FeatureToggles.tsx
    ├── StorageIndicator.tsx
    └── SystemStatusCard.tsx

Refactored SettingsPage

// features/settings/SettingsPage.tsx
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { SettingsLayout } from './SettingsLayout';
import {
  GeneralSettingsTab,
  EventSettingsTab,
  StatusTab,
  SecuritySettingsTab,
  CategoriesTab,
  AnalyticsSettingsTab,
  ModerationTab,
  StylingTab
} from './tabs';

type SettingsTab =
  | 'general'
  | 'events'
  | 'status'
  | 'security'
  | 'categories'
  | 'analytics'
  | 'moderation'
  | 'styling';

export const SettingsPage: React.FC = () => {
  const { t } = useTranslation();
  const [activeTab, setActiveTab] = useState<SettingsTab>('general');

  const tabs = [
    { id: 'general', label: t('settings.general.title') },
    { id: 'events', label: t('settings.events.title') },
    { id: 'status', label: t('settings.status.title') },
    { id: 'security', label: t('settings.security.title') },
    { id: 'categories', label: t('settings.categories.title') },
    { id: 'analytics', label: t('settings.analytics.title') },
    { id: 'moderation', label: t('settings.moderation.title') },
    { id: 'styling', label: t('settings.styling.title') },
  ] as const;

  const renderTabContent = () => {
    switch (activeTab) {
      case 'general':
        return <GeneralSettingsTab />;
      case 'events':
        return <EventSettingsTab />;
      case 'status':
        return <StatusTab />;
      case 'security':
        return <SecuritySettingsTab />;
      case 'categories':
        return <CategoriesTab />;
      case 'analytics':
        return <AnalyticsSettingsTab />;
      case 'moderation':
        return <ModerationTab />;
      case 'styling':
        return <StylingTab />;
    }
  };

  return (
    <SettingsLayout
      title={t('settings.title')}
      description={t('settings.description')}
      tabs={tabs}
      activeTab={activeTab}
      onTabChange={setActiveTab}
    >
      {renderTabContent()}
    </SettingsLayout>
  );
};

Example Tab Component

// features/settings/tabs/GeneralSettingsTab.tsx
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Card } from '../../../components/common';
import {
  useGeneralSettings,
  useAdminProfile,
  useSaveGeneralSettings
} from '../hooks';
import { AccountForm } from '../components/AccountForm';
import { FeatureToggles } from '../components/FeatureToggles';
import { LanguageSelector } from '../components/LanguageSelector';
import { DateFormatSelector } from '../components/DateFormatSelector';

export const GeneralSettingsTab: React.FC = () => {
  const { t } = useTranslation();
  const { data: settings, isLoading } = useGeneralSettings();
  const { data: profile } = useAdminProfile();
  const saveMutation = useSaveGeneralSettings();

  if (isLoading) {
    return <Loading />;
  }

  return (
    <div className="space-y-6">
      <Card padding="md">
        <h2 className="text-lg font-semibold mb-4">
          {t('settings.general.accountSection')}
        </h2>
        <AccountForm
          initialData={profile}
          onSave={(data) => saveMutation.mutate(data)}
          isSaving={saveMutation.isPending}
        />
      </Card>

      <Card padding="md">
        <h2 className="text-lg font-semibold mb-4">
          {t('settings.general.features')}
        </h2>
        <FeatureToggles settings={settings} />
      </Card>

      <Card padding="md">
        <h2 className="text-lg font-semibold mb-4">
          {t('settings.general.localization')}
        </h2>
        <LanguageSelector />
        <DateFormatSelector />
      </Card>
    </div>
  );
};

3.2 Decompose EventDetailsPage

Current: EventDetailsPage.tsx (1,479 lines) with 33 hooks

Target: Section-based composition

New File Structure

frontend/src/features/events/
├── index.ts
├── EventDetailsPage.tsx              # Main page (~150 lines)
├── sections/
│   ├── index.ts
│   ├── EventInfoSection.tsx          # ~200 lines
│   ├── PhotoManagementSection.tsx    # ~250 lines
│   ├── ThemeSection.tsx              # ~150 lines
│   ├── FeedbackSection.tsx           # ~150 lines
│   ├── ShareLinkSection.tsx          # ~150 lines
│   └── ActionsSection.tsx            # ~100 lines
├── hooks/
│   ├── index.ts
│   ├── useEventDetails.ts
│   ├── useEventPhotos.ts
│   ├── useEventTheme.ts
│   ├── useEventFeedback.ts
│   └── useEventActions.ts
└── components/
    ├── EventHeader.tsx
    ├── EventStats.tsx
    ├── PhotoGrid.tsx
    └── PhotoViewer.tsx

3.3 Decompose Large Components

ThemeCustomizerEnhanced (607 lines)

Split into:

frontend/src/components/admin/theme/
├── index.ts
├── ThemeCustomizer.tsx        # Main component (~150 lines)
├── ColorEditor.tsx            # Color pickers (~150 lines)
├── TypographyEditor.tsx       # Font selection (~100 lines)
├── LayoutSelector.tsx         # Gallery layout (~100 lines)
├── PresetSelector.tsx         # Theme presets (~80 lines)
└── ThemePreview.tsx           # Live preview (~100 lines)

CMSEditor (572 lines)

Split into:

frontend/src/components/admin/cms/
├── index.ts
├── CMSEditor.tsx              # Main editor (~150 lines)
├── EditorToolbar.tsx          # Formatting toolbar (~150 lines)
├── EditorPreview.tsx          # Preview pane (~80 lines)
├── ViewModeSelector.tsx       # Edit/Preview/Split (~60 lines)
├── EditorStats.tsx            # Word/char count (~50 lines)
└── EditorHelp.tsx             # Help modal (~80 lines)

AdminPhotoViewer (515 lines)

Split into:

frontend/src/components/admin/photos/
├── index.ts
├── PhotoViewer.tsx            # Main viewer (~150 lines)
├── PhotoNavigation.tsx        # Prev/next controls (~80 lines)
├── PhotoMetadata.tsx          # File info display (~100 lines)
├── PhotoFeedback.tsx          # Ratings/comments (~100 lines)
├── PhotoActions.tsx           # Download/delete (~80 lines)
└── hooks/
    ├── usePhotoNavigation.ts
    └── usePhotoFeedback.ts

Phase 4: Cross-Cutting Concerns

Priority: MEDIUM Estimated Effort: 2-3 days Dependencies: Phases 1-3 complete

4.1 Create Route Helpers Utility

Create: backend/src/utils/routeHelpers.js

/**
 * Async route handler wrapper
 * Catches errors and passes to error middleware
 */
const handleAsync = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

/**
 * Validate request and return early if invalid
 */
const validateRequest = (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    res.status(400).json({ errors: errors.array() });
    return false;
  }
  return true;
};

/**
 * Standard success response
 */
const successResponse = (res, data, statusCode = 200) => {
  res.status(statusCode).json({
    success: true,
    data
  });
};

/**
 * Standard error response
 */
const errorResponse = (res, message, statusCode = 500, details = null) => {
  res.status(statusCode).json({
    success: false,
    error: message,
    details
  });
};

module.exports = {
  handleAsync,
  validateRequest,
  successResponse,
  errorResponse
};

4.2 Create Custom Error Classes

Create: backend/src/utils/errors.js

class AppError extends Error {
  constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
    super(message);
    this.statusCode = statusCode;
    this.code = code;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

class ValidationError extends AppError {
  constructor(errors) {
    super('Validation failed', 400, 'VALIDATION_ERROR');
    this.errors = errors;
  }
}

class NotFoundError extends AppError {
  constructor(resource = 'Resource') {
    super(`${resource} not found`, 404, 'NOT_FOUND');
  }
}

class UnauthorizedError extends AppError {
  constructor(message = 'Unauthorized') {
    super(message, 401, 'UNAUTHORIZED');
  }
}

class ForbiddenError extends AppError {
  constructor(message = 'Forbidden') {
    super(message, 403, 'FORBIDDEN');
  }
}

class ConflictError extends AppError {
  constructor(message = 'Resource conflict') {
    super(message, 409, 'CONFLICT');
  }
}

module.exports = {
  AppError,
  ValidationError,
  NotFoundError,
  UnauthorizedError,
  ForbiddenError,
  ConflictError
};

4.3 Create Global Error Handler

Update: backend/src/middleware/errorHandler.js

const logger = require('../utils/logger');
const { AppError } = require('../utils/errors');

const errorHandler = (err, req, res, next) => {
  // Log error
  logger.error('Request error', {
    error: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
    body: req.body,
    user: req.admin?.id
  });

  // Operational errors (expected)
  if (err instanceof AppError && err.isOperational) {
    return res.status(err.statusCode).json({
      success: false,
      error: err.message,
      code: err.code,
      ...(err.errors && { errors: err.errors })
    });
  }

  // Multer errors
  if (err.code === 'LIMIT_FILE_SIZE') {
    return res.status(400).json({
      success: false,
      error: 'File too large',
      code: 'FILE_TOO_LARGE'
    });
  }

  // Database errors
  if (err.code === 'SQLITE_CONSTRAINT' || err.code === '23505') {
    return res.status(409).json({
      success: false,
      error: 'Resource already exists',
      code: 'DUPLICATE_ENTRY'
    });
  }

  // Unknown errors - don't leak details
  res.status(500).json({
    success: false,
    error: 'An unexpected error occurred',
    code: 'INTERNAL_ERROR'
  });
};

module.exports = errorHandler;

Testing Strategy

Unit Tests Required

Backend

File Test File Coverage Target
utils/parsers.js parsers.test.js 100%
services/events/eventService.js eventService.test.js 90%
services/events/eventValidator.js eventValidator.test.js 100%
services/photos/photoService.js photoService.test.js 90%
services/settings/settingsService.js settingsService.test.js 90%
services/backup/BackupOrchestrator.js BackupOrchestrator.test.js 85%
services/restore/RestoreOrchestrator.js RestoreOrchestrator.test.js 85%

Frontend

Component Test File Coverage Target
utils/parsers.ts parsers.test.ts 100%
features/settings/hooks/* *.test.ts 90%
features/settings/tabs/* *.test.tsx 80%
features/events/hooks/* *.test.ts 90%

Integration Tests Required

Scenario Test File
Event CRUD operations events.integration.test.js
Photo upload flow photos.integration.test.js
Settings management settings.integration.test.js
Backup/Restore cycle backup.integration.test.js

E2E Tests Required

Flow Test File
Create event end-to-end create-event.e2e.ts
Settings page navigation settings.e2e.ts
Photo management photos.e2e.ts

Migration & Rollback Plan

Pre-Migration Checklist

  • All existing tests pass
  • Database backup created
  • Feature flags configured
  • Rollback scripts prepared
  • Monitoring alerts configured

Phase Rollout Strategy

┌─────────────────────────────────────────────────────────────┐
│                    ROLLOUT TIMELINE                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Week 1: Phase 1 (Code Duplication)                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Day 1-2: Create shared utilities                    │   │
│  │ Day 3:   Update imports, run tests                  │   │
│  │ Day 4:   Deploy to staging                          │   │
│  │ Day 5:   Monitor, fix issues, deploy to production  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Week 2-3: Phase 2 (Backend Services)                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Day 1-3: EventService extraction                    │   │
│  │ Day 4-5: PhotoService extraction                    │   │
│  │ Day 6-7: SettingsService extraction                 │   │
│  │ Day 8-10: Backup/Restore decomposition              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Week 4-5: Phase 3 (Frontend Decomposition)                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Day 1-3: SettingsPage decomposition                 │   │
│  │ Day 4-5: EventDetailsPage decomposition             │   │
│  │ Day 6-7: Component decomposition                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Week 6: Phase 4 (Cross-Cutting) + Cleanup                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Day 1-2: Error handling, route helpers              │   │
│  │ Day 3-4: Delete deprecated files                    │   │
│  │ Day 5:   Final testing, documentation               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Rollback Procedures

Phase 1 Rollback

# Revert parser utility changes
git revert <commit-hash>
# No database changes required

Phase 2 Rollback

# Service layer changes are additive
# Routes can be reverted without data loss
git revert <commit-hash>
# Restart backend services
docker-compose restart backend

Phase 3 Rollback

# Frontend changes are purely presentational
git revert <commit-hash>
# Rebuild frontend
docker-compose build frontend
docker-compose restart frontend

Implementation Checklist

Phase 1: Code Duplication (Days 1-3)

  • 1.1 Shared Parsers

    • Create backend/src/utils/parsers.js
    • Create frontend/src/utils/parsers.ts
    • Write unit tests (100% coverage)
    • Update adminEvents.js imports
    • Update events.js imports
    • Update SettingsPage.tsx imports
    • Delete inline function definitions
    • Run full test suite
  • 1.2 Auth Routes Consolidation

    • Audit current auth route usage
    • Create consolidated auth.js
    • Update server.js mount point
    • Add deprecation notices to old files
    • Test auth flows
    • Schedule old file deletion
  • 1.3 CreateEvent Page Consolidation

    • Diff files to identify differences
    • Create hooks/useEventForm.ts
    • Merge enhanced features into standard
    • Update router
    • Delete duplicate file
  • 1.4 CMS Page Consolidation

    • Diff files to identify differences
    • Create hooks/useAutoSave.ts
    • Create hooks/useUnsavedChanges.ts
    • Merge into single CMSPage
    • Delete duplicate file
  • 1.5 Multer Config Factory

    • Create config/multerConfig.js
    • Update adminPhotos.js
    • Update adminSettings.js
    • Update other files using multer
    • Test all upload flows

Phase 2: Backend Services (Days 4-10)

  • 2.1 Event Service

    • Create services/events/ directory structure
    • Implement eventService.js
    • Implement eventRepository.js
    • Implement eventValidator.js
    • Implement eventMapper.js
    • Refactor adminEvents.js to use service
    • Write unit tests (90% coverage)
    • Write integration tests
  • 2.2 Photo Service

    • Create services/photos/ directory structure
    • Implement service layer
    • Refactor adminPhotos.js
    • Write tests
  • 2.3 Settings Service

    • Create services/settings/ directory structure
    • Implement service layer
    • Refactor adminSettings.js
    • Write tests
  • 2.4 Backup/Restore Decomposition

    • Create services/backup/ directory structure
    • Split backupService.js into components
    • Create services/restore/ directory structure
    • Split restoreService.js into components
    • Write tests

Phase 3: Frontend Decomposition (Days 11-17)

  • 3.1 Settings Page

    • Create features/settings/ directory structure
    • Extract tab components
    • Create custom hooks
    • Refactor main page
    • Write tests
  • 3.2 Event Details Page

    • Create features/events/ directory structure
    • Extract section components
    • Create custom hooks
    • Refactor main page
    • Write tests
  • 3.3 Component Decomposition

    • Split ThemeCustomizerEnhanced
    • Split CMSEditor
    • Split AdminPhotoViewer
    • Write tests

Phase 4: Cross-Cutting (Days 18-20)

  • 4.1 Route Helpers

    • Create utils/routeHelpers.js
    • Update routes to use helpers
    • Write tests
  • 4.2 Error Classes

    • Create utils/errors.js
    • Create global error handler
    • Update services to throw typed errors
    • Write tests
  • 4.3 Cleanup

    • Delete deprecated auth route files
    • Delete duplicate page files
    • Update documentation
    • Final review

Post-Implementation

  • Update README with new architecture
  • Create architecture diagram
  • Conduct code review
  • Performance benchmarking
  • Monitor error rates for 1 week

Success Metrics

Metric Current Target
Largest file (lines) 1,839 < 400
Average file size ~500 < 200
Code duplication ~3,500 lines 0
Test coverage ~60% > 85%
Build time - No increase
Bundle size - No increase

Appendix: File Impact Summary

Files to Create

Path Purpose Est. Lines
backend/src/utils/parsers.js Shared parsers 80
backend/src/utils/routeHelpers.js Route utilities 50
backend/src/utils/errors.js Error classes 60
backend/src/config/multerConfig.js Upload config 100
backend/src/services/events/* Event service layer 600
backend/src/services/photos/* Photo service layer 500
backend/src/services/settings/* Settings service layer 400
backend/src/services/backup/* Backup components 800
backend/src/services/restore/* Restore components 700
frontend/src/utils/parsers.ts Shared parsers 50
frontend/src/features/settings/* Settings feature 1,200
frontend/src/features/events/* Events feature 1,000

Files to Modify

Path Change Type
backend/src/routes/adminEvents.js Reduce to ~200 lines
backend/src/routes/adminPhotos.js Reduce to ~200 lines
backend/src/routes/adminSettings.js Reduce to ~200 lines
backend/server.js Update route imports
frontend/src/pages/admin/SettingsPage.tsx Reduce to ~100 lines
frontend/src/pages/admin/EventDetailsPage.tsx Reduce to ~150 lines

Files to Delete

Path Reason
backend/src/routes/auth-enhanced.js Consolidated into auth.js
backend/src/routes/auth-enhanced-v2.js Consolidated into auth.js
frontend/src/pages/admin/CreateEventPageEnhanced.tsx Merged into CreateEventPage
frontend/src/pages/admin/CMSPageEnhanced.tsx Merged into CMSPage

Document maintained by: Development Team Last updated: January 2026