# PunimTag Photo Viewer A modern, fast, and beautiful photo viewing website that connects to your PunimTag PostgreSQL database. ## 🚀 Quick Start ### Prerequisites See the [Prerequisites Guide](docs/PREREQUISITES.md) for a complete list of required and optional software. **Required:** - Node.js 20+ (currently using 18.19.1 - may need upgrade) - PostgreSQL database with PunimTag schema - Read-only database user (see setup below) **Optional:** - **FFmpeg** (for video thumbnail generation) - See [FFmpeg Setup Guide](docs/FFMPEG_SETUP.md) - **libvips** (for image watermarking) - See [Prerequisites Guide](docs/PREREQUISITES.md) - **Resend API Key** (for email verification) - **Network-accessible storage** (for photo uploads) ### Installation **Quick Setup (Recommended):** ```bash # Run the comprehensive setup script npm run setup ``` This will: - Install all npm dependencies - Set up Sharp library (for image processing) - Generate Prisma clients - Set up database tables (if DATABASE_URL_AUTH is configured) - Create admin user (if needed) - Verify the setup **Manual Setup:** 1. **Install dependencies:** ```bash npm run install:deps # Or manually: npm install npm run prisma:generate:all ``` The install script will: - Check Node.js version - Install npm dependencies - Set up Sharp library (for image processing) - Generate Prisma clients - Check for optional system dependencies (libvips, FFmpeg) 2. **Set up environment variables:** Create a `.env` file in the root directory: ```bash DATABASE_URL="postgresql://viewer_readonly:password@localhost:5432/punimtag" DATABASE_URL_WRITE="postgresql://viewer_write:password@localhost:5432/punimtag" DATABASE_URL_AUTH="postgresql://viewer_write:password@localhost:5432/punimtag_auth" NEXTAUTH_SECRET="your-secret-key-here" NEXTAUTH_URL="http://localhost:3001" NEXT_PUBLIC_SITE_NAME="PunimTag Photo Viewer" NEXT_PUBLIC_SITE_DESCRIPTION="Family Photo Gallery" # Email verification (Resend) RESEND_API_KEY="re_your_resend_api_key_here" RESEND_FROM_EMAIL="noreply@yourdomain.com" # Optional: Override base URL for email links (defaults to NEXTAUTH_URL) # NEXT_PUBLIC_APP_URL="http://localhost:3001" # Upload directory for pending photos (REQUIRED - must be network-accessible) # RECOMMENDED: Use the same server as your database (see docs/NETWORK_SHARE_SETUP.md) # Examples: # Database server via SSHFS: /mnt/db-server-uploads/pending-photos # Separate network share: /mnt/shared/pending-photos # Windows: \\server\share\pending-photos (mapped to drive) UPLOAD_DIR="/mnt/db-server-uploads/pending-photos" # Or use PENDING_PHOTOS_DIR as an alias # PENDING_PHOTOS_DIR="/mnt/network-share/pending-photos" ``` **Note:** Generate a secure `NEXTAUTH_SECRET` using: ```bash openssl rand -base64 32 ``` 3. **Grant read-only permissions on main database tables:** The read-only user needs SELECT permissions on all main tables. If you see "permission denied" errors, run: **✅ WORKING METHOD (tested and confirmed):** ```bash PGPASSWORD=punimtag_password psql -h localhost -U punimtag -d punimtag -f grant_readonly_permissions.sql ``` **Alternative methods:** ```bash # Using postgres user: PGPASSWORD=postgres_password psql -h localhost -U postgres -d punimtag -f grant_readonly_permissions.sql # Using sudo: sudo -u postgres psql -d punimtag -f grant_readonly_permissions.sql ``` **Check permissions:** ```bash npm run check:permissions ``` This will verify all required permissions and provide instructions if any are missing. **For Face Identification (Write Access):** You have two options to enable write access for face identification: **Option 1: Grant write permissions to existing user** (simpler) ```bash # Run as PostgreSQL superuser: psql -U postgres -d punimtag -f grant_write_permissions.sql ``` Then use the same `DATABASE_URL` for both read and write operations. **Option 2: Create a separate write user** (more secure) ```bash # Run as PostgreSQL superuser: psql -U postgres -d punimtag -f create_write_user.sql ``` Then add to your `.env` file: ```bash DATABASE_URL_WRITE="postgresql://viewer_write:password@localhost:5432/punimtag" ``` 4. **Create database tables for authentication:** ```bash # Run as PostgreSQL superuser: psql -U postgres -d punimtag_auth -f create_auth_tables.sql ``` **Add pending_photos table for photo uploads:** ```bash # Run as PostgreSQL superuser: psql -U postgres -d punimtag_auth -f migrations/add-pending-photos-table.sql ``` **Add email verification columns:** ```bash # Run as PostgreSQL superuser: psql -U postgres -d punimtag_auth -f migrations/add-email-verification-columns.sql ``` Then grant permissions to your write user: ```sql -- If using viewer_write user: GRANT SELECT, INSERT, UPDATE ON TABLE users TO viewer_write; GRANT SELECT, INSERT, UPDATE ON TABLE pending_identifications TO viewer_write; GRANT SELECT, INSERT, UPDATE ON TABLE pending_photos TO viewer_write; GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO viewer_write; GRANT USAGE, SELECT ON SEQUENCE pending_identifications_id_seq TO viewer_write; GRANT USAGE, SELECT ON SEQUENCE pending_photos_id_seq TO viewer_write; -- Or if using viewer_readonly with write permissions: GRANT SELECT, INSERT, UPDATE ON TABLE users TO viewer_readonly; GRANT SELECT, INSERT, UPDATE ON TABLE pending_identifications TO viewer_readonly; GRANT SELECT, INSERT, UPDATE ON TABLE pending_photos TO viewer_readonly; GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO viewer_readonly; GRANT USAGE, SELECT ON SEQUENCE pending_identifications_id_seq TO viewer_readonly; GRANT USAGE, SELECT ON SEQUENCE pending_photos_id_seq TO viewer_readonly; ``` 5. **Generate Prisma client:** ```bash npx prisma generate ``` 6. **Run development server:** ```bash npm run dev ``` 7. **Open your browser:** Navigate to http://localhost:3000 ## 📁 Project Structure ``` punimtag-viewer/ ├── app/ # Next.js 14 App Router │ ├── layout.tsx # Root layout │ ├── page.tsx # Home page (photo grid with search) │ ├── HomePageContent.tsx # Client component for home page │ ├── search/ # Search page │ │ ├── page.tsx # Search page │ │ └── SearchContent.tsx # Search content component │ └── api/ # API routes │ ├── search/ # Search API endpoint │ └── photos/ # Photo API endpoints ├── components/ # React components │ ├── PhotoGrid.tsx # Photo grid with tooltips │ ├── search/ # Search components │ │ ├── CollapsibleSearch.tsx # Collapsible search bar │ │ ├── FilterPanel.tsx # Filter panel │ │ ├── PeopleFilter.tsx # People filter │ │ ├── DateRangeFilter.tsx # Date range filter │ │ ├── TagFilter.tsx # Tag filter │ │ └── SearchBar.tsx # Search bar component │ └── ui/ # shadcn/ui components ├── lib/ # Utilities │ ├── db.ts # Prisma client │ └── queries.ts # Database query helpers ├── prisma/ │ └── schema.prisma # Database schema └── public/ # Static assets ``` ## 🔐 Database Setup ### Create Read-Only User On your PostgreSQL server, run: ```sql -- Create read-only user CREATE USER viewer_readonly WITH PASSWORD 'your_secure_password'; -- Grant permissions GRANT CONNECT ON DATABASE punimtag TO viewer_readonly; GRANT USAGE ON SCHEMA public TO viewer_readonly; GRANT SELECT ON ALL TABLES IN SCHEMA public TO viewer_readonly; -- Grant on future tables ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO viewer_readonly; -- Verify no write permissions REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM viewer_readonly; ``` ## 🎨 Features - ✅ Photo grid with responsive layout - ✅ Image optimization with Next.js Image - ✅ Read-only database access - ✅ Type-safe queries with Prisma - ✅ Modern, clean design - ✅ **Collapsible search bar** on main page with filters - ✅ **Search functionality** - Search by people, dates, and tags - ✅ **Photo tooltips** - Hover over photos to see people names - ✅ **Search page** - Dedicated search page at `/search` - ✅ **Filter panel** - People, date range, and tag filters ## ✉️ Email Verification The application includes email verification for new user registrations. Users must verify their email address before they can sign in. ### Setup 1. **Get a Resend API Key:** - Sign up at [resend.com](https://resend.com) - Create an API key in your dashboard - Add it to your `.env` file: ```bash RESEND_API_KEY="re_your_api_key_here" RESEND_FROM_EMAIL="noreply@yourdomain.com" ``` 2. **Run the Database Migration:** ```bash psql -U postgres -d punimtag_auth -f migrations/add-email-verification-columns.sql ``` 3. **Configure Email Domain (Optional):** - For production, verify your domain in Resend - Update `RESEND_FROM_EMAIL` to use your verified domain - For development, you can use Resend's test domain (`onboarding@resend.dev`) ### How It Works 1. **Registration:** When a user signs up, they receive a confirmation email with a verification link 2. **Verification:** Users click the link to verify their email address 3. **Login:** Users must verify their email before they can sign in 4. **Resend:** Users can request a new confirmation email if needed ### Features - ✅ Secure token-based verification (24-hour expiration) - ✅ Email verification required before login - ✅ Resend confirmation email functionality - ✅ User-friendly error messages - ✅ Backward compatible (existing users are auto-verified) ## 📤 Photo Uploads Users can upload photos for admin review. Uploaded photos are stored on a **network-accessible location** (required) and tracked in the database. ### Storage Location Uploaded photos are stored in a directory structure organized by user ID: ``` {UPLOAD_DIR}/ └── {userId}/ └── {timestamp}-{filename} ``` **Configuration (REQUIRED):** - **Must** set `UPLOAD_DIR` or `PENDING_PHOTOS_DIR` environment variable - **Must** point to a network-accessible location (database server recommended) - The directory will be created automatically if it doesn't exist **Recommended: Use Database Server** The simplest setup is to use the same server where your PostgreSQL database is located: 1. **Create directory on database server:** ```bash ssh user@db-server.example.com sudo mkdir -p /var/punimtag/uploads/pending-photos ``` 2. **Mount database server on web server (via SSHFS):** ```bash sudo apt-get install sshfs sudo mkdir -p /mnt/db-server-uploads sudo sshfs user@db-server.example.com:/var/punimtag/uploads /mnt/db-server-uploads ``` 3. **Set in .env:** ```bash UPLOAD_DIR="/mnt/db-server-uploads/pending-photos" ``` **See full setup guide:** [`docs/NETWORK_SHARE_SETUP.md`](docs/NETWORK_SHARE_SETUP.md) **Important:** - Ensure the web server process has read/write permissions - The approval system must have read access to the same location - Test network connectivity and permissions before deploying ### Database Tracking Upload metadata is stored in the `pending_photos` table in the `punimtag_auth` database: - File location and metadata - User who uploaded - Status: `pending`, `approved`, `rejected` - Review information (when reviewed, by whom, rejection reason) ### Access for Approval System The approval system can: 1. **Read files from disk** using the `file_path` from the database 2. **Query the database** for pending photos: ```sql SELECT * FROM pending_photos WHERE status = 'pending' ORDER BY submitted_at; ``` 3. **Update status** after review: ```sql UPDATE pending_photos SET status = 'approved', reviewed_at = NOW(), reviewed_by = {admin_user_id} WHERE id = {photo_id}; ``` ## 🚧 Coming Soon - [ ] Photo detail page with lightbox - [ ] Infinite scroll - [ ] Favorites system - [ ] People and tags browsers - [ ] Authentication (optional) ## 📚 Documentation For complete documentation, see: - [Quick Start Guide](../../punimtag/docs/PHOTO_VIEWER_QUICKSTART.md) - [Complete Plan](../../punimtag/docs/PHOTO_VIEWER_PLAN.md) - [Architecture](../../punimtag/docs/PHOTO_VIEWER_ARCHITECTURE.md) ## 🛠️ Development ### Available Scripts - `npm run dev` - Start development server - `npm run build` - Build for production - `npm run start` - Start production server - `npm run lint` - Run ESLint - `npm run check:permissions` - Check database permissions and provide fix instructions ### Prisma Commands - `npx prisma generate` - Generate Prisma client - `npx prisma studio` - Open Prisma Studio (database browser) - `npx prisma db pull` - Pull schema from database ## 🔍 Troubleshooting ### Permission Denied Errors If you see "permission denied for table photos" errors: 1. **Check permissions:** ```bash npm run check:permissions ``` 2. **Grant permissions (WORKING METHOD - tested and confirmed):** ```bash PGPASSWORD=punimtag_password psql -h localhost -U punimtag -d punimtag -f grant_readonly_permissions.sql ``` **Alternative methods:** ```bash # Using postgres user: PGPASSWORD=postgres_password psql -h localhost -U postgres -d punimtag -f grant_readonly_permissions.sql # Using sudo: sudo -u postgres psql -d punimtag -f grant_readonly_permissions.sql ``` 3. **Or check health endpoint:** ```bash curl http://localhost:3001/api/health ``` ### Database Connection Issues - Verify `DATABASE_URL` is set correctly in `.env` - Check that the database user exists and has the correct password - Ensure PostgreSQL is running and accessible ## ⚠️ Known Issues - Node.js version: Currently using Node 18.19.1, but Next.js 16 requires >=20.9.0 - **Solution:** Upgrade Node.js or use Node Version Manager (nvm) ## 📝 Notes ### Image Serving (Hybrid Approach) The application automatically detects and handles two types of photo storage: 1. **HTTP/HTTPS URLs** (SharePoint, CDN, etc.) - If `photo.path` starts with `http://` or `https://`, images are served directly - Next.js Image optimization is applied automatically - Configure allowed domains in `next.config.ts` → `remotePatterns` 2. **File System Paths** (Local storage) - If `photo.path` is a file system path, images are served via API proxy - Make sure photo file paths are accessible from the Next.js server - No additional configuration needed **Benefits:** - ✅ Works with both SharePoint URLs and local file system - ✅ Automatic detection - no configuration needed per photo - ✅ Optimal performance for both storage types - ✅ No N+1 database queries (path passed via query parameter) ### Search Features The application includes a powerful search system: 1. **Collapsible Search Bar** (Main Page) - Minimized by default to save space - Click to expand and reveal full filter panel - Shows active filter count badge - Filters photos in real-time 2. **Search Filters** - **People Filter**: Multi-select searchable dropdown - **Date Range Filter**: Presets (Today, This Week, This Month, This Year) or custom range - **Tag Filter**: Multi-select searchable tag filter - All filters work together with AND logic 3. **Photo Tooltips** - Hover over any photo to see people names - Shows "People: Name1, Name2" if people are identified - Falls back to filename if no people identified 4. **Search Page** (`/search`) - Dedicated search page with full filter panel - URL query parameter sync for shareable search links - Pagination support ## 🤝 Contributing This is a private project. For questions or issues, refer to the main PunimTag documentation. --- **Built with:** Next.js 14, React, TypeScript, Prisma, Tailwind CSS