punimtag/viewer-frontend
Tanya f038238a69
Some checks failed
CI / skip-ci-check (push) Successful in 1m27s
CI / skip-ci-check (pull_request) Successful in 1m26s
CI / lint-and-type-check (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
CI / lint-and-type-check (push) Successful in 2m5s
CI / python-lint (push) Successful in 1m51s
CI / test-backend (push) Successful in 2m38s
CI / build (push) Failing after 2m23s
CI / secret-scanning (push) Successful in 1m40s
CI / dependency-scan (push) Successful in 1m32s
CI / sast-scan (push) Successful in 2m46s
CI / workflow-summary (push) Successful in 1m25s
chore: Enhance CI workflow with dependency audits and update package versions
This commit updates the CI workflow to include steps for auditing dependencies in both the admin and viewer frontends, ensuring that vulnerabilities are identified and addressed. Additionally, it updates the `package-lock.json` and `package.json` files to reflect the latest versions of `vite` and other dependencies, improving overall project stability and security. These changes contribute to a more robust development environment and maintain code quality.
2026-01-07 13:04:01 -05:00
..

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 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
  • libvips (for image watermarking) - See Prerequisites Guide
  • Resend API Key (for email verification)
  • Network-accessible storage (for photo uploads)

Installation

Quick Setup (Recommended):

# 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:

    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:

    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:

    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):

    PGPASSWORD=punimtag_password psql -h localhost -U punimtag -d punimtag -f grant_readonly_permissions.sql
    

    Alternative methods:

    # 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:

    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)

    # 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)

    # Run as PostgreSQL superuser:
    psql -U postgres -d punimtag -f create_write_user.sql
    

    Then add to your .env file:

    DATABASE_URL_WRITE="postgresql://viewer_write:password@localhost:5432/punimtag"
    
  4. Create database tables for authentication:

    # Run as PostgreSQL superuser:
    psql -U postgres -d punimtag_auth -f create_auth_tables.sql
    

    Add pending_photos table for photo uploads:

    # Run as PostgreSQL superuser:
    psql -U postgres -d punimtag_auth -f migrations/add-pending-photos-table.sql
    

    Add email verification columns:

    # Run as PostgreSQL superuser:
    psql -U postgres -d punimtag_auth -f migrations/add-email-verification-columns.sql
    

    Then grant permissions to your write user:

    -- 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:

    npx prisma generate
    
  6. Run development server:

    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:

-- 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
    • Create an API key in your dashboard
    • Add it to your .env file:
      RESEND_API_KEY="re_your_api_key_here"
      RESEND_FROM_EMAIL="noreply@yourdomain.com"
      
  2. Run the Database Migration:

    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:

    ssh user@db-server.example.com
    sudo mkdir -p /var/punimtag/uploads/pending-photos
    
  2. Mount database server on web server (via SSHFS):

    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:

    UPLOAD_DIR="/mnt/db-server-uploads/pending-photos"
    

See full setup guide: 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:
    SELECT * FROM pending_photos WHERE status = 'pending' ORDER BY submitted_at;
    
  3. Update status after review:
    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:

🛠️ 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:

    npm run check:permissions
    
  2. Grant permissions (WORKING METHOD - tested and confirmed):

    PGPASSWORD=punimtag_password psql -h localhost -U punimtag -d punimtag -f grant_readonly_permissions.sql
    

    Alternative methods:

    # 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:

    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.tsremotePatterns
  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