diff --git a/docs/PHOTO_VIEWER_ARCHITECTURE.md b/docs/PHOTO_VIEWER_ARCHITECTURE.md new file mode 100644 index 0000000..e1e7b92 --- /dev/null +++ b/docs/PHOTO_VIEWER_ARCHITECTURE.md @@ -0,0 +1,780 @@ +# PunimTag Photo Viewer - Architecture Overview + +## System Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ EXISTING PUNIMTAG SYSTEM │ +│ ┌────────────────────┐ ┌─────────────────┐ │ +│ │ Desktop App │ │ Web Admin │ │ +│ │ (Tkinter) │ │ Interface │ │ +│ │ │ │ (React+FastAPI)│ │ +│ └──────────┬─────────┘ └────────┬────────┘ │ +│ │ │ │ +│ │ ┌──────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────────────────────────────┐ │ +│ │ PostgreSQL Database │ │ +│ │ ┌────────────────────────────┐ │ │ +│ │ │ Tables: │ │ │ +│ │ │ • photos │ │ │ +│ │ │ • faces │ │ │ +│ │ │ • people │ │ │ +│ │ │ • person_encodings │ │ │ +│ │ │ • tags │ │ │ +│ │ │ • phototaglinkage │ │ │ +│ │ │ • photo_favorites │ │ │ +│ │ └────────────────────────────┘ │ │ +│ └──────────────────┬─────────────────┘ │ +└─────────────────────┼────────────────────────────────────────────────────┘ + │ + │ READ-ONLY ACCESS + │ +┌─────────────────────▼──────────────────────────────────────────────────┐ +│ NEW PHOTO VIEWER WEBSITE │ +│ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ User's Web Browser │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ Desktop │ │ Tablet │ │ Mobile │ │ │ +│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ +│ └──────────────────────────┬─────────────────────────────────────┘ │ +│ │ HTTPS │ +│ ┌──────────────────────────▼─────────────────────────────────────┐ │ +│ │ Next.js 14 Frontend │ │ +│ │ ┌────────────────────────────────────────────────────────┐ │ │ +│ │ │ React Server Components (App Router) │ │ │ +│ │ │ • Photo Grid (Infinite Scroll) │ │ │ +│ │ │ • Search & Filters │ │ │ +│ │ │ • Lightbox Viewer │ │ │ +│ │ │ • People & Tags Browsers │ │ │ +│ │ └────────────────────────────────────────────────────────┘ │ │ +│ │ ┌────────────────────────────────────────────────────────┐ │ │ +│ │ │ Client Components (Interactive) │ │ │ +│ │ │ • TanStack Query (State Management) │ │ │ +│ │ │ • Framer Motion (Animations) │ │ │ +│ │ │ • React Photo Album (Grid Layout) │ │ │ +│ │ └────────────────────────────────────────────────────────┘ │ │ +│ └──────────────────────────┬─────────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼─────────────────────────────────────┐ │ +│ │ Next.js API Routes (Optional) │ │ +│ │ • /api/photos - List and search photos │ │ +│ │ • /api/photos/[id] - Get photo details │ │ +│ │ • /api/people - List people with photo counts │ │ +│ │ • /api/tags - List tags with photo counts │ │ +│ │ • /api/search - Combined search │ │ +│ └──────────────────────────┬─────────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼─────────────────────────────────────┐ │ +│ │ Prisma ORM Client │ │ +│ │ • Type-safe database queries │ │ +│ │ • Read-only operations │ │ +│ │ • Connection pooling │ │ +│ └──────────────────────────┬─────────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼─────────────────────────────────────┐ │ +│ │ PostgreSQL (Read-Only User) │ │ +│ │ Username: viewer_readonly │ │ +│ │ Permissions: SELECT only on all tables │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Data Flow Diagram + +### Photo Gallery Loading + +``` +User Opens Browser + │ + ▼ +┌─────────────────┐ +│ Next.js Server │ +│ (App Router) │ +└────────┬────────┘ + │ + │ 1. Server Component Renders + │ await prisma.photo.findMany() + │ + ▼ +┌─────────────────┐ +│ Prisma Client │ +└────────┬────────┘ + │ + │ 2. SQL Query (Read-Only) + │ SELECT * FROM photos + │ WHERE processed = true + │ ORDER BY date_taken DESC + │ LIMIT 30 + │ + ▼ +┌─────────────────┐ +│ PostgreSQL │ +│ Database │ +└────────┬────────┘ + │ + │ 3. Return Photo Records + │ [{id, path, filename, date_taken, ...}, ...] + │ + ▼ +┌─────────────────┐ +│ Next.js Server │ +│ (Pre-renders) │ +└────────┬────────┘ + │ + │ 4. Generate HTML with Photo Data + │ + ▼ +┌─────────────────┐ +│ User Browser │ +│ (Receives HTML)│ +└────────┬────────┘ + │ + │ 5. Hydrate React Components + │ + ▼ +┌─────────────────┐ +│ Photo Grid │ +│ (Rendered) │ +└────────┬────────┘ + │ + │ 6. Load Images (Lazy) + │ + │ + ▼ +┌─────────────────┐ +│ Next.js Image │ +│ Optimization │ +└────────┬────────┘ + │ + │ 7. Serve Optimized Images + │ (WebP, responsive sizes) + │ + ▼ +┌─────────────────┐ +│ User Sees Photos│ +└─────────────────┘ +``` + +### Search Flow + +``` +User Types in Search + │ + ▼ +┌─────────────────┐ +│ SearchBar │ +│ (Debounced) │ +└────────┬────────┘ + │ 300ms delay + ▼ +┌─────────────────┐ +│ TanStack Query │ +│ (React Query) │ +└────────┬────────┘ + │ + │ Check cache first + │ If not cached: + │ + ▼ +┌─────────────────┐ +│ API Route │ +│ /api/search │ +└────────┬────────┘ + │ + │ Build Query + │ {people: [1,2], tags: [3], dateFrom: '2024-01-01'} + │ + ▼ +┌─────────────────┐ +│ Prisma Query │ +│ Builder │ +└────────┬────────┘ + │ + │ WHERE processed = true + │ AND faces.person_id IN (1,2) + │ AND photo_tags.tag_id IN (3) + │ AND date_taken >= '2024-01-01' + │ + ▼ +┌─────────────────┐ +│ PostgreSQL │ +│ (Indexed) │ +└────────┬────────┘ + │ + │ Return Matching Photos + │ + ▼ +┌─────────────────┐ +│ TanStack Query │ +│ (Cache Result) │ +└────────┬────────┘ + │ + │ staleTime: 5 min + │ cacheTime: 10 min + │ + ▼ +┌─────────────────┐ +│ Photo Grid │ +│ (Update UI) │ +└─────────────────┘ +``` + +--- + +## Component Hierarchy + +``` +App Layout +├── Header +│ ├── Logo +│ ├── Navigation +│ │ ├── Home Link +│ │ ├── People Link +│ │ ├── Tags Link +│ │ └── Timeline Link +│ ├── SearchButton +│ └── UserMenu +│ ├── Login/Logout +│ └── Settings +│ +├── Main Content (Page) +│ │ +│ ├── HomePage (/) +│ │ ├── Hero Section +│ │ ├── QuickFilters +│ │ └── PhotoGrid +│ │ ├── PhotoCard (x30) +│ │ │ ├── Image (Next.js) +│ │ │ ├── Overlay +│ │ │ └── Actions +│ │ └── InfiniteScroll Trigger +│ │ +│ ├── SearchPage (/search) +│ │ ├── SearchBar +│ │ ├── FilterPanel +│ │ │ ├── PeopleFilter +│ │ │ ├── DateRangePicker +│ │ │ └── TagFilter +│ │ └── SearchResults +│ │ └── PhotoGrid +│ │ +│ ├── PhotoDetailPage (/photo/[id]) +│ │ ├── Lightbox +│ │ │ ├── FullSizeImage +│ │ │ ├── NavigationButtons +│ │ │ └── CloseButton +│ │ └── MetadataPanel +│ │ ├── DateTaken +│ │ ├── PeopleList +│ │ ├── TagList +│ │ └── Actions +│ │ +│ ├── PeoplePage (/people) +│ │ ├── PeopleGrid +│ │ │ └── PersonCard (x many) +│ │ │ ├── RepresentativePhoto +│ │ │ ├── PersonName +│ │ │ └── PhotoCount +│ │ └── SearchBar +│ │ +│ └── TagsPage (/tags) +│ ├── TagCloud +│ │ └── TagBadge (x many) +│ │ ├── TagName +│ │ └── PhotoCount +│ └── SearchBar +│ +└── Footer + ├── Copyright + ├── Links + └── Version +``` + +--- + +## Technology Stack Layers + +``` +┌──────────────────────────────────────────────────────┐ +│ Presentation Layer │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ React Server Components + Client Components │ │ +│ │ • shadcn/ui (UI Components) │ │ +│ │ • Tailwind CSS (Styling) │ │ +│ │ • Framer Motion (Animations) │ │ +│ │ • Lucide React (Icons) │ │ +│ └────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────┐ +│ Application Layer │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Next.js 14 (App Router) │ │ +│ │ • Server-Side Rendering │ │ +│ │ • API Routes │ │ +│ │ • Image Optimization │ │ +│ │ • Route Handlers │ │ +│ └────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────┐ +│ State Management Layer │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ TanStack Query (React Query) │ │ +│ │ • Server State Caching │ │ +│ │ • Automatic Refetching │ │ +│ │ • Optimistic Updates │ │ +│ │ • Infinite Scroll │ │ +│ └────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────┐ +│ Data Access Layer │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Prisma ORM │ │ +│ │ • Type-Safe Queries │ │ +│ │ • Connection Pooling │ │ +│ │ • Query Builder │ │ +│ │ • Migration Management │ │ +│ └────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────┐ +│ Database Layer │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ PostgreSQL │ │ +│ │ • Relational Database │ │ +│ │ • ACID Transactions │ │ +│ │ • Indices for Performance │ │ +│ │ • Read-Only User (viewer_readonly) │ │ +│ └────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ +``` + +--- + +## Deployment Architecture + +### Option 1: Vercel (Recommended) + +``` +┌──────────────────────────────────────────────────────────┐ +│ Vercel Edge Network │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Global CDN (Automatic) │ │ +│ │ • Caches static assets │ │ +│ │ │ - HTML, CSS, JS │ │ +│ │ │ - Optimized images │ │ +│ │ └─ Serves from nearest edge location │ │ +│ └────────────────────┬───────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────▼───────────────────────────────┐ │ +│ │ Next.js Server (Serverless Functions) │ │ +│ │ • API Routes │ │ +│ │ │ - /api/photos │ │ +│ │ │ - /api/search │ │ +│ │ │ - /api/people │ │ +│ │ └─ Server Components rendering │ │ +│ └────────────────────┬───────────────────────────────┘ │ +└────────────────────────┼──────────────────────────────────┘ + │ + │ Database Connection + │ (Secure, encrypted) + │ +┌────────────────────────▼──────────────────────────────────┐ +│ Your PostgreSQL Server │ +│ • On your local network OR │ +│ • Hosted database (Railway, Supabase, etc.) │ +│ • Connection via DATABASE_URL env variable │ +└───────────────────────────────────────────────────────────┘ +``` + +### Option 2: Self-Hosted (Docker) + +``` +┌──────────────────────────────────────────────────────────┐ +│ Your Server / VPS │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Caddy (Reverse Proxy + Auto HTTPS) │ │ +│ │ • Automatic SSL certificates │ │ +│ │ • HTTPS redirection │ │ +│ │ • Static file serving │ │ +│ └────────────────────┬───────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────▼───────────────────────────────┐ │ +│ │ Docker Container: punimtag-viewer │ │ +│ │ ┌──────────────────────────────────────────────┐ │ │ +│ │ │ Next.js Production Server │ │ │ +│ │ │ • Node.js 20 │ │ │ +│ │ │ • Optimized build │ │ │ +│ │ │ • Port 3000 │ │ │ +│ │ └────────────────┬─────────────────────────────┘ │ │ +│ └───────────────────┼────────────────────────────────┘ │ +│ │ │ +│ ┌───────────────────▼────────────────────────────────┐ │ +│ │ Docker Container: PostgreSQL (Optional) │ │ +│ │ • Or connect to existing PunimTag database │ │ +│ └────────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────┘ +``` + +--- + +## Security Architecture + +``` +┌──────────────────────────────────────────────────────────┐ +│ User Browser │ +└───────────────────────┬──────────────────────────────────┘ + │ HTTPS Only + │ (TLS 1.3) + ▼ +┌──────────────────────────────────────────────────────────┐ +│ Authentication Layer │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ NextAuth.js (Optional) │ │ +│ │ • JWT Sessions │ │ +│ │ • OAuth Providers (Google, etc.) │ │ +│ │ • OR Simple Password Protection │ │ +│ └────────────────────┬───────────────────────────────┘ │ +└────────────────────────┼──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────┐ +│ Authorization Layer │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Read-Only Access │ │ +│ │ • No write operations allowed │ │ +│ │ • No photo uploads │ │ +│ │ • No deletion │ │ +│ │ • Favorites only write operation (per user) │ │ +│ └────────────────────┬───────────────────────────────┘ │ +└────────────────────────┼──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────┐ +│ API Security Layer │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ • Rate Limiting (10 req/10s per IP) │ │ +│ │ • Input Validation (Zod schemas) │ │ +│ │ • SQL Injection Prevention (Prisma ORM) │ │ +│ │ • XSS Prevention (React auto-escaping) │ │ +│ │ • CSRF Protection (SameSite cookies) │ │ +│ └────────────────────┬───────────────────────────────┘ │ +└────────────────────────┼──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────┐ +│ Database Security │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Read-Only PostgreSQL User │ │ +│ │ • Username: viewer_readonly │ │ +│ │ • SELECT permission only │ │ +│ │ • No INSERT, UPDATE, DELETE │ │ +│ │ • Connection over encrypted channel │ │ +│ └────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ +``` + +--- + +## Performance Optimization Strategy + +``` +┌──────────────────────────────────────────────────────────┐ +│ Browser Layer │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Browser Caching │ │ +│ │ • Static assets: 1 year │ │ +│ │ • Images: 1 month │ │ +│ │ • API responses: 5 minutes │ │ +│ └────────────────────────────────────────────────────┘ │ +└───────────────────────┬──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────┐ +│ React Query Layer │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ In-Memory Cache │ │ +│ │ • Query results cached │ │ +│ │ • staleTime: 5 minutes │ │ +│ │ • cacheTime: 10 minutes │ │ +│ │ • Automatic background refetching │ │ +│ └────────────────────────────────────────────────────┘ │ +└───────────────────────┬──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────┐ +│ Next.js Layer │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Image Optimization │ │ +│ │ • Automatic WebP/AVIF conversion │ │ +│ │ • Responsive images (srcset) │ │ +│ │ • Lazy loading │ │ +│ │ • Blur placeholders │ │ +│ ├────────────────────────────────────────────────────┤ │ +│ │ Code Splitting │ │ +│ │ • Route-based splitting │ │ +│ │ • Dynamic imports │ │ +│ │ • Component lazy loading │ │ +│ ├────────────────────────────────────────────────────┤ │ +│ │ Server-Side Rendering │ │ +│ │ • Initial render on server │ │ +│ │ • Faster first contentful paint │ │ +│ └────────────────────────────────────────────────────┘ │ +└───────────────────────┬──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────┐ +│ Database Layer │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Prisma Connection Pooling │ │ +│ │ • Reuse database connections │ │ +│ │ • Max connections: 10 │ │ +│ ├────────────────────────────────────────────────────┤ │ +│ │ PostgreSQL Indices │ │ +│ │ • idx_photos_date_taken │ │ +│ │ • idx_photos_processed │ │ +│ │ • idx_faces_person_id │ │ +│ │ • idx_photo_tags_tag │ │ +│ ├────────────────────────────────────────────────────┤ │ +│ │ Query Optimization │ │ +│ │ • Cursor-based pagination │ │ +│ │ • SELECT only needed columns │ │ +│ │ • Minimize JOINs │ │ +│ └────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ +``` + +--- + +## Comparison: Admin Interface vs Photo Viewer + +| Aspect | Admin Interface | Photo Viewer | +|--------|----------------|--------------| +| **Purpose** | Manage & Process | View & Browse | +| **Tech Stack** | React + FastAPI | Next.js 14 | +| **Rendering** | Client-Side (SPA) | Server + Client (SSR) | +| **State** | React Context | TanStack Query | +| **Database Access** | Full (R/W) via API | Read-Only via Prisma | +| **Photo Upload** | ✅ Yes | ❌ No | +| **Face Processing** | ✅ Yes | ❌ No | +| **Search** | ✅ Advanced | ✅ User-friendly | +| **Performance** | Good | Excellent | +| **Mobile UX** | Functional | Optimized | +| **Auth** | Required | Optional | +| **Deployment** | Backend + Frontend | Single Next.js App | +| **Target Users** | Admin (1-2) | All Users (5-50+) | +| **Complexity** | High | Low | + +--- + +## Database Read Patterns + +### Admin Interface (Write-Heavy) + +``` +┌─────────────────────────────────────┐ +│ Photo Upload │ +│ INSERT INTO photos │ +│ (Heavy write operations) │ +└─────────────────────────────────────┘ + +┌─────────────────────────────────────┐ +│ Face Processing │ +│ INSERT INTO faces │ +│ UPDATE photos SET processed │ +│ (Batch operations) │ +└─────────────────────────────────────┘ + +┌─────────────────────────────────────┐ +│ Person Identification │ +│ UPDATE faces SET person_id │ +│ INSERT INTO person_encodings │ +└─────────────────────────────────────┘ +``` + +### Photo Viewer (Read-Heavy) + +``` +┌─────────────────────────────────────┐ +│ Browse Photos │ +│ SELECT * FROM photos │ +│ WHERE processed = true │ +│ ORDER BY date_taken DESC │ +│ LIMIT 30 OFFSET X │ +└─────────────────────────────────────┘ + +┌─────────────────────────────────────┐ +│ Search by Person │ +│ SELECT p.* FROM photos p │ +│ JOIN faces f ON p.id = f.photo_id │ +│ WHERE f.person_id = X │ +└─────────────────────────────────────┘ + +┌─────────────────────────────────────┐ +│ Get Photo Details │ +│ SELECT * FROM photos WHERE id = X │ +│ + JOIN faces + people + tags │ +└─────────────────────────────────────┘ +``` + +--- + +## Image Serving Strategy + +``` +┌──────────────────────────────────────────────────────────┐ +│ Original Photo Files │ +│ Location: /path/to/photos/2024/01/IMG_1234.jpg │ +│ Size: 4000x3000, 8MB │ +└───────────────────────┬──────────────────────────────────┘ + │ + │ Referenced by path in DB + │ +┌───────────────────────▼──────────────────────────────────┐ +│ Next.js Image Component │ +│ │ +└───────────────────────┬──────────────────────────────────┘ + │ + │ Optimization Request + │ +┌───────────────────────▼──────────────────────────────────┐ +│ Next.js Image Optimization │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ 1. Load original image │ │ +│ │ 2. Resize to requested dimensions │ │ +│ │ 3. Convert to WebP/AVIF (browser support) │ │ +│ │ 4. Apply quality compression (85%) │ │ +│ │ 5. Generate blur placeholder (base64) │ │ +│ │ 6. Cache optimized image │ │ +│ └────────────────────────────────────────────────────┘ │ +└───────────────────────┬──────────────────────────────────┘ + │ + │ Serve Optimized + │ +┌───────────────────────▼──────────────────────────────────┐ +│ Browser Display │ +│ Sizes: │ +│ • Thumbnail: 300x200 (~30KB WebP) │ +│ • Medium: 800x600 (~150KB WebP) │ +│ • Large: 1920x1440 (~500KB WebP) │ +│ • Full: 4000x3000 (~2MB original, if download enabled) │ +└──────────────────────────────────────────────────────────┘ +``` + +--- + +## Responsive Design Strategy + +### Mobile First Approach + +``` +┌─────────────────────────────────────────────────────────┐ +│ Mobile (<640px) │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ [☰] PunimTag [🔍] │ │ +│ ├───────────────────────────────────────────────────┤ │ +│ │ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ Photo │ │ Photo │ 2 columns │ │ +│ │ └──────────┘ └──────────┘ │ │ +│ │ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ Photo │ │ Photo │ Compact │ │ +│ │ └──────────┘ └──────────┘ │ │ +│ └───────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ Tablet (640px - 1024px) │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ [Logo] Home | People | Tags [Search] [👤] │ │ +│ ├───────────────────────────────────────────────────┤ │ +│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ +│ │ │Photo│ │Photo│ │Photo│ 3-4 columns │ │ +│ │ └─────┘ └─────┘ └─────┘ │ │ +│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ +│ │ │Photo│ │Photo│ │Photo│ Medium size │ │ +│ │ └─────┘ └─────┘ └─────┘ │ │ +│ └───────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ Desktop (>1024px) │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ [Logo] Home | People | Tags [Search Bar] [👤] │ │ +│ ├──────┬────────────────────────────────────────────┤ │ +│ │Filter│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │ │ +│ │ │ │Ph│ │Ph│ │Ph│ │Ph│ │Ph│ 5-6 columns │ │ +│ │People│ └──┘ └──┘ └──┘ └──┘ └──┘ │ │ +│ │☐ John│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │ │ +│ │☐ Jane│ │Ph│ │Ph│ │Ph│ │Ph│ │Ph│ Large grid │ │ +│ │ │ └──┘ └──┘ └──┘ └──┘ └──┘ │ │ +│ │Tags │ │ │ +│ │☐ Fam │ │ │ +│ └──────┴────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## Error Handling Flow + +``` +User Request + │ + ▼ +┌──────────────────┐ +│ Try Operation │ +└────────┬─────────┘ + │ + ├─ Success ────────────────┐ + │ ▼ + │ ┌─────────────────┐ + │ │ Return Data │ + │ │ Update UI │ + │ └─────────────────┘ + │ + └─ Error ──────────────────┐ + ▼ + ┌──────────────────┐ + │ Catch Error │ + └────────┬─────────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌───────────────┐ ┌─────────────┐ ┌────────────┐ + │ Network Error │ │ 404 Not │ │ 500 Server │ + │ (Offline) │ │ Found │ │ Error │ + └───────┬───────┘ └──────┬──────┘ └─────┬──────┘ + │ │ │ + │ │ │ + └────────────────┼──────────────┘ + ▼ + ┌──────────────────┐ + │ Log Error │ + │ (Sentry) │ + └────────┬─────────┘ + ▼ + ┌──────────────────┐ + │ Show Error UI │ + │ • Toast │ + │ • Error Page │ + │ • Retry Button │ + └──────────────────┘ +``` + +--- + +This architecture ensures a clean separation between the admin interface (for management) and the photo viewer (for browsing), while sharing the same database and maintaining data consistency. + diff --git a/docs/PHOTO_VIEWER_EXECUTIVE_SUMMARY.md b/docs/PHOTO_VIEWER_EXECUTIVE_SUMMARY.md new file mode 100644 index 0000000..66e5015 --- /dev/null +++ b/docs/PHOTO_VIEWER_EXECUTIVE_SUMMARY.md @@ -0,0 +1,425 @@ +# PunimTag Photo Viewer - Executive Summary + +## 📋 Overview + +This document provides a high-level summary of the plan to create a new photo viewing website for PunimTag. This summary is designed for decision-makers and provides key recommendations. + +--- + +## 🎯 What Are We Building? + +A **modern, fast, beautiful photo gallery website** that allows family members to browse and search photos from your PunimTag database without needing admin access. + +### Key Features +- ✅ Browse all photos in a beautiful grid layout +- ✅ Search by people, dates, and tags +- ✅ View full-size photos with metadata +- ✅ Mobile-optimized responsive design +- ✅ Fast loading with modern optimizations +- ✅ Optional authentication for privacy + +### What It's NOT +- ❌ Not a replacement for the admin interface +- ❌ No photo uploads (read-only) +- ❌ No face processing or management +- ❌ No bulk operations + +--- + +## 💡 Why Do We Need This? + +### Current Situation +You have a powerful admin interface for managing photos, but it's: +- Too complex for casual family browsing +- Requires admin credentials +- Focused on management, not viewing +- Not optimized for mobile devices + +### The Solution +A separate, simplified photo viewer that: +- Focuses solely on viewing and searching +- Easy for anyone in the family to use +- Optimized for browsing experience +- Beautiful, modern design +- Works great on all devices + +--- + +## 🏗️ Technical Approach + +### Recommended Technology Stack + +| Component | Technology | Why? | +|-----------|-----------|------| +| **Framework** | Next.js 14 | Best-in-class for performance, SEO, and developer experience | +| **UI Library** | shadcn/ui + Tailwind | Modern, beautiful, customizable components | +| **Database** | Existing PostgreSQL | Reuse your database with read-only access | +| **ORM** | Prisma | Type-safe database queries, excellent PostgreSQL support | +| **State** | TanStack Query | Perfect for server data caching and infinite scroll | +| **Images** | Next.js Image | Automatic optimization, WebP conversion, lazy loading | +| **Hosting** | Vercel (recommended) | Zero-config deployment, global CDN, free tier available | + +### Architecture Overview + +``` +User Browser → Next.js Frontend → Prisma ORM → PostgreSQL Database + (Read-Only User) +``` + +--- + +## 📊 Decision Matrix + +### 🌟 Recommended Approach: Next.js with Vercel + +**Pros:** +- ⭐ Fastest time to market (6-8 weeks) +- ⭐ Best performance (Lighthouse score >90) +- ⭐ Easiest deployment (zero config) +- ⭐ Free hosting tier available +- ⭐ Automatic image optimization +- ⭐ Built-in SEO and accessibility + +**Cons:** +- 💰 Can be expensive for high traffic (but free tier is generous) +- 🌐 Requires database to be network-accessible (or use hosted DB) + +**Best For:** Most users, especially if you want the best UX and fastest development + +--- + +### Alternative 1: Self-Hosted Docker + +**Pros:** +- 💰 Lower ongoing costs (after initial setup) +- 🔒 Full control over hosting +- 🏠 Can run on local network +- 🔐 More privacy (no third-party hosting) + +**Cons:** +- 🛠️ Requires DevOps knowledge +- ⏰ Longer setup time +- 📈 Manual scaling +- 🔧 More maintenance + +**Best For:** Technical users who want full control and have server management experience + +--- + +### Alternative 2: Astro + React Islands + +**Pros:** +- ⚡ Even faster than Next.js +- 💾 Lower bandwidth usage +- 🎨 Great for content-heavy sites + +**Cons:** +- 📚 Smaller ecosystem than Next.js +- 🆕 Less mature (but stable) +- 🔧 More manual configuration + +**Best For:** Users who prioritize absolute maximum performance + +--- + +## 💰 Cost Analysis + +### Development Costs + +| Approach | Timeline | Estimated Cost | +|----------|----------|---------------| +| **DIY (Self-Development)** | 12 weeks | Your time (200-300 hours) | +| **Freelancer** | 8-12 weeks | $18,000 - $28,000 | +| **Agency** | 6-10 weeks | $30,000 - $50,000 | + +### Monthly Hosting Costs + +| Option | Cost/Month | Best For | +|--------|------------|----------| +| **Vercel (Free Tier)** | $0 | Small families (<100 users) | +| **Vercel (Pro)** | $20 | Medium usage (100-1000 users) | +| **Railway** | $5-20 | Hobby projects | +| **VPS (DigitalOcean)** | $12-24 | Self-hosted, full control | +| **Home Server** | $0* | After hardware cost, local network | + +**Recommendation:** Start with Vercel Free Tier, upgrade if needed + +--- + +## ⏱️ Timeline + +### Fast Track (6 weeks) + +| Week | Phase | Deliverables | +|------|-------|--------------| +| 1-2 | Foundation | Project setup, database connection, basic layout | +| 3-4 | Core Features | Photo grid, lightbox, search by people/tags | +| 5 | Polish | Responsive design, animations, performance | +| 6 | Launch | Deploy, test, documentation | + +### Standard Track (12 weeks) + +| Week | Phase | Deliverables | +|------|-------|--------------| +| 1-2 | Foundation | Setup + authentication | +| 3-4 | Core Features | Photo browsing | +| 5-6 | Search | Advanced search and filters | +| 7-8 | Polish | Optimization and UX | +| 9-10 | Advanced | Favorites, sharing, timeline | +| 11-12 | Launch | Testing, deployment, docs | + +**Recommendation:** Standard track for best quality and features + +--- + +## 🎨 Design Approach + +### Modern, Clean, Fast + +- **Inspiration:** Google Photos, iCloud Photos +- **Style:** Minimalist, photo-first design +- **Colors:** Modern blues and clean whites/darks +- **Typography:** Inter font family (clean, readable) +- **Layout:** Responsive grid (masonry or justified) +- **Animations:** Subtle, smooth, meaningful + +### Mobile First +- Works beautifully on phones and tablets +- Touch-optimized interactions +- Fast loading on cellular networks +- Responsive images for all screen sizes + +--- + +## 🔒 Security Considerations + +### Database Security +- ✅ Read-only PostgreSQL user (no write access) +- ✅ Separate credentials from admin interface +- ✅ Encrypted database connections +- ✅ Network isolation (if self-hosted) + +### Application Security +- ✅ Optional authentication (NextAuth.js) +- ✅ Rate limiting to prevent abuse +- ✅ Input validation on all queries +- ✅ XSS and CSRF protection (built-in) +- ✅ HTTPS only (automatic with Vercel/Caddy) + +### Privacy Options +- ✅ Strip GPS data from EXIF +- ✅ Configurable download permissions +- ✅ Per-user favorites (privacy-preserving) +- ✅ Optional authentication requirement + +--- + +## 📈 Success Metrics + +### Technical Goals +- 🎯 Lighthouse Performance Score: >90 +- 🎯 Page Load Time: <2 seconds +- 🎯 Image Load Time: <1 second +- 🎯 API Response Time: <200ms +- 🎯 Mobile Experience: Excellent + +### User Experience Goals +- 🎯 User Adoption: 90% of family members +- 🎯 Session Duration: >5 minutes +- 🎯 Return Visits: >60% weekly +- 🎯 User Satisfaction: >4.5/5 stars +- 🎯 Mobile Usage: >50% of traffic + +--- + +## 🚦 Go/No-Go Criteria + +### ✅ Proceed If: +- You have a PostgreSQL database with photos +- You want to share photos with family/friends +- You have budget for development OR time to DIY +- You value user experience and modern design +- You want mobile-optimized photo browsing + +### ❌ Don't Proceed If: +- Your database has <100 photos (not worth it yet) +- You're happy with current admin interface for viewing +- Budget constraints (<$500 for hosting/development) +- You don't care about mobile experience +- You need write operations (upload, edit, delete) + +--- + +## 🎬 Next Steps + +### Option A: Hire Developer/Agency +1. ✅ Approve this plan +2. ✅ Choose technology stack (recommend: Next.js + Vercel) +3. ✅ Set budget and timeline +4. ✅ Find and hire developer/agency +5. ✅ Provide database access (read-only user) +6. ✅ Review progress weekly +7. ✅ Test and provide feedback +8. ✅ Launch and train users + +### Option B: DIY Development +1. ✅ Review technical requirements +2. ✅ Follow Quick Start Guide (`docs/PHOTO_VIEWER_QUICKSTART.md`) +3. ✅ Set up development environment +4. ✅ Follow phase-by-phase plan (`docs/PHOTO_VIEWER_PLAN.md`) +5. ✅ Build incrementally (one feature at a time) +6. ✅ Test with real users frequently +7. ✅ Deploy when stable +8. ✅ Iterate based on feedback + +### Option C: Hybrid Approach +1. ✅ Hire freelancer for initial setup (Weeks 1-4) +2. ✅ Learn and customize yourself (Weeks 5+) +3. ✅ Freelancer available for support +4. ✅ Best of both worlds + +**Recommendation:** Option A or C for fastest, highest-quality results + +--- + +## ❓ Frequently Asked Questions + +### Q: Can I keep using the admin interface? +**A:** Yes! The photo viewer is a separate application. Your admin interface continues working exactly as before. + +### Q: Will this modify my database? +**A:** No. The photo viewer uses a read-only database user. It can't modify your photos, faces, or people data. The only exception is the optional favorites feature, which adds per-user favorites to a separate table. + +### Q: Do I need to migrate data? +**A:** No. The photo viewer reads directly from your existing PunimTag PostgreSQL database. No data migration needed. + +### Q: Can I customize the design? +**A:** Absolutely! The design is fully customizable. You can change colors, fonts, layouts, and components to match your preferences. + +### Q: What if I have 100,000 photos? +**A:** The architecture is designed to scale. With proper indexing and pagination, it can handle hundreds of thousands of photos. Performance may require some optimization for very large databases. + +### Q: Can users upload photos? +**A:** Not in the initial design (read-only). However, this can be added as a future enhancement if needed. The admin interface remains the primary way to add photos. + +### Q: Is it mobile-friendly? +**A:** Yes! Mobile-first design with responsive layouts, touch-optimized interactions, and fast loading on cellular networks. + +### Q: What about authentication? +**A:** Optional. You can: +- Make it completely public (no login) +- Add simple password protection +- Use OAuth (Google, Facebook) +- Use email/password authentication + +Choose based on your privacy needs. + +### Q: Can I host it on my home server? +**A:** Yes! You can self-host using Docker. The plan includes instructions for both cloud (Vercel) and self-hosted (Docker) deployment. + +### Q: What if my database is not network-accessible? +**A:** For Vercel hosting, you'll need network access. For self-hosted, you can run it on the same network as your database. + +### Q: How do I update it when new photos are added? +**A:** It's automatic! The viewer reads from the live database, so new photos appear immediately after they're processed in the admin interface. + +--- + +## 🎯 Recommendation Summary + +### For Most Users: Next.js + Vercel +- ⭐ Best performance and user experience +- ⭐ Fastest development (6-8 weeks) +- ⭐ Easiest deployment and maintenance +- ⭐ Free hosting tier available +- ⭐ Proven, mature ecosystem + +### Cost: $0-20/month hosting + $18K-28K development (or DIY) + +### Timeline: 6-12 weeks depending on approach + +### Next Action: Review full plan, make go/no-go decision, allocate budget + +--- + +## 📚 Documentation Index + +| Document | Purpose | Audience | +|----------|---------|----------| +| **This Document** | Executive summary and decision guide | Decision makers | +| `PHOTO_VIEWER_PLAN.md` | Complete detailed plan (20+ pages) | Developers, project managers | +| `PHOTO_VIEWER_QUICKSTART.md` | Quick setup guide (5 minutes to start) | Developers | +| `PHOTO_VIEWER_ARCHITECTURE.md` | Technical architecture and diagrams | Developers, architects | + +--- + +## ✅ Action Items + +### For You (Decision Maker) +- [ ] Review this executive summary +- [ ] Read the full detailed plan if needed +- [ ] Make go/no-go decision +- [ ] Allocate budget (development + hosting) +- [ ] Choose deployment approach (Vercel vs self-hosted) +- [ ] Decide on authentication requirement +- [ ] Approve timeline and milestones + +### For Developer/Agent +- [ ] Read all documentation +- [ ] Set up development environment +- [ ] Create read-only database user +- [ ] Initialize Next.js project +- [ ] Follow Phase 1 tasks +- [ ] Provide weekly progress updates + +--- + +## 🎊 Expected Outcome + +After completion, you'll have: + +✅ **A beautiful, modern photo gallery** that family members love to use + +✅ **Fast, responsive browsing** on all devices (desktop, tablet, mobile) + +✅ **Powerful search capabilities** (people, dates, tags) + +✅ **Read-only access** that keeps your database safe + +✅ **Separate from admin interface** so you can manage photos independently + +✅ **Scalable architecture** that grows with your photo collection + +✅ **Low maintenance** with automatic updates and optimizations + +--- + +## 📧 Questions? + +If you have questions or need clarification on any part of this plan: + +1. **Review the detailed plan** (`PHOTO_VIEWER_PLAN.md`) +2. **Check the architecture** (`PHOTO_VIEWER_ARCHITECTURE.md`) +3. **Try the quick start** (`PHOTO_VIEWER_QUICKSTART.md`) +4. **Consult Next.js docs** (https://nextjs.org/docs) + +--- + +**Ready to proceed?** 🚀 + +Choose your path: +- **Option A:** Hire a developer and provide them with this plan +- **Option B:** Build it yourself using the Quick Start Guide +- **Option C:** Hybrid approach (developer + your customizations) + +**This is a well-planned project with clear deliverables, proven technologies, and realistic timelines.** + +Let's build something amazing! 🎉 + +--- + +**Document Version:** 1.0 +**Last Updated:** November 14, 2025 +**Status:** Ready for Decision +**Recommended Action:** Proceed with Next.js + Vercel approach + diff --git a/docs/PHOTO_VIEWER_PLAN.md b/docs/PHOTO_VIEWER_PLAN.md new file mode 100644 index 0000000..21e7672 --- /dev/null +++ b/docs/PHOTO_VIEWER_PLAN.md @@ -0,0 +1,1989 @@ +# PunimTag Photo Viewer - Frontend Website Plan + +## Executive Summary + +This document outlines the detailed plan for creating a new, lightweight, public-facing photo viewing website that connects to the existing PunimTag PostgreSQL database. This website will be separate from the admin interface and focused on providing a beautiful, fast, and intuitive photo browsing experience. + +--- + +## 1. Project Overview + +### 1.1 Purpose +Create a read-only photo gallery website that allows users to: +- Browse photos from the PunimTag database +- Search by people names, dates taken, and tags +- View high-quality photos with smooth navigation +- Experience a modern, responsive design + +### 1.2 Key Differentiators from Admin Interface +| Feature | Admin Interface | Photo Viewer | +|---------|----------------|--------------| +| Purpose | Photo management & AI processing | Photo viewing & browsing | +| Authentication | Required | Optional (configurable) | +| Operations | Read/Write (CRUD) | Read-only | +| Target Users | Administrators | End users/Family members | +| Complexity | Feature-rich, complex | Simple, elegant | +| Performance | Batch operations | Optimized for browsing | + +--- + +## 2. Technology Stack + +### 2.1 Frontend Framework +**Next.js 14+ (App Router)** +- **Why Next.js?** + - Built-in SSR/SSG for excellent performance + - Image optimization out of the box + - File-based routing + - API routes for backend proxy + - Excellent SEO support + - Great TypeScript support + +**Alternative: Astro 4+ with React Islands** +- Even faster page loads +- Partial hydration +- Great for content-heavy sites +- Zero JS by default + +**Recommendation: Next.js 14** (more mature ecosystem, better for dynamic data) + +### 2.2 UI Framework & Styling +**shadcn/ui + Tailwind CSS** +- Modern, accessible components +- Highly customizable +- Built on Radix UI primitives +- Consistent design system +- Copy-paste components (no npm bloat) + +**Additional UI Libraries:** +- **Framer Motion** - Smooth animations and transitions +- **Lucide React** - Beautiful, consistent icons +- **React Photo Album** - Optimized photo grid layouts +- **Yet Another React Lightbox** - Photo viewing/slideshow + +### 2.3 State Management +**TanStack Query (React Query) v5** +- Perfect for server state management +- Built-in caching and invalidation +- Loading and error states +- Infinite scroll support +- Optimistic updates + +**Zustand** (optional, for client state) +- Lightweight alternative to Redux +- Simple API +- Only if needed for complex client state + +### 2.4 Backend Architecture + +#### Option A: Direct Database Access (Recommended for simplicity) +``` +Next.js App → Prisma ORM → PostgreSQL Database +``` + +**Prisma ORM** +- Type-safe database client +- Auto-generated types from schema +- Excellent PostgreSQL support +- Read-only mode for safety + +#### Option B: Backend Proxy (Recommended for security) +``` +Next.js App → Lightweight API Layer → PostgreSQL Database +``` + +**Lightweight API Options:** +1. **Next.js API Routes** - Built-in, simple +2. **tRPC** - End-to-end typesafe APIs +3. **Separate FastAPI microservice** - Reuse existing backend code + +**Recommendation: Next.js API Routes with Prisma** (simplest, most integrated) + +### 2.5 Database Strategy +**Read-Only PostgreSQL Access** +- Connect to existing PunimTag PostgreSQL database +- Read-only database user for security +- Use connection pooling (PgBouncer or Prisma Accelerate) +- No schema changes needed + +--- + +## 3. Architecture Design + +### 3.1 Project Structure +``` +punimtag-viewer/ +├── src/ +│ ├── app/ # Next.js 14 App Router +│ │ ├── (marketing)/ # Public pages (landing, about) +│ │ ├── (viewer)/ # Main photo viewer pages +│ │ │ ├── layout.tsx # Viewer layout with nav +│ │ │ ├── page.tsx # Home/Gallery grid +│ │ │ ├── photo/[id]/ # Individual photo view +│ │ │ ├── people/ # Browse by people +│ │ │ ├── tags/ # Browse by tags +│ │ │ └── search/ # Search results +│ │ ├── api/ # API routes (if needed) +│ │ └── layout.tsx # Root layout +│ ├── components/ # React components +│ │ ├── ui/ # shadcn/ui components +│ │ ├── gallery/ # Gallery-specific components +│ │ │ ├── PhotoGrid.tsx +│ │ │ ├── PhotoCard.tsx +│ │ │ ├── Lightbox.tsx +│ │ │ └── InfiniteScroll.tsx +│ │ ├── search/ # Search components +│ │ │ ├── SearchBar.tsx +│ │ │ ├── FilterPanel.tsx +│ │ │ └── DateRangePicker.tsx +│ │ └── layout/ # Layout components +│ │ ├── Header.tsx +│ │ ├── Sidebar.tsx +│ │ └── Footer.tsx +│ ├── lib/ # Utilities and configs +│ │ ├── db.ts # Prisma client +│ │ ├── queries.ts # Database queries +│ │ ├── utils.ts # Helper functions +│ │ └── constants.ts # Constants +│ ├── hooks/ # Custom React hooks +│ │ ├── usePhotos.ts +│ │ ├── usePeople.ts +│ │ ├── useTags.ts +│ │ └── useInfiniteScroll.ts +│ └── types/ # TypeScript types +│ └── database.ts +├── prisma/ +│ └── schema.prisma # Prisma schema (read-only) +├── public/ +│ └── assets/ # Static assets +├── .env.local # Environment variables +├── next.config.js +├── tailwind.config.ts +├── package.json +└── README.md +``` + +### 3.2 Data Flow + +``` +User Browser + ↓ +Next.js Frontend (React Components) + ↓ +TanStack Query (Caching & State) + ↓ +Next.js API Routes (Optional Proxy) + ↓ +Prisma ORM (Type-safe queries) + ↓ +PostgreSQL Database (Read-Only) + ↓ +Return Photo Data + URLs + ↓ +Next.js Image Optimization + ↓ +Optimized Images to User +``` + +### 3.3 Image Serving Strategy + +#### Option A: Direct File Access (Fastest) +- Next.js serves images directly from file system +- Use Next.js Image component for optimization +- Configure `images.domains` for external sources +- Requires access to original photo files + +#### Option B: API Proxy (More Flexible) +- API endpoint serves images from database-referenced paths +- Supports image resizing and caching +- Can add watermarks, metadata stripping +- Slightly slower but more controlled + +**Recommendation: Option A** (Direct file access with Next.js Image optimization) + +--- + +## 4. Core Features & Pages + +### 4.1 Home/Gallery Page +**URL:** `/` + +**Features:** +- Infinite scroll photo grid (masonry or justified layout) +- Lazy loading with blur placeholders +- Quick filters: All, Favorites, Recent +- Photo count badge +- Smooth animations on load + +**Components:** +- `PhotoGrid` - Main grid container +- `PhotoCard` - Individual photo card with hover effects +- `InfiniteScroll` - Load more on scroll +- `FilterBar` - Quick filter chips + +**Performance:** +- Initial load: 30 photos +- Lazy load: 30 photos per page +- Virtual scrolling for 10,000+ photos + +### 4.2 Search Page +**URL:** `/search` + +**Search Criteria:** +1. **People Search** + - Autocomplete dropdown + - Multiple people selection + - "Match all" vs "Match any" toggle + +2. **Date Search** + - Date range picker + - Preset ranges (Today, This week, This month, This year) + - Custom date range + +3. **Tag Search** + - Tag cloud with counts + - Multi-select tags + - Hierarchical tags (if implemented) + +4. **Combined Search** + - All filters work together (AND logic) + - Clear filters button + - Search result count + +**Components:** +- `SearchBar` - Main search input +- `PeopleFilter` - People selection +- `DateRangePicker` - Date picker +- `TagFilter` - Tag selection +- `SearchResults` - Results grid + +### 4.3 Photo Detail Page +**URL:** `/photo/[id]` + +**Features:** +- Full-size photo viewer +- Photo metadata panel + - Date taken + - Dimensions + - Tagged people (with links) + - Tags (with links) +- Previous/Next navigation +- Keyboard shortcuts (←/→ arrows, Esc) +- Download button (optional, configurable) +- Share button (copy link) + +**Components:** +- `Lightbox` - Full-screen photo viewer +- `MetadataPanel` - Photo information +- `PeopleList` - Tagged people +- `TagList` - Photo tags + +### 4.4 People Browser +**URL:** `/people` + +**Features:** +- Grid of people with representative photo +- Person name and photo count +- Sort by: Name, Photo count, Recent +- Search/filter people + +**Detail Page:** `/people/[id]` +- Person's name and details +- Grid of all photos featuring this person +- Timeline view option +- Photo count + +### 4.5 Tags Browser +**URL:** `/tags` + +**Features:** +- Tag cloud or list view +- Photo count per tag +- Color-coded tags (if applicable) +- Search tags + +**Detail Page:** `/tags/[id]` +- All photos with this tag +- Related tags +- Tag description (if available) + +### 4.6 Timeline View (Optional) +**URL:** `/timeline` + +**Features:** +- Chronological photo layout +- Group by year/month/day +- Timeline scrubber +- Jump to date + +--- + +## 5. UI/UX Design + +### 5.1 Design System + +**Color Palette (Modern & Clean):** +``` +Primary: + - Light: #3b82f6 (Blue 500) + - Dark: #60a5fa (Blue 400) + +Secondary: + - Light: #8b5cf6 (Violet 500) + - Dark: #a78bfa (Violet 400) + +Neutral: + - Background Light: #ffffff + - Background Dark: #0f172a (Slate 900) + - Surface Light: #f8fafc (Slate 50) + - Surface Dark: #1e293b (Slate 800) + +Text: + - Primary Light: #0f172a (Slate 900) + - Primary Dark: #f8fafc (Slate 50) + - Secondary Light: #64748b (Slate 500) + - Secondary Dark: #94a3b8 (Slate 400) +``` + +**Typography:** +``` +Font Family: + - Headings: Inter (Google Fonts) + - Body: Inter + - Mono: JetBrains Mono (for metadata) + +Font Sizes: + - Display: 4rem (64px) + - H1: 3rem (48px) + - H2: 2.25rem (36px) + - H3: 1.875rem (30px) + - H4: 1.5rem (24px) + - Body: 1rem (16px) + - Small: 0.875rem (14px) + - Tiny: 0.75rem (12px) +``` + +**Spacing Scale:** +- 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96 (px) + +**Border Radius:** +- Small: 4px +- Medium: 8px +- Large: 12px +- XLarge: 16px + +### 5.2 Layout & Navigation + +**Header (Desktop):** +``` +┌────────────────────────────────────────────────────┐ +│ [Logo] PunimTag [Search] [User Menu] │ +│ Home | People | Tags | Timeline │ +└────────────────────────────────────────────────────┘ +``` + +**Header (Mobile):** +``` +┌─────────────────────────────┐ +│ [☰] PunimTag [Search] │ +└─────────────────────────────┘ +``` + +**Sidebar Filter Panel (Desktop):** +``` +┌──────────────┐ +│ Filters │ +│ │ +│ People │ +│ □ John │ +│ □ Jane │ +│ │ +│ Date Range │ +│ [Picker] │ +│ │ +│ Tags │ +│ □ Family │ +│ □ Vacation │ +│ │ +│ [Apply] │ +└──────────────┘ +``` + +### 5.3 Responsive Design + +**Breakpoints:** +- Mobile: < 640px (1 column) +- Tablet: 640px - 1024px (2-3 columns) +- Desktop: > 1024px (4-5 columns) +- Wide: > 1536px (5-6 columns) + +**Photo Grid:** +- Mobile: 1-2 columns +- Tablet: 3-4 columns +- Desktop: 4-5 columns +- Wide: 6+ columns + +### 5.4 Animation & Transitions + +**Principles:** +- Subtle and smooth (no jarring animations) +- Fast transitions (150-300ms) +- Meaningful motion (guide user attention) + +**Key Animations:** +1. Photo Grid + - Fade in on load + - Scale on hover + - Smooth masonry layout shifts + +2. Page Transitions + - Fade between pages + - Slide in sidebars + +3. Lightbox + - Zoom in from thumbnail + - Smooth previous/next transitions + +**Framer Motion Variants:** +```typescript +const photoCardVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.3 } + }, + hover: { + scale: 1.05, + transition: { duration: 0.2 } + } +} +``` + +--- + +## 6. Performance Optimization + +### 6.1 Image Optimization + +**Next.js Image Component:** +```typescript +{photo.filename} +``` + +**Strategies:** +1. **Responsive Images** + - Generate multiple sizes (thumbnail, medium, large, full) + - Serve appropriate size based on viewport + - Use `srcset` and `sizes` attributes + +2. **Blur Placeholder** + - Generate blur hash on backend + - Show blur while loading + - Smooth transition to full image + +3. **Lazy Loading** + - Load images as they enter viewport + - Prefetch next page of images + - Intersection Observer API + +4. **CDN Integration (Optional)** + - Cloudflare Images + - Vercel Image Optimization + - imgix or Cloudinary + +### 6.2 Data Fetching Optimization + +**React Query Configuration:** +```typescript +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + cacheTime: 10 * 60 * 1000, // 10 minutes + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}); +``` + +**Strategies:** +1. **Prefetching** + - Prefetch next page on current page load + - Prefetch photo details on hover + +2. **Infinite Scroll** + - Use React Query's `useInfiniteQuery` + - Load 30 photos per page + - Keep 3 pages in memory + +3. **Debouncing** + - Debounce search inputs (300ms) + - Debounce filter changes + +4. **Parallel Queries** + - Fetch photos and metadata simultaneously + - Batch API requests + +### 6.3 Database Optimization + +**Indexing Strategy:** +```sql +-- Existing indices (already in PunimTag) +CREATE INDEX idx_photos_date_taken ON photos(date_taken); +CREATE INDEX idx_photos_processed ON photos(processed); +CREATE INDEX idx_faces_person_id ON faces(person_id); +CREATE INDEX idx_faces_photo_id ON faces(photo_id); +CREATE INDEX idx_photo_tags_tag ON phototaglinkage(tag_id); +CREATE INDEX idx_photo_tags_photo ON phototaglinkage(photo_id); + +-- Additional indices for viewer +CREATE INDEX idx_photos_date_added_desc ON photos(date_added DESC); +CREATE INDEX idx_people_names ON people(first_name, last_name); +CREATE INDEX idx_tags_name ON tags(tag_name); +``` + +**Query Optimization:** +1. **Pagination** + - Use cursor-based pagination for infinite scroll + - Avoid OFFSET for large datasets + +2. **Joins** + - Minimize JOIN operations + - Use SELECT only needed columns + - Aggregate queries for counts + +3. **Connection Pooling** + - Use PgBouncer or Prisma connection pooling + - Limit max connections + +### 6.4 Caching Strategy + +**Multi-Layer Caching:** + +1. **Browser Cache** + - Cache static assets (1 year) + - Cache images (1 month) + - Cache API responses (5 minutes) + +2. **React Query Cache** + - Cache query results in memory + - Automatic invalidation + - Background refetching + +3. **Next.js Cache** + - Static page generation (ISR) + - API route caching + - Image optimization cache + +4. **Database Cache (Optional)** + - Redis for frequently accessed data + - Cache photo counts, tag lists + - Cache search results + +### 6.5 Performance Targets + +| Metric | Target | Measurement | +|--------|--------|-------------| +| First Contentful Paint | < 1.5s | Lighthouse | +| Largest Contentful Paint | < 2.5s | Lighthouse | +| Time to Interactive | < 3.5s | Lighthouse | +| Cumulative Layout Shift | < 0.1 | Lighthouse | +| API Response Time | < 200ms | Server logs | +| Photo Load Time | < 1s | Network tab | +| Infinite Scroll Delay | < 500ms | User testing | + +--- + +## 7. Search Implementation + +### 7.1 Search API Endpoints + +**Existing PunimTag API:** `/api/photos` +```typescript +// Search photos by multiple criteria +GET /api/photos?search_type=name&person_name=John&page=1&page_size=30 +GET /api/photos?search_type=date&date_from=2024-01-01&date_to=2024-12-31 +GET /api/photos?search_type=tags&tag_names=family,vacation&match_all=false +``` + +**New Viewer API (Recommended):** +```typescript +// Combined search endpoint +GET /api/viewer/photos?people=1,2&tags=3,4&date_from=2024-01-01&page=1 + +// Get all people with photo counts +GET /api/viewer/people + +// Get all tags with photo counts +GET /api/viewer/tags + +// Get photo by ID with full metadata +GET /api/viewer/photos/:id +``` + +### 7.2 Search UI Components + +**SearchBar Component:** +```typescript +interface SearchBarProps { + onSearch: (query: string) => void; + placeholder?: string; +} + +// Features: +// - Debounced input (300ms) +// - Clear button +// - Search icon +// - Keyboard shortcuts (Cmd+K, Ctrl+K) +``` + +**FilterPanel Component:** +```typescript +interface FilterPanelProps { + people: Person[]; + tags: Tag[]; + dateRange: DateRange; + onFilterChange: (filters: Filters) => void; +} + +// Features: +// - Collapsible sections +// - Multi-select with checkboxes +// - Date range picker +// - Clear all filters +// - Active filter count badge +``` + +**PeopleAutocomplete:** +```typescript +interface PeopleAutocompleteProps { + onSelect: (people: Person[]) => void; + multiple?: boolean; +} + +// Features: +// - Fuzzy search +// - Avatar thumbnails +// - Photo count badges +// - Keyboard navigation +``` + +### 7.3 Search Query Building + +**Prisma Query Example:** +```typescript +async function searchPhotos(filters: SearchFilters) { + const where = { + processed: true, + AND: [ + // Date filter + filters.dateFrom ? { date_taken: { gte: filters.dateFrom } } : {}, + filters.dateTo ? { date_taken: { lte: filters.dateTo } } : {}, + + // People filter (photo has face with person_id in list) + filters.people?.length ? { + faces: { + some: { + person_id: { in: filters.people } + } + } + } : {}, + + // Tags filter + filters.tags?.length ? { + photo_tags: { + some: { + tag_id: { in: filters.tags } + } + } + } : {}, + ] + }; + + return await prisma.photo.findMany({ + where, + include: { + faces: { + include: { person: true } + }, + photo_tags: { + include: { tag: true } + } + }, + orderBy: { date_taken: 'desc' }, + take: 30, + skip: (page - 1) * 30, + }); +} +``` + +### 7.4 Search Performance + +**Optimization Techniques:** +1. **Full-Text Search (Future Enhancement)** + ```sql + -- Add tsvector column for fast text search + ALTER TABLE photos ADD COLUMN search_vector tsvector; + CREATE INDEX idx_photos_search ON photos USING gin(search_vector); + ``` + +2. **Materialized Views (For Aggregate Counts)** + ```sql + -- Precompute photo counts per person + CREATE MATERIALIZED VIEW person_photo_counts AS + SELECT person_id, COUNT(*) as photo_count + FROM faces + WHERE person_id IS NOT NULL + GROUP BY person_id; + ``` + +3. **Elasticsearch Integration (Advanced)** + - Index photos in Elasticsearch + - Fast full-text search + - Faceted search + - Relevance scoring + +--- + +## 8. Security & Access Control + +### 8.1 Authentication (Optional but Recommended) + +**Auth Options:** + +1. **Simple Password Protection** + - Single shared password + - Session-based auth + - Cookie storage + +2. **Email/Password Auth** + - User registration + - Password hashing (bcrypt) + - JWT tokens + +3. **OAuth/Social Login** + - Google, Facebook, GitHub + - NextAuth.js integration + - Easy for users + +4. **Family Sharing Model** + - Invite codes + - Family groups + - Role-based access + +**Recommendation:** Start with NextAuth.js for flexibility + +**NextAuth.js Configuration:** +```typescript +// app/api/auth/[...nextauth]/route.ts +import NextAuth from "next-auth"; +import CredentialsProvider from "next-auth/providers/credentials"; +import GoogleProvider from "next-auth/providers/google"; + +export const authOptions = { + providers: [ + GoogleProvider({ + clientId: process.env.GOOGLE_ID, + clientSecret: process.env.GOOGLE_SECRET, + }), + CredentialsProvider({ + // Simple password or email/password + credentials: { + password: { label: "Password", type: "password" } + }, + authorize: async (credentials) => { + // Verify password + if (credentials.password === process.env.VIEWER_PASSWORD) { + return { id: "viewer", name: "Family Member" }; + } + return null; + } + }) + ], + pages: { + signIn: '/login', + }, + session: { + strategy: "jwt", + } +}; +``` + +### 8.2 Read-Only Database Access + +**PostgreSQL Read-Only User:** +```sql +-- Create read-only user +CREATE USER viewer_readonly WITH PASSWORD 'secure_password_here'; + +-- Grant connect permission +GRANT CONNECT ON DATABASE punimtag TO viewer_readonly; + +-- Grant usage on schema +GRANT USAGE ON SCHEMA public TO viewer_readonly; + +-- Grant SELECT on all tables +GRANT SELECT ON ALL TABLES IN SCHEMA public TO viewer_readonly; + +-- Grant SELECT on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public +GRANT SELECT ON TABLES TO viewer_readonly; + +-- Ensure no write permissions +REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM viewer_readonly; +``` + +**Prisma Configuration:** +```typescript +// prisma/schema.prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL_READONLY") +} + +// .env.local +DATABASE_URL_READONLY="postgresql://viewer_readonly:secure_password@localhost:5432/punimtag" +``` + +### 8.3 API Security + +**Rate Limiting:** +```typescript +// middleware.ts +import { Ratelimit } from "@upstash/ratelimit"; +import { Redis } from "@upstash/redis"; + +const ratelimit = new Ratelimit({ + redis: Redis.fromEnv(), + limiter: Ratelimit.slidingWindow(10, "10 s"), +}); + +export async function middleware(request: Request) { + const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1"; + const { success } = await ratelimit.limit(ip); + + if (!success) { + return new Response("Too Many Requests", { status: 429 }); + } +} +``` + +**Input Validation:** +```typescript +import { z } from "zod"; + +const searchParamsSchema = z.object({ + people: z.array(z.number().int().positive()).optional(), + tags: z.array(z.number().int().positive()).optional(), + dateFrom: z.string().datetime().optional(), + dateTo: z.string().datetime().optional(), + page: z.number().int().positive().default(1), + pageSize: z.number().int().positive().max(100).default(30), +}); +``` + +**Content Security Policy:** +```typescript +// next.config.js +const securityHeaders = [ + { + key: 'Content-Security-Policy', + value: ` + default-src 'self'; + img-src 'self' data: https:; + script-src 'self' 'unsafe-eval' 'unsafe-inline'; + style-src 'self' 'unsafe-inline'; + `.replace(/\s{2,}/g, ' ').trim() + }, + { + key: 'X-Frame-Options', + value: 'DENY' + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff' + }, +]; +``` + +### 8.4 Privacy Considerations + +**Configuration Options:** +1. **Public vs Private Mode** + - Public: No auth required + - Private: Auth required for all pages + - Hybrid: Auth required for specific people/tags + +2. **Download Controls** + - Enable/disable photo downloads + - Watermark on downloads + - Resolution limits + +3. **EXIF Data** + - Strip GPS coordinates + - Remove camera metadata + - Keep only date_taken + +4. **Favorites & User Data** + - Per-user favorites + - View history + - User-specific albums + +--- + +## 9. Development Roadmap + +### Phase 1: Foundation (Week 1-2) +**Goal:** Basic project setup and infrastructure + +**Tasks:** +1. **Project Initialization** + - [ ] Create Next.js 14 project with TypeScript + - [ ] Install dependencies (TailwindCSS, shadcn/ui, React Query) + - [ ] Configure ESLint, Prettier + - [ ] Set up Git repository + +2. **Database Setup** + - [ ] Create Prisma schema from existing PostgreSQL schema + - [ ] Generate Prisma client + - [ ] Test read-only database connection + - [ ] Create database query helpers + +3. **Design System** + - [ ] Install shadcn/ui components + - [ ] Configure Tailwind theme + - [ ] Create color palette + - [ ] Set up typography + - [ ] Create base layout components (Header, Footer) + +4. **Authentication (Optional)** + - [ ] Set up NextAuth.js + - [ ] Create login page + - [ ] Implement simple password protection + - [ ] Add session management + +**Deliverables:** +- Working Next.js app with database connection +- Basic layout and navigation +- Authentication (if required) + +### Phase 2: Core Features (Week 3-4) +**Goal:** Photo gallery and basic navigation + +**Tasks:** +1. **Photo Grid** + - [ ] Create PhotoGrid component + - [ ] Implement infinite scroll + - [ ] Add lazy loading + - [ ] Create photo card with hover effects + +2. **Photo Detail Page** + - [ ] Create Lightbox component + - [ ] Add metadata panel + - [ ] Implement previous/next navigation + - [ ] Add keyboard shortcuts + +3. **Image Optimization** + - [ ] Configure Next.js Image component + - [ ] Generate blur placeholders + - [ ] Test responsive images + - [ ] Implement lazy loading + +4. **API Routes** + - [ ] Create `/api/photos` endpoint + - [ ] Create `/api/photos/[id]` endpoint + - [ ] Add pagination support + - [ ] Implement error handling + +**Deliverables:** +- Working photo gallery with infinite scroll +- Photo detail page with lightbox +- Optimized image loading + +### Phase 3: Search & Filtering (Week 5-6) +**Goal:** Comprehensive search functionality + +**Tasks:** +1. **People Search** + - [ ] Create people browser page + - [ ] Add people filter component + - [ ] Implement people autocomplete + - [ ] Create person detail page + +2. **Tag Search** + - [ ] Create tags browser page + - [ ] Add tag filter component + - [ ] Implement tag cloud + - [ ] Create tag detail page + +3. **Date Search** + - [ ] Add date range picker + - [ ] Implement date presets + - [ ] Create timeline view (optional) + +4. **Combined Search** + - [ ] Create search page + - [ ] Implement filter panel + - [ ] Add URL query parameter sync + - [ ] Create search results view + +**Deliverables:** +- Working search with all filter types +- People and tags browsers +- Combined search functionality + +### Phase 4: Polish & Optimization (Week 7-8) +**Goal:** Performance, UX, and visual polish + +**Tasks:** +1. **Performance Optimization** + - [ ] Audit Lighthouse scores + - [ ] Optimize bundle size + - [ ] Implement code splitting + - [ ] Add service worker (PWA) + +2. **UX Improvements** + - [ ] Add loading skeletons + - [ ] Implement error boundaries + - [ ] Add toast notifications + - [ ] Create empty states + +3. **Animations** + - [ ] Add page transitions + - [ ] Implement photo grid animations + - [ ] Add hover effects + - [ ] Create lightbox transitions + +4. **Responsive Design** + - [ ] Test on mobile devices + - [ ] Optimize touch interactions + - [ ] Add mobile navigation + - [ ] Test tablet layouts + +**Deliverables:** +- Lighthouse score > 90 +- Smooth animations and transitions +- Fully responsive design + +### Phase 5: Advanced Features (Week 9-10) +**Goal:** Nice-to-have features + +**Tasks:** +1. **User Features** + - [ ] Favorites system + - [ ] Create albums (optional) + - [ ] Share links + - [ ] Download photos (configurable) + +2. **Social Features** + - [ ] Comments on photos (optional) + - [ ] Photo ratings (optional) + - [ ] Share to social media + +3. **Advanced Search** + - [ ] Full-text search + - [ ] Save searches + - [ ] Search suggestions + - [ ] Similar photos + +4. **Admin Panel (Light)** + - [ ] Basic configuration + - [ ] View usage stats + - [ ] Manage access + +**Deliverables:** +- Enhanced user experience +- Social and sharing features +- Advanced search capabilities + +### Phase 6: Testing & Deployment (Week 11-12) +**Goal:** Production-ready application + +**Tasks:** +1. **Testing** + - [ ] Unit tests (Vitest) + - [ ] Integration tests (Playwright) + - [ ] Performance testing + - [ ] Security audit + +2. **Documentation** + - [ ] User guide + - [ ] Admin documentation + - [ ] API documentation + - [ ] Deployment guide + +3. **Deployment** + - [ ] Set up production environment + - [ ] Configure environment variables + - [ ] Deploy to Vercel/Railway/VPS + - [ ] Set up monitoring + +4. **Production Optimization** + - [ ] Configure CDN + - [ ] Set up error tracking (Sentry) + - [ ] Add analytics (optional) + - [ ] Create backup strategy + +**Deliverables:** +- Deployed, production-ready application +- Complete documentation +- Monitoring and analytics + +--- + +## 10. Deployment Options + +### 10.1 Vercel (Recommended - Easiest) + +**Pros:** +- Zero-config deployment for Next.js +- Automatic HTTPS +- Global CDN +- Image optimization built-in +- Free tier available + +**Cons:** +- Requires internet access to database +- Can be expensive for high traffic + +**Setup:** +```bash +# Install Vercel CLI +npm i -g vercel + +# Deploy +vercel deploy + +# Set environment variables +vercel env add DATABASE_URL_READONLY +vercel env add NEXTAUTH_SECRET +vercel env add NEXTAUTH_URL +``` + +**Configuration:** +```json +// vercel.json +{ + "buildCommand": "npm run build", + "outputDirectory": ".next", + "devCommand": "npm run dev", + "installCommand": "npm install", + "framework": "nextjs", + "regions": ["iad1"] +} +``` + +### 10.2 Docker (Self-Hosted) + +**Pros:** +- Full control +- Can run on local network +- One-time cost +- No vendor lock-in + +**Cons:** +- Requires server management +- Need to handle HTTPS, CDN + +**Dockerfile:** +```dockerfile +FROM node:20-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npx prisma generate +RUN npm run build + +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV production + +COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +EXPOSE 3000 +ENV PORT 3000 + +CMD ["node", "server.js"] +``` + +**docker-compose.yml:** +```yaml +version: '3.8' + +services: + viewer: + build: . + ports: + - "3000:3000" + environment: + - DATABASE_URL_READONLY=postgresql://viewer_readonly:password@postgres:5432/punimtag + - NEXTAUTH_SECRET=your_secret_here + - NEXTAUTH_URL=http://localhost:3000 + restart: unless-stopped + depends_on: + - postgres + + postgres: + image: postgres:16 + # Use existing PunimTag database + environment: + - POSTGRES_DB=punimtag + - POSTGRES_USER=punimtag + - POSTGRES_PASSWORD=punimtag_password + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: +``` + +### 10.3 Railway + +**Pros:** +- Easy deployment from GitHub +- Built-in database support +- Affordable pricing +- Good for hobby projects + +**Cons:** +- Smaller network than Vercel +- Less mature platform + +### 10.4 Netlify + +**Pros:** +- Great for static sites +- Free tier +- Good CDN + +**Cons:** +- Less optimized for Next.js than Vercel +- Limited API route capabilities + +### 10.5 VPS (DigitalOcean, Linode, AWS EC2) + +**Pros:** +- Full control +- Predictable pricing +- Can run on private network + +**Cons:** +- Requires DevOps knowledge +- Manual scaling + +**Recommended:** Use Docker + Caddy for automatic HTTPS + +--- + +## 11. Configuration & Customization + +### 11.1 Environment Variables + +**.env.local:** +```bash +# Database (Read-Only) +DATABASE_URL_READONLY="postgresql://viewer_readonly:password@localhost:5432/punimtag" + +# Authentication +NEXTAUTH_SECRET="generate-with-openssl-rand-base64-32" +NEXTAUTH_URL="http://localhost:3000" + +# Optional: OAuth Providers +GOOGLE_ID="your-google-client-id" +GOOGLE_SECRET="your-google-client-secret" + +# Site Configuration +NEXT_PUBLIC_SITE_NAME="Family Photos" +NEXT_PUBLIC_SITE_DESCRIPTION="Our family photo collection" +NEXT_PUBLIC_REQUIRE_AUTH="true" + +# Feature Flags +NEXT_PUBLIC_ENABLE_DOWNLOADS="false" +NEXT_PUBLIC_ENABLE_FAVORITES="true" +NEXT_PUBLIC_ENABLE_COMMENTS="false" + +# Image Serving +NEXT_PUBLIC_PHOTO_BASE_PATH="/path/to/photos" + +# Optional: Analytics +NEXT_PUBLIC_ANALYTICS_ID="" + +# Optional: Sentry +SENTRY_DSN="" +``` + +### 11.2 Feature Flags + +**config/features.ts:** +```typescript +export const features = { + auth: { + enabled: process.env.NEXT_PUBLIC_REQUIRE_AUTH === "true", + providers: ['google', 'credentials'], + }, + photos: { + allowDownload: process.env.NEXT_PUBLIC_ENABLE_DOWNLOADS === "true", + maxDownloadSize: 1920, // max width/height for downloads + showExif: false, // Don't expose EXIF data in UI + }, + social: { + favorites: process.env.NEXT_PUBLIC_ENABLE_FAVORITES === "true", + comments: process.env.NEXT_PUBLIC_ENABLE_COMMENTS === "true", + sharing: true, + }, + search: { + fuzzySearch: true, + fullTextSearch: false, // Requires Elasticsearch + }, + ui: { + theme: 'system', // 'light' | 'dark' | 'system' + defaultView: 'grid', // 'grid' | 'masonry' | 'timeline' + photosPerPage: 30, + }, +}; +``` + +### 11.3 Branding Customization + +**config/branding.ts:** +```typescript +export const branding = { + siteName: process.env.NEXT_PUBLIC_SITE_NAME || "PunimTag Viewer", + siteDescription: process.env.NEXT_PUBLIC_SITE_DESCRIPTION || "Photo Gallery", + logo: "/logo.svg", + favicon: "/favicon.ico", + colors: { + primary: "#3b82f6", + secondary: "#8b5cf6", + }, + fonts: { + heading: "Inter", + body: "Inter", + }, +}; +``` + +--- + +## 12. Testing Strategy + +### 12.1 Unit Tests (Vitest) + +**Components:** +```typescript +// __tests__/components/PhotoCard.test.tsx +import { render, screen } from '@testing-library/react'; +import { PhotoCard } from '@/components/gallery/PhotoCard'; + +describe('PhotoCard', () => { + it('renders photo correctly', () => { + const photo = { + id: 1, + path: '/photos/test.jpg', + filename: 'test.jpg', + }; + + render(); + expect(screen.getByAlt('test.jpg')).toBeInTheDocument(); + }); +}); +``` + +**Utilities:** +```typescript +// __tests__/lib/queries.test.ts +import { searchPhotos } from '@/lib/queries'; + +describe('searchPhotos', () => { + it('filters by date range', async () => { + const results = await searchPhotos({ + dateFrom: new Date('2024-01-01'), + dateTo: new Date('2024-12-31'), + }); + + expect(results.length).toBeGreaterThan(0); + }); +}); +``` + +### 12.2 Integration Tests (Playwright) + +**E2E Tests:** +```typescript +// e2e/photo-gallery.spec.ts +import { test, expect } from '@playwright/test'; + +test('should load photo gallery', async ({ page }) => { + await page.goto('/'); + + // Wait for photos to load + await expect(page.locator('[data-testid="photo-card"]').first()).toBeVisible(); + + // Check infinite scroll + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(1000); + + const photoCount = await page.locator('[data-testid="photo-card"]').count(); + expect(photoCount).toBeGreaterThan(30); +}); + +test('should search by person', async ({ page }) => { + await page.goto('/'); + + // Open search + await page.click('[data-testid="search-button"]'); + + // Select person + await page.fill('[data-testid="people-search"]', 'John'); + await page.click('[data-testid="person-option-1"]'); + + // Apply filter + await page.click('[data-testid="apply-filters"]'); + + // Verify results + await expect(page.locator('[data-testid="person-badge"]')).toContainText('John'); +}); +``` + +### 12.3 Performance Tests + +**Lighthouse CI:** +```yaml +# .github/workflows/lighthouse.yml +name: Lighthouse CI +on: [push] + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - run: npm ci + - run: npm run build + - run: npm run start & + - uses: treosh/lighthouse-ci-action@v9 + with: + urls: | + http://localhost:3000 + http://localhost:3000/people + http://localhost:3000/search + uploadArtifacts: true + temporaryPublicStorage: true +``` + +### 12.4 Load Testing (K6) + +```javascript +// load-test.js +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export let options = { + stages: [ + { duration: '1m', target: 50 }, + { duration: '3m', target: 50 }, + { duration: '1m', target: 0 }, + ], +}; + +export default function () { + // Test photo gallery + let res = http.get('http://localhost:3000/api/photos?page=1&pageSize=30'); + check(res, { + 'status is 200': (r) => r.status === 200, + 'response time < 200ms': (r) => r.timings.duration < 200, + }); + + sleep(1); +} +``` + +--- + +## 13. Monitoring & Analytics + +### 13.1 Error Tracking (Sentry) + +**Setup:** +```bash +npm install @sentry/nextjs +npx @sentry/wizard -i nextjs +``` + +**Configuration:** +```typescript +// sentry.client.config.ts +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + tracesSampleRate: 0.1, + environment: process.env.NODE_ENV, + beforeSend(event, hint) { + // Don't send errors in development + if (process.env.NODE_ENV === 'development') { + return null; + } + return event; + }, +}); +``` + +### 13.2 Analytics (Vercel Analytics or Plausible) + +**Vercel Analytics:** +```typescript +// app/layout.tsx +import { Analytics } from '@vercel/analytics/react'; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + + ); +} +``` + +**Plausible (Privacy-Friendly):** +```typescript +// app/layout.tsx +import Script from 'next/script'; + +export default function RootLayout({ children }) { + return ( + + +