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

1745 lines
60 KiB
Markdown

# 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](#executive-summary)
2. [Current Architecture Analysis](#current-architecture-analysis)
3. [Target Architecture Vision](#target-architecture-vision)
4. [Phase 1: Code Duplication Elimination](#phase-1-code-duplication-elimination)
5. [Phase 2: Backend Route & Service Refactoring](#phase-2-backend-route--service-refactoring)
6. [Phase 3: Frontend Page Decomposition](#phase-3-frontend-page-decomposition)
7. [Phase 4: Cross-Cutting Concerns](#phase-4-cross-cutting-concerns)
8. [Testing Strategy](#testing-strategy)
9. [Migration & Rollback Plan](#migration--rollback-plan)
10. [Implementation Checklist](#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`
```javascript
/**
* 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`
```typescript
/**
* 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**:
```bash
# 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)
```javascript
// 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:
```bash
diff -u CreateEventPage.tsx CreateEventPageEnhanced.tsx > event_pages_diff.txt
```
2. **Merge enhanced features into standard page**
3. **Extract shared logic into custom hooks**:
```typescript
// 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**:
```typescript
interface CMSPageProps {
enableAutoSave?: boolean;
enableUnsavedWarning?: boolean;
}
export const CMSPage: React.FC<CMSPageProps> = ({
enableAutoSave = true,
enableUnsavedWarning = true
}) => {
// Merged implementation
};
```
2. **Extract reusable hooks**:
```typescript
// 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`
```javascript
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`
```javascript
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`
```javascript
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
```typescript
// 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
```typescript
// 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`
```javascript
/**
* 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`
```javascript
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`
```javascript
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
```bash
# Revert parser utility changes
git revert <commit-hash>
# No database changes required
```
#### Phase 2 Rollback
```bash
# 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
```bash
# 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*