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
This commit is contained in:
Paul Nothaft
2026-01-02 09:56:19 +01:00
parent 64ceb20431
commit 77a4bfd499
38 changed files with 7989 additions and 58 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+338
View File
@@ -0,0 +1,338 @@
# Test Specification: Custom CSS Gallery Templates
This document specifies the test cases for the Custom CSS Gallery Templates feature, which allows administrators to create and manage up to 3 custom CSS templates for gallery styling.
## Prerequisites
- Local Docker environment running (`docker-compose up`)
- Access to admin dashboard
- Backend migrations applied (052_add_css_templates.js)
## Test Cases
### 1. Template Editor Access
#### TC-CCT-001: Access CSS Templates Tab
**Steps:**
1. Navigate to Settings page
2. Look for "Custom CSS Templates" or "Styling" section
**Expected Result:**
- CSS Templates editor is accessible
- Three template slots are visible as tabs
#### TC-CCT-002: Default Template Content
**Steps:**
1. Navigate to CSS Templates editor
2. Select Template 1 tab
**Expected Result:**
- Template 1 named "Elegant Dark"
- Contains pre-populated CSS content
- Is marked as enabled
- Is marked as default
### 2. Template Editing
#### TC-CCT-003: Edit Template Name
**Steps:**
1. Select Template 2
2. Change name from "Untitled" to "My Custom Theme"
3. Save template
**Expected Result:**
- Name updates in tab
- Save confirmation shown
- Name persists after refresh
#### TC-CCT-004: Edit CSS Content
**Steps:**
1. Select Template 2
2. Add CSS: `.gallery-page { background: #ff0000; }`
3. Save template
**Expected Result:**
- CSS saved successfully
- No sanitization warnings for valid CSS
- Character count updates
#### TC-CCT-005: Enable/Disable Template
**Steps:**
1. Select Template 2
2. Toggle "Enable this template" checkbox
3. Save template
**Expected Result:**
- Template status changes
- Tab shows check mark when enabled
- Disabled templates not available in event form
### 3. CSS Sanitization
#### TC-CCT-006: Block JavaScript Expressions
**Steps:**
1. Enter CSS with `expression(alert('xss'))`
2. Save template
**Expected Result:**
- Pattern blocked (replaced with /* BLOCKED */)
- Sanitization warning shown
- Template saves with sanitized content
#### TC-CCT-007: Block @import Rules
**Steps:**
1. Enter CSS with `@import url('http://evil.com/styles.css');`
2. Save template
**Expected Result:**
- @import blocked
- Warning shown
- External resource not loaded
#### TC-CCT-008: Block External URLs
**Steps:**
1. Enter CSS with `background-image: url('http://external.com/image.jpg');`
2. Save template
**Expected Result:**
- External URL blocked
- Only data: URIs allowed for images
- Warning shown
#### TC-CCT-009: Allow Safe CSS Properties
**Steps:**
1. Enter CSS with standard properties:
```css
.gallery-page {
background-color: #333;
color: white;
font-family: Arial, sans-serif;
padding: 20px;
}
```
2. Save template
**Expected Result:**
- All properties saved as-is
- No sanitization warnings
- CSS valid
### 4. Template Size Limits
#### TC-CCT-010: CSS Size Limit
**Steps:**
1. Try to save CSS content > 100KB
2. Attempt to save
**Expected Result:**
- Error message about size limit
- Template not saved
- User informed of 100KB limit
### 5. Reset to Default
#### TC-CCT-011: Reset Template 1
**Steps:**
1. Modify Template 1 CSS
2. Save changes
3. Click "Reset to Default"
4. Confirm action
**Expected Result:**
- Template reverts to default "Elegant Dark" content
- Name reset to "Elegant Dark"
- Enable status reset to true
#### TC-CCT-012: Reset Button Only on Template 1
**Steps:**
1. Select Template 2
2. Look for Reset button
**Expected Result:**
- Reset to Default button NOT shown for Template 2 or 3
- Only Template 1 has reset option
### 6. Event Integration
#### TC-CCT-013: Template Dropdown in Event Form
**Steps:**
1. Enable at least one CSS template
2. Navigate to Create Event page
3. Look for CSS Template selector
**Expected Result:**
- Dropdown shows "None (Use default theme)" option
- Enabled templates appear in list
- Disabled templates NOT shown
#### TC-CCT-014: Assign Template to Event
**Steps:**
1. Create new event
2. Select an enabled CSS template
3. Save event
**Expected Result:**
- Event created with template assigned
- Template ID stored in database
- Event edit shows selected template
#### TC-CCT-015: Update Event Template
**Steps:**
1. Edit existing event
2. Change CSS template selection
3. Save event
**Expected Result:**
- Template updated successfully
- Gallery reflects new template
### 7. Gallery CSS Loading
#### TC-CCT-016: Gallery Loads Custom CSS
**Steps:**
1. Assign template to an event
2. View gallery as guest
3. Inspect page source/styles
**Expected Result:**
- Custom CSS injected via `<style id="gallery-custom-css">`
- Gallery styling matches template
- CSS scoped to .gallery-page
#### TC-CCT-017: Gallery Without Template
**Steps:**
1. Create event without template (select "None")
2. View gallery
**Expected Result:**
- No custom CSS loaded
- Default theme used
- No errors
#### TC-CCT-018: Disabled Template Not Applied
**Steps:**
1. Assign template to event
2. Disable the template in settings
3. View gallery
**Expected Result:**
- Custom CSS NOT loaded
- Gallery uses default styling
- No errors
### 8. API Tests
#### TC-CCT-019: Get All Templates
**Steps:**
1. Call API: `GET /api/admin/css-templates`
**Expected Result:**
- Returns array of 3 templates
- Each has: id, slot_number, name, css_content, is_enabled, is_default, updated_at
#### TC-CCT-020: Get Enabled Templates
**Steps:**
1. Call API: `GET /api/admin/css-templates/enabled`
**Expected Result:**
- Returns only enabled templates
- Each has: id, name, slot_number
#### TC-CCT-021: Update Template
**Steps:**
1. Call API: `PUT /api/admin/css-templates/2`
Body: `{ "name": "Test", "css_content": "...", "is_enabled": true }`
**Expected Result:**
- Returns 200 OK
- Template updated
- Sanitization warnings array included
#### TC-CCT-022: Gallery CSS Endpoint
**Steps:**
1. Assign template to event with slug "test-gallery"
2. Call API: `GET /api/gallery/test-gallery/css-template`
**Expected Result:**
- Returns 200 OK with Content-Type: text/css
- Body contains sanitized CSS content
#### TC-CCT-023: Gallery CSS Not Found
**Steps:**
1. Create event without template
2. Call API: `GET /api/gallery/no-template-event/css-template`
**Expected Result:**
- Returns 204 No Content
- No body
### 9. Edge Cases
#### TC-CCT-024: Empty CSS Content
**Steps:**
1. Save template with empty CSS content
2. Assign to event
3. View gallery
**Expected Result:**
- Template saves successfully
- Gallery loads without custom CSS
- No errors
#### TC-CCT-025: Invalid CSS Syntax
**Steps:**
1. Enter CSS with mismatched braces: `{ color: red;`
2. Try to save
**Expected Result:**
- Validation error shown
- Template not saved
- Error message indicates syntax issue
### 10. Persistence Tests
#### TC-CCT-026: Template Persists After Restart
**Steps:**
1. Create and save custom template
2. Restart backend container
3. Reload template editor
**Expected Result:**
- Template content preserved
- All settings intact
- No data loss
## Files Created/Modified
### Backend
- `/backend/migrations/core/052_add_css_templates.js`
- `/backend/src/utils/cssSanitizer.js`
- `/backend/src/routes/adminCssTemplates.js`
- `/backend/src/routes/gallery.js`
- `/backend/server.js`
### Frontend
- `/frontend/src/services/cssTemplates.service.ts`
- `/frontend/src/components/admin/CssTemplateEditor.tsx`
- `/frontend/src/components/admin/index.ts`
- `/frontend/src/hooks/useGalleryCustomCss.ts`
## Integration Notes
The following additional integrations are recommended:
1. Add CssTemplateEditor to Settings page styling tab
2. Add CSS template dropdown to CreateEventPageEnhanced.tsx
3. Add CSS template dropdown to CreateEventPage.tsx
4. Update event edit forms to show/edit template selection
5. Update GalleryPage.tsx to use useGalleryCustomCss hook
6. Add `.gallery-page` class to gallery container components
## Automated Testing Notes
For Playwright tests:
1. Login to admin dashboard
2. Navigate to CSS Templates editor
3. Manipulate template tabs, inputs, and checkboxes
4. Verify save operations via API calls
5. Navigate to Create Event, verify template dropdown
6. View gallery, verify custom CSS is applied
+342
View File
@@ -0,0 +1,342 @@
# Test Specification: Event Rename Feature
## Overview
This document specifies the test cases for the Event Rename feature, which allows administrators to rename events from the admin panel.
---
## Test Environment
- **URL**: http://localhost:7100
- **Admin Credentials**: admin / AdminTest@2026!
- **Prerequisites**: At least one active (non-archived) event with photos
---
## Test Cases
### TC-RN-001: Access Rename Dialog
**Description**: Verify the Rename button is visible and opens the rename dialog
**Steps**:
1. Login to admin panel
2. Navigate to an active event's detail page
3. Verify "Rename" button is visible in the header action buttons
4. Click the "Rename" button
5. Verify rename dialog opens
**Expected Results**:
- Rename button visible next to Edit button
- Dialog opens with current event name pre-filled
- Dialog shows input field for new name
---
### TC-RN-002: Validate New Name - Too Short
**Description**: Verify validation error for names shorter than 3 characters
**Steps**:
1. Open rename dialog for an event
2. Enter a name with 2 characters (e.g., "AB")
3. Observe validation state
**Expected Results**:
- Rename button remains disabled
- No API call made for validation
---
### TC-RN-003: Validate New Name - Same as Current
**Description**: Verify error when new name generates same slug as current
**Steps**:
1. Open rename dialog for an event
2. Enter the same name as current (or minor variation that results in same slug)
3. Wait for validation
**Expected Results**:
- Error message: "New name generates the same URL as the current name"
- Rename button disabled
---
### TC-RN-004: Validate New Name - Duplicate Slug
**Description**: Verify error when new name would conflict with existing event
**Steps**:
1. Create two events with different names on the same date
2. Open rename dialog for event A
3. Enter event B's name
4. Wait for validation
**Expected Results**:
- Error message: "An event with this name already exists for the same date"
- Rename button disabled
---
### TC-RN-005: Validate New Name - Valid
**Description**: Verify successful validation of a valid new name
**Steps**:
1. Open rename dialog for an event
2. Enter a valid, unique new name (at least 3 characters)
3. Wait for validation
**Expected Results**:
- New URL preview shown in green box
- Rename button becomes enabled
- No error messages
---
### TC-RN-006: Successful Rename
**Description**: Verify complete rename operation
**Steps**:
1. Open rename dialog for an event with photos
2. Enter a valid new name
3. Wait for validation to complete
4. Click "Rename Event" button
5. Wait for operation to complete
**Expected Results**:
- Progress indicator shown during operation
- Success message displayed
- New share link shown
- Files renamed count displayed
- Event name updated in UI
- Share link updated
---
### TC-RN-007: Rename with Email Notification
**Description**: Verify rename with email notification option
**Steps**:
1. Open rename dialog for event with customer email
2. Enter valid new name
3. Check "Resend invitation email with new gallery link" checkbox
4. Click "Rename Event"
5. Wait for completion
**Expected Results**:
- Operation completes successfully
- Email sent confirmation shown
- (If SMTP configured) Email received with new link
---
### TC-RN-008: Old URL Redirects to New URL
**Description**: Verify old gallery URLs redirect to new location
**Steps**:
1. Note the current gallery share link before rename
2. Rename the event
3. Try to access the old share link
4. Verify redirect to new URL
**Expected Results**:
- Old URL returns 301 redirect response
- Browser redirects to new gallery URL
- Gallery is accessible at new URL
---
### TC-RN-009: Archived Events Cannot Be Renamed
**Description**: Verify rename button is not available for archived events
**Steps**:
1. Navigate to an archived event's detail page
2. Check for Rename button presence
**Expected Results**:
- Rename button is NOT visible
- Only archive-related actions available
---
### TC-RN-010: Rename Dialog Cancel
**Description**: Verify cancel functionality in rename dialog
**Steps**:
1. Open rename dialog
2. Enter a new name
3. Click Cancel button
4. Verify dialog closes
5. Verify event name unchanged
**Expected Results**:
- Dialog closes
- No changes made to event
- Event name remains original
---
### TC-RN-011: Rename Dialog Close (X Button)
**Description**: Verify X button closes dialog without changes
**Steps**:
1. Open rename dialog
2. Enter a new name
3. Click X button in top-right
4. Verify dialog closes
5. Verify event name unchanged
**Expected Results**:
- Dialog closes
- No changes made to event
---
### TC-RN-012: Photo File Paths Updated
**Description**: Verify photo file paths are updated after rename
**Steps**:
1. Create event with photos
2. Note photo paths in database
3. Rename the event
4. Verify photos are still accessible
5. Check photo paths in database
**Expected Results**:
- All photos remain accessible
- Photo paths updated to reflect new slug
- Thumbnails still work
---
### TC-RN-013: Activity Log Entry Created
**Description**: Verify rename operation is logged
**Steps**:
1. Rename an event
2. Check activity logs in database or admin panel
**Expected Results**:
- Activity log entry created with type "event_renamed"
- Metadata includes old name, new name, old slug, new slug
- Actor information recorded
---
## API Test Cases
### TC-RN-API-001: POST /api/admin/events/:id/rename
**Description**: Test rename API endpoint
**Request**:
```json
{
"newEventName": "New Event Name",
"resendEmail": false
}
```
**Expected Response (200)**:
```json
{
"success": true,
"message": "Event renamed successfully",
"data": {
"eventId": 1,
"oldName": "Old Event Name",
"newName": "New Event Name",
"oldSlug": "wedding-old-event-name-2026-01-01",
"newSlug": "wedding-new-event-name-2026-01-01",
"newShareLink": "/gallery/wedding-new-event-name-2026-01-01/abc123...",
"emailSent": false,
"filesRenamed": 3
}
}
```
---
### TC-RN-API-002: POST /api/admin/events/:id/validate-rename
**Description**: Test rename validation endpoint
**Request**:
```json
{
"newEventName": "New Event Name"
}
```
**Expected Response (200)**:
```json
{
"valid": true,
"newSlug": "wedding-new-event-name-2026-01-01"
}
```
---
### TC-RN-API-003: Rename Requires Authentication
**Description**: Verify API requires admin authentication
**Steps**:
1. Call rename endpoint without auth token
2. Call rename endpoint with invalid token
**Expected Results**:
- 401 Unauthorized response
---
## Database Verification
### TC-RN-DB-001: Events Table Updated
After successful rename, verify:
- `event_name` updated to new name
- `slug` updated to new slug
- `share_link` updated with new slug
### TC-RN-DB-002: Photos Table Updated
After successful rename, verify:
- `path` column updated for all event photos
- `thumbnail_path` column updated for all event photos
### TC-RN-DB-003: Slug Redirects Table Populated
After successful rename, verify:
- Entry created in `slug_redirects` table
- `old_slug` contains previous slug
- `new_slug` contains new slug
- `event_id` references correct event
---
## Error Handling
### TC-RN-ERR-001: Database Error During Rename
**Description**: Verify rollback on database error
**Expected Behavior**:
- File system changes rolled back
- Original event state preserved
- Error message returned to user
### TC-RN-ERR-002: File System Error During Rename
**Description**: Verify handling of file system errors
**Expected Behavior**:
- Transaction rolled back
- Error message returned to user
- Event remains unchanged
---
## Performance
### TC-RN-PERF-001: Rename Large Event
**Description**: Verify performance with events containing many photos
**Steps**:
1. Create event with 100+ photos
2. Rename the event
3. Measure time taken
**Expected Results**:
- Operation completes within reasonable time
- Progress indicator keeps user informed
- All photos remain accessible
+245
View File
@@ -0,0 +1,245 @@
# Test Specification: Optional Event Fields
This document specifies the test cases for the Optional Event Fields feature, which allows administrators to configure whether customer name, customer email, and admin email are required when creating events.
## Prerequisites
- Local Docker environment running (`docker-compose up`)
- Access to admin dashboard
- Backend migrations applied
## Test Cases
### 1. Settings Page - Event Creation Tab
#### TC-OEF-001: Event Creation Tab Visibility
**Steps:**
1. Navigate to Settings page (`/admin/settings`)
2. Verify "Event Creation" tab is visible
**Expected Result:**
- Tab labeled "Event Creation" or similar should be present in the settings navigation
#### TC-OEF-002: Default Field Requirements
**Steps:**
1. Navigate to Settings > Event Creation tab
2. Check initial state of all three toggles
**Expected Result:**
- "Require Customer Name" toggle is ON (enabled)
- "Require Customer Email" toggle is ON (enabled)
- "Require Admin Email" toggle is ON (enabled)
#### TC-OEF-003: Toggle Customer Name Requirement
**Steps:**
1. Navigate to Settings > Event Creation
2. Toggle OFF "Require Customer Name"
3. Click Save
**Expected Result:**
- Setting saves successfully
- Toast notification confirms save
- Toggle remains OFF after page refresh
#### TC-OEF-004: Toggle Customer Email Requirement
**Steps:**
1. Navigate to Settings > Event Creation
2. Toggle OFF "Require Customer Email"
3. Click Save
**Expected Result:**
- Setting saves successfully
- Warning message about email functionality is shown
- Toggle remains OFF after page refresh
#### TC-OEF-005: Toggle Admin Email Requirement
**Steps:**
1. Navigate to Settings > Event Creation
2. Toggle OFF "Require Admin Email"
3. Click Save
**Expected Result:**
- Setting saves successfully
- Warning message about notifications is shown
- Toggle remains OFF after page refresh
### 2. Create Event Form - Conditional Validation
#### TC-OEF-006: All Fields Required (Default)
**Steps:**
1. Ensure all three settings are ON in Settings > Event Creation
2. Navigate to Create Event page
3. Try to submit form without filling customer name, customer email, or admin email
**Expected Result:**
- Validation errors shown for all three empty fields
- Form does not submit
#### TC-OEF-007: Customer Name Optional
**Steps:**
1. Set "Require Customer Name" to OFF in Settings
2. Navigate to Create Event page
3. Verify Host Name field label shows "(optional)"
4. Submit form without customer name (but with required fields filled)
**Expected Result:**
- Host Name label shows "(optional)" suffix
- Form submits successfully without customer name
- Event is created
#### TC-OEF-008: Customer Email Optional
**Steps:**
1. Set "Require Customer Email" to OFF in Settings
2. Navigate to Create Event page
3. Verify Host Email field label shows "(optional)"
4. Submit form without customer email (but with required fields filled)
**Expected Result:**
- Host Email label shows "(optional)" suffix
- Form submits successfully without customer email
- Event is created
#### TC-OEF-009: Admin Email Optional
**Steps:**
1. Set "Require Admin Email" to OFF in Settings
2. Navigate to Create Event page
3. Verify Admin Email field label shows "(optional)"
4. Submit form without admin email (but with required fields filled)
**Expected Result:**
- Admin Email label shows "(optional)" suffix
- Form submits successfully without admin email
- Event is created
#### TC-OEF-010: All Fields Optional
**Steps:**
1. Set all three settings to OFF in Settings
2. Navigate to Create Event page
3. Submit form with only event name, date, and password
**Expected Result:**
- All three optional fields show "(optional)" suffix
- Form submits successfully
- Event is created with null/empty contact fields
### 3. Backend Validation
#### TC-OEF-011: Backend Respects Settings
**Steps:**
1. Set "Require Customer Email" to OFF
2. Make direct API call to create event without customer_email:
```
POST /api/admin/events
{ event_name: "Test", event_date: "2025-01-15", ... }
```
**Expected Result:**
- API accepts the request
- Returns 201 Created
- Event is created without customer_email
#### TC-OEF-012: Backend Rejects When Required
**Steps:**
1. Set "Require Customer Email" to ON
2. Make direct API call to create event without customer_email
**Expected Result:**
- API rejects the request
- Returns 400 Bad Request with validation error
- Error message indicates customer_email is required
### 4. Format Validation for Optional Fields
#### TC-OEF-013: Invalid Email Format Still Rejected
**Steps:**
1. Set "Require Customer Email" to OFF
2. Navigate to Create Event page
3. Enter invalid email format (e.g., "notanemail")
4. Submit form
**Expected Result:**
- Validation error for invalid email format
- Form does not submit
- Error message: "Invalid email format"
### 5. Create Event Enhanced Page
#### TC-OEF-014: Enhanced Page Respects Settings
**Steps:**
1. Set all three settings to OFF
2. Navigate to enhanced Create Event page (`/admin/events/create`)
3. Verify all three fields show "(optional)"
4. Submit form without contact fields
**Expected Result:**
- All optional labels visible
- Form submits successfully
- Event is created
### 6. Settings Persistence
#### TC-OEF-015: Settings Persist Across Sessions
**Steps:**
1. Set specific combination (e.g., customer name OFF, emails ON)
2. Log out
3. Log back in
4. Navigate to Settings > Event Creation
**Expected Result:**
- Settings remain as configured
- Toggle states match what was saved
#### TC-OEF-016: Settings Persist Across Backend Restart
**Steps:**
1. Set specific combination of settings
2. Restart backend container
3. Create new event
**Expected Result:**
- Validation behavior matches saved settings
- Database persisted settings correctly
## Edge Cases
### TC-OEF-017: Empty String vs Null
**Steps:**
1. Set field to optional
2. Create event with empty string for that field
3. View event details
**Expected Result:**
- Field stores empty value appropriately
- No errors in display
### TC-OEF-018: Rapid Toggle Changes
**Steps:**
1. Quickly toggle settings on/off multiple times
2. Save after each change
**Expected Result:**
- Each save completes without error
- Final state matches last save action
## Automated Testing Notes
For Playwright tests:
1. Login to admin dashboard
2. Navigate to Settings
3. Click Event Creation tab
4. Manipulate toggles using checkbox selectors
5. Navigate to Create Event
6. Verify label text contains or doesn't contain "(optional)"
7. Attempt form submission with various field combinations
8. Assert on validation messages and success/failure states
## Files Modified
### Backend
- `/backend/migrations/core/050_add_optional_event_fields_settings.js`
- `/backend/src/routes/adminEvents.js`
- `/backend/src/routes/publicSettings.js`
### Frontend
- `/frontend/src/pages/admin/SettingsPage.tsx`
- `/frontend/src/pages/admin/CreateEventPage.tsx`
- `/frontend/src/pages/admin/CreateEventPageEnhanced.tsx`
+327
View File
@@ -0,0 +1,327 @@
# Test Specification: Photo Filtering & Export
This document specifies the test cases for the Photo Filtering & Export feature, which allows administrators to filter photos by guest feedback and export filtered selections in various formats.
## Prerequisites
- Local Docker environment running (`docker-compose up`)
- Access to admin dashboard
- Event with photos that have feedback (ratings, likes, favorites, comments)
- Backend migrations applied (051_add_photo_filter_indexes.js)
## Test Cases
### 1. Filter Panel UI
#### TC-PFE-001: Filter Panel Visibility
**Steps:**
1. Navigate to Event Details > Photos tab
2. Verify "Feedback Filters" panel is visible
**Expected Result:**
- Filter panel shows rating dropdown
- Checkboxes for: Has likes, Has favorites, Has comments
- Summary counts displayed
#### TC-PFE-002: Rating Filter Options
**Steps:**
1. Open the Rating dropdown in filter panel
2. Verify all rating options are present
**Expected Result:**
- Options: All photos, Any rating, 1+ stars, 2+ stars, 3+ stars, 4+ stars, 5 stars only
- Dropdown is functional
#### TC-PFE-003: Filter by Rating
**Steps:**
1. Select "4+ stars" from rating dropdown
2. Observe changes
**Expected Result:**
- Filter is applied
- Export menu becomes enabled (if photos match)
#### TC-PFE-004: Filter by Likes
**Steps:**
1. Check "Has likes" checkbox
2. Observe changes
**Expected Result:**
- Count shows number of photos with likes
- Filter is applied
#### TC-PFE-005: Filter by Favorites
**Steps:**
1. Check "Has favorites" checkbox
2. Observe changes
**Expected Result:**
- Count shows number of favorited photos
- Filter is applied
#### TC-PFE-006: Filter by Comments
**Steps:**
1. Check "Has comments" checkbox
2. Observe changes
**Expected Result:**
- Count shows number of commented photos
- Filter is applied
#### TC-PFE-007: AND/OR Logic Toggle
**Steps:**
1. Check multiple feedback filters (e.g., Has likes AND Has favorites)
2. Toggle between AND and OR
3. Observe export button state
**Expected Result:**
- AND: Photos must have both likes AND favorites
- OR: Photos can have likes OR favorites
- Toggle is visible when multiple filters selected
#### TC-PFE-008: Clear Filters
**Steps:**
1. Apply multiple filters
2. Click "Clear" button
**Expected Result:**
- All filters reset to default
- Rating dropdown shows "All photos"
- All checkboxes unchecked
### 2. Export Menu
#### TC-PFE-009: Export Menu Disabled State
**Steps:**
1. Clear all feedback filters
2. Ensure no photos are selected
3. Observe Export button
**Expected Result:**
- Export button is disabled
- Hint text: "Select photos or apply filters to export"
#### TC-PFE-010: Export Menu Enabled with Filters
**Steps:**
1. Apply a feedback filter (e.g., rating >= 3)
2. Observe Export button
**Expected Result:**
- Export button becomes enabled
- Dropdown shows export format options
#### TC-PFE-011: Export Format Options
**Steps:**
1. Enable Export button with filters
2. Click to open dropdown
**Expected Result:**
- Four format options visible:
- Filename List (TXT)
- Filename List (CSV)
- XMP Sidecar Files (ZIP)
- Metadata (JSON)
- Each has description text
### 3. Export Functionality
#### TC-PFE-012: Export TXT Format
**Steps:**
1. Apply filter (e.g., has favorites)
2. Click Export > Filename List (TXT)
3. Open downloaded file
**Expected Result:**
- File downloads with .txt extension
- Contains one filename per line
- Uses original filenames (e.g., IMG_0001.jpg)
#### TC-PFE-013: Export CSV Format
**Steps:**
1. Apply filter
2. Click Export > Filename List (CSV)
3. Open in spreadsheet
**Expected Result:**
- File downloads with .csv extension
- Headers: filename, original_filename, rating, rating_count, likes, favorites, comments, category, etc.
- Data rows for each filtered photo
#### TC-PFE-014: Export XMP Format
**Steps:**
1. Apply filter
2. Click Export > XMP Sidecar Files (ZIP)
3. Extract ZIP and inspect files
**Expected Result:**
- ZIP file downloads
- Contains .xmp files for each photo
- XMP files contain:
- xmp:Rating (1-5)
- xmp:Label (color)
- Description with feedback summary
- Keywords including "picpeak-export"
#### TC-PFE-015: Export JSON Format
**Steps:**
1. Apply filter
2. Click Export > Metadata (JSON)
3. Open/parse JSON file
**Expected Result:**
- JSON file downloads
- Contains export_info (event name, date, exported_at, total_photos)
- Contains photos array with full metadata
#### TC-PFE-016: XMP Rating Mapping
**Steps:**
1. Have photos with various ratings
2. Export XMP
3. Check xmp:Rating values
**Expected Result:**
- 4.5-5.0 stars → xmp:Rating="5", Label="Red"
- 3.5-4.4 stars → xmp:Rating="4", Label="Yellow"
- 2.5-3.4 stars → xmp:Rating="3", Label="Green"
- 1.5-2.4 stars → xmp:Rating="2", Label="Blue"
- 0.5-1.4 stars → xmp:Rating="1", Label="Purple"
### 4. Backend API Tests
#### TC-PFE-017: Filtered Photos Endpoint
**Steps:**
1. Call API: `GET /api/admin/photo-export/:eventId/filtered?min_rating=4&has_likes=true`
**Expected Result:**
- Returns 200 OK
- Response includes photos, pagination, and summary
- Only photos matching filter returned
#### TC-PFE-018: Filter Summary Endpoint
**Steps:**
1. Call API: `GET /api/admin/photo-export/:eventId/filter-summary`
**Expected Result:**
- Returns 200 OK
- Response includes: total, withRatings, withLikes, withFavorites, withComments
#### TC-PFE-019: Export Endpoint with Photo IDs
**Steps:**
1. Call API: `POST /api/admin/photo-export/:eventId/export`
Body: `{ "photo_ids": [1, 2, 3], "format": "csv" }`
**Expected Result:**
- Returns CSV file
- Contains only specified photos
#### TC-PFE-020: Export Endpoint with Filters
**Steps:**
1. Call API: `POST /api/admin/photo-export/:eventId/export`
Body: `{ "filter": { "minRating": 4 }, "format": "txt" }`
**Expected Result:**
- Returns TXT file
- Contains all photos matching filter
### 5. Edge Cases
#### TC-PFE-021: Empty Filter Results
**Steps:**
1. Apply filter that matches no photos (e.g., rating = 5 when no 5-star photos exist)
2. Try to export
**Expected Result:**
- Export button disabled or shows "0 photos"
- Error message if attempting export
#### TC-PFE-022: Large Export
**Steps:**
1. Filter to include 100+ photos
2. Export as XMP ZIP
**Expected Result:**
- Export completes (may take time)
- ZIP contains all matching .xmp files
- Loading indicator shown during export
#### TC-PFE-023: Special Characters in Filenames
**Steps:**
1. Have photo with special characters in original filename
2. Export CSV
**Expected Result:**
- Filename properly escaped in CSV
- No parsing errors
### 6. Integration Tests
#### TC-PFE-024: XMP Import to Lightroom
**Steps:**
1. Export XMP files
2. Place XMP files next to original photos
3. In Lightroom: Select photos > Metadata > Read Metadata from Files
**Expected Result:**
- Lightroom reads XMP files
- Ratings appear on photos
- Color labels applied
- Keywords visible in metadata panel
#### TC-PFE-025: TXT List in Lightroom Search
**Steps:**
1. Export TXT filename list
2. In Lightroom: Library > Filter > Text > Filename > Contains
3. Paste comma-separated list
**Expected Result:**
- Lightroom filters to matching files
- Can select and add to collection
### 7. Performance Tests
#### TC-PFE-026: Filter Performance
**Steps:**
1. Event with 500+ photos
2. Apply feedback filter
3. Measure response time
**Expected Result:**
- Filter applied within 2 seconds
- UI remains responsive
#### TC-PFE-027: Export Performance
**Steps:**
1. Export 200 photos as XMP ZIP
2. Measure download time
**Expected Result:**
- Export completes within reasonable time
- Progress indicator shown for large exports
## Automated Testing Notes
For Playwright tests:
1. Login to admin dashboard
2. Navigate to an event with photos
3. Go to Photos tab
4. Manipulate filter panel controls
5. Click export menu
6. Verify file downloads
7. For API tests, use direct fetch/axios calls
## Files Modified
### Backend
- `/backend/migrations/core/051_add_photo_filter_indexes.js`
- `/backend/src/utils/photoFilterBuilder.js`
- `/backend/src/services/xmpGenerator.js`
- `/backend/src/services/photoExportService.js`
- `/backend/src/routes/adminPhotoExport.js`
- `/backend/server.js`
### Frontend
- `/frontend/src/services/photos.service.ts`
- `/frontend/src/components/admin/PhotoFilterPanel.tsx`
- `/frontend/src/components/admin/PhotoExportMenu.tsx`
- `/frontend/src/components/admin/index.ts`
- `/frontend/src/pages/admin/EventDetailsPage.tsx`