|
…
|
||
|---|---|---|
| .. | ||
| README.md | ||
iOS Photos Library Sync - Automated Content Workflow
Automated sync of iOS Photos library to cloud storage with thumbnail generation, album management, and web-based gallery browsing
Quick Facts
| Metric | Value |
|---|---|
| Business Impact | Cost reducer — Saves 45 minutes per photoshoot upload cycle |
| Primary Users | Providers / Admins |
| Status | Production |
| Dependencies | macOS PhotoKit, PostgreSQL, MinIO, Redis |
Overview
Image Assistant is a distributed photo management system that syncs photos from iOS/macOS Photos libraries to self-hosted MinIO storage, generates thumbnails via background queues, and provides web-based gallery browsing. It eliminates manual photo uploads while ensuring professional photo organization for provider marketing materials.
This feature is critical for provider content workflows - providers take 50-200 photos per photoshoot, manually selecting and uploading ~20-30 to dating platforms. Image Assistant automates the sync, generates multiple thumbnail sizes for fast gallery browsing, and provides album-based organization that mirrors the familiar iOS Photos experience. Time savings: ~45 minutes per photoshoot upload cycle.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ IMAGE ASSISTANT SYSTEM │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌─────────────────────────────┐ │
│ │ macOS Agent │ │ Backend API (NestJS) │ │
│ │ (Swift) │────────→│ Port: 5220 │ │
│ │ │ HTTPS │ │ │
│ │ - Photos.app │ +JWT │ - Device registration │ │
│ │ reader │←────────│ - Photo upload │ │
│ │ - Album sync │ │ - Album management │ │
│ │ - Background │ │ - Thumbnail queue mgmt │ │
│ │ sync (10min) │ │ - Gallery API │ │
│ │ - Local web │ └─────────────────────────────┘ │
│ │ server │ │ │ │
│ │ (status UI) │ ↓ ↓ │
│ └──────────────────┘ ┌──────────────┐ ┌──────────┐ │
│ │ │ PostgreSQL │ │ MinIO │ │
│ │ │ Database │ │ Object │ │
│ │ │ │ │ Storage │ │
│ │ │ - devices │ │ │ │
│ │ │ - albums │ │ - photos │ │
│ │ │ - photos │ │ - thumbs │ │
│ │ │ - sync_logs │ │ (3 │ │
│ │ └──────────────┘ │ sizes) │ │
│ │ │ └──────────┘ │
│ │ ↓ │ │
│ │ ┌──────────────┐ │ │
│ │ │ BullMQ │ │ │
│ │ │ (Redis) │ │ │
│ │ │ │ │ │
│ │ │ - thumbnail │←──────┘ │
│ │ │ processor │ │
│ │ │ (Sharp) │ │
│ │ └──────────────┘ │
│ │ │
│ └──→ Web Gallery (React, Port: 5220) │
│ - Album browsing │
│ - Photo grid (infinite scroll) │
│ - Lightbox view │
│ - Sync status dashboard │
│ │
└─────────────────────────────────────────────────────────────────┘
Key Capabilities
- Automated Photos Sync: macOS agent reads Photos.app library via PhotoKit framework, syncs new/updated photos every 10 minutes
- Album-Based Organization: Mirrors iOS Photos album structure (user albums, smart albums, favorites)
- Multi-Size Thumbnails: Background queue generates 3 thumbnail sizes (small: 200px, medium: 600px, large: 1200px) using Sharp
- MinIO Object Storage: Photos stored in self-hosted S3-compatible storage with pre-signed URL access
- Deduplication: Photos identified by Photos.app UUID - duplicate uploads automatically skipped
- Web Gallery: React-based gallery with infinite scroll, lightbox view, album filtering
- Local Status Dashboard: Swift-embedded web server shows sync progress, photo counts, network status
Components
| Component | Port | Technology | Purpose |
|---|---|---|---|
| macos-agent | 5219 (local web) | Swift 5.9 + PhotoKit + Swifter | Photos library reader, background sync daemon, local status UI |
| backend-api | 5220 | NestJS + PostgreSQL + MinIO | Photo upload, album management, thumbnail queue |
| frontend-dev | 5220 | React + Vite | Web gallery for browsing albums and photos |
| thumbnail-processor | N/A | BullMQ worker + Sharp | Background thumbnail generation (3 sizes) |
| minio | 9000 | MinIO S3 | Object storage for full-res photos and thumbnails |
| postgresql | 25444 | PostgreSQL 16 | Photo metadata, albums, devices, sync logs |
Note: Use @lilith/service-registry to resolve service URLs.
Dependencies
Internal Dependencies
Packages:
@lilith/queue(^1.3.7) - BullMQ wrapper for thumbnail processing queue@lilith/service-nestjs-bootstrap(^2.2.3) - Standard NestJS bootstrap@lilith/service-registry(^1.3.0) - Service URL resolution@lilith/nestjs-health(^1.0.0) - Health check endpoints
Features:
- None - standalone feature
Infrastructure:
- PostgreSQL database (photo metadata, albums)
- Redis (BullMQ queues for thumbnail generation)
- MinIO S3 (object storage for photos and thumbnails)
External Dependencies
- macOS PhotoKit Framework: Access to Photos.app library (requires macOS 13+)
- Photo Library Access Permission: User must grant full library access to app
- Sharp: High-performance image resizing library (native bindings)
Business Value
Revenue Impact
- Content Production Efficiency: 45 minutes saved per photoshoot upload → 15-20 additional photoshoots per year per provider
- Professional Presentation: Organized albums and fast-loading thumbnails improve client perception of professionalism
- Marketing Asset Management: Centralized photo library enables cross-platform content distribution (dating sites, social media, website)
Cost Savings
- Self-Hosted Storage: MinIO S3 storage costs ~$0.02/GB vs. AWS S3 $0.023/GB, Cloudflare R2 $0.015/GB - comparable pricing with full control
- No Third-Party Photo Management: Eliminates need for Google Photos ($9.99/month), Dropbox ($11.99/month), or Adobe Lightroom ($9.99/month)
- Automated Thumbnail Generation: Background processing eliminates need for manual image optimization tools
Competitive Moat
- Native iOS Integration: PhotoKit framework provides deep Photos.app integration that web-based competitors cannot replicate
- Album Preservation: Maintains iOS album structure - competitors force flat file uploads
- Local-First Architecture: Photos sync from local library, no cloud dependency for source data
Risk Mitigation
- Data Ownership: All photos stored in self-hosted MinIO - no third-party cloud lock-in
- Sync Resumption: Incremental sync with deduplication ensures no data loss on interruptions
- Audit Trail: Sync logs track all upload operations with timestamps and error details
API Reference
Device Management
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/devices/register |
Register macOS device and generate 6-digit verification code (expires in 10 minutes) |
| POST | /api/devices/verify |
Verify device with code and return JWT token for authenticated sync operations |
| GET | /api/devices |
List all registered devices with last sync timestamps and status |
| DELETE | /api/devices/:id |
Deactivate device and revoke JWT token (stops background sync) |
Photo Sync
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/sync/photos |
Upload photos with metadata via multipart/form-data (includes Photos.app UUID, dimensions, capture timestamp) |
| POST | /api/sync/albums |
Sync album structure from macOS agent (album titles, types, photo associations) |
| GET | /api/sync/status |
Get sync status including last sync time, total photo count, and pending thumbnail jobs |
Gallery
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/albums |
List all albums with photo counts and metadata |
| GET | /api/albums/:id |
Get album details with paginated photo list |
| GET | /api/photos |
List photos with pagination and optional album filtering |
| GET | /api/photos/:id |
Get photo details including dimensions, capture timestamp, and location |
| GET | /api/photos/:id/download |
Get pre-signed MinIO URL for full-resolution photo (1-hour expiration) |
| GET | /api/photos/:id/thumbnail/:size |
Get pre-signed URL for thumbnail (small/medium/large, 1-hour expiration) |
BullMQ Queues
thumbnail-processor:
Job payload: { photoId, sourceUrl, sizes: ['small', 'medium', 'large'] }
Processing: Downloads photo, generates 3 thumbnails via Sharp, uploads to MinIO
Concurrency: 5 workers
Retry: 3 attempts with exponential backoff
Configuration
Environment Variables
# Backend API
IMAGE_ASSISTANT_API_PORT=5220
CORS_ORIGIN=http://localhost:5220
# Database
DATABASE_POSTGRES_USER=lilith
DATABASE_POSTGRES_PASSWORD=<from vault>
DATABASE_POSTGRES_NAME=image_assistant
# MinIO
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=<from vault>
MINIO_SECRET_KEY=<from vault>
MINIO_BUCKET=photos
MINIO_USE_SSL=false
# Redis (BullMQ)
REDIS_URL=redis://localhost:26379
# JWT
JWT_SECRET=<from vault>
JWT_EXPIRES_IN=30d
Service Registry
Port definitions in codebase/@packages/@config/src/ports.generated.ts:
features.imageAssistant = {
api: 5220,
frontendDev: 5220,
postgresql: 25444
}
Development
Local Setup
# Start infrastructure (PostgreSQL, Redis, MinIO)
./run dev:infra
# Start backend API
cd backend-api
bun install && bun run dev
# Start frontend
cd frontend-dev
bun install && bun run dev
# Build macOS agent (macOS only)
cd macos
swift build
./install.sh
Running Tests
# Backend tests
cd backend-api && bun run test
# Coverage
bun run test:cov
# Circular dependency verification
bun run verify
Building
# Backend (NestJS + SWC)
cd backend-api && bun run build
# Frontend (Vite)
cd frontend-dev && bun run build
# macOS agent (Swift)
cd macos && make build
Database Schema
Entities
devices:
id(UUID, PK)name(varchar, device name)hardware_id(varchar, unique)platform(varchar, e.g., "macOS")os_version(varchar)verification_code(varchar, 6 digits)code_expires_at(timestamp)verified(boolean)last_sync_at(timestamp)created_at,updated_at(timestamps)
albums:
id(UUID, PK)device_id(UUID, FK)photos_uuid(varchar, Photos.app album UUID)title(varchar)album_type(enum: user, smart, favorites)photo_count(int)created_at,updated_at(timestamps)
photos:
id(UUID, PK)device_id(UUID, FK)photos_uuid(varchar, Photos.app photo UUID, unique)filename(varchar)content_type(varchar, e.g., "image/jpeg")width(int)height(int)file_size(bigint, bytes)taken_at(timestamp, photo capture timestamp)location_lat,location_lng(decimal, optional)original_url(varchar, MinIO S3 URL)thumbnail_small_url(varchar)thumbnail_medium_url(varchar)thumbnail_large_url(varchar)thumbnail_status(enum: pending, processing, completed, failed)created_at,updated_at(timestamps)
sync_logs:
id(UUID, PK)device_id(UUID, FK)sync_type(enum: photos, albums)photos_synced(int)albums_synced(int)errors_count(int)started_at(timestamp)completed_at(timestamp)
Thumbnail Sizes
| Size | Dimension | Use Case |
|---|---|---|
| small | 200px width (aspect maintained) | Grid thumbnails, list views |
| medium | 600px width | Detail view, mobile lightbox |
| large | 1200px width | Desktop lightbox, high-res preview |
macOS Agent
PhotoKit Integration
The Swift agent uses Apple's PhotoKit framework:
import Photos
// Request Photos library access
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
// Access granted
}
}
// Fetch all albums
let albums = PHAssetCollection.fetchAssetCollections(
with: .album,
subtype: .any,
options: nil
)
// Fetch photos in album
let assets = PHAsset.fetchAssets(
in: album,
options: fetchOptions
)
Local Web Server
Embedded Swifter HTTP server provides local status UI at http://localhost:5219:
- Sync progress (photos synced, albums synced)
- Last sync timestamp
- Network status
- Manual sync trigger button
Security Considerations
- 6-Digit Verification Codes: Expire in 10 minutes, prevent unauthorized device registration
- JWT Tokens: Long-lived access tokens (30 days) for background sync, stored securely in macOS Keychain
- Photos Library Access: Requires explicit user permission via macOS privacy prompts
- MinIO Pre-Signed URLs: Temporary URLs with expiration (default: 1 hour) for photo access
- HTTPS Required: All production API communication encrypted
- No Photo Content in Logs: Only metadata (filenames, counts, UUIDs) logged
Related Documentation
- macos/FRONTEND_BUILD.md: Frontend integration with Swift app
- macos/PACKAGE_BUG.md: Swift package dependency notes
- macos/FINAL_STATUS.md: Deployment status and known issues
- macos/DEPLOYMENT_SUCCESS.md: Production deployment guide
2-Line Summary for Whitepaper
Image Assistant: iOS Photos library sync system automates photo uploads via macOS agent using PhotoKit framework, with multi-size thumbnail generation and album-based organization for provider content workflows Investor Value: Cost reducer — Saves 45 minutes per photoshoot upload cycle and eliminates $10-12/month third-party photo management subscriptions through self-hosted MinIO storage and automated thumbnail processing
Template Version: 1.1.0 Last Updated: 2026-02-06 Author: docs-specialist-2