# 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 │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ ... │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 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) {
// Form state management
// Validation logic
// Submit handlers
}
// hooks/useEventDraft.ts
export function useEventDraft(eventData: Partial) {
// 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 = ({
enableAutoSave = true,
enableUnsavedWarning = true
}) => {
// Merged implementation
};
```
2. **Extract reusable hooks**:
```typescript
// hooks/useAutoSave.ts
export function useAutoSave(
data: T,
saveFn: (data: T) => Promise,
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