This commit adds dynamic rendering to the main page, photo detail page, and search page in the viewer frontend. By enforcing dynamic rendering, we prevent database queries during the build process, enhancing application performance and reliability. These changes contribute to a more efficient development workflow and improve the overall user experience.
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:
-
Install dependencies:
npm run install:deps # Or manually: npm install npm run prisma:generate:allThe 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)
-
Set up environment variables: Create a
.envfile 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_SECRETusing:openssl rand -base64 32 -
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.sqlAlternative 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.sqlCheck permissions:
npm run check:permissionsThis 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.sqlThen use the same
DATABASE_URLfor 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.sqlThen add to your
.envfile:DATABASE_URL_WRITE="postgresql://viewer_write:password@localhost:5432/punimtag" -
Create database tables for authentication:
# Run as PostgreSQL superuser: psql -U postgres -d punimtag_auth -f create_auth_tables.sqlAdd pending_photos table for photo uploads:
# Run as PostgreSQL superuser: psql -U postgres -d punimtag_auth -f migrations/add-pending-photos-table.sqlAdd email verification columns:
# Run as PostgreSQL superuser: psql -U postgres -d punimtag_auth -f migrations/add-email-verification-columns.sqlThen 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; -
Generate Prisma client:
npx prisma generate -
Run development server:
npm run dev -
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
-
Get a Resend API Key:
- Sign up at resend.com
- Create an API key in your dashboard
- Add it to your
.envfile:RESEND_API_KEY="re_your_api_key_here" RESEND_FROM_EMAIL="noreply@yourdomain.com"
-
Run the Database Migration:
psql -U postgres -d punimtag_auth -f migrations/add-email-verification-columns.sql -
Configure Email Domain (Optional):
- For production, verify your domain in Resend
- Update
RESEND_FROM_EMAILto use your verified domain - For development, you can use Resend's test domain (
onboarding@resend.dev)
How It Works
- Registration: When a user signs up, they receive a confirmation email with a verification link
- Verification: Users click the link to verify their email address
- Login: Users must verify their email before they can sign in
- 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_DIRorPENDING_PHOTOS_DIRenvironment 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:
-
Create directory on database server:
ssh user@db-server.example.com sudo mkdir -p /var/punimtag/uploads/pending-photos -
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 -
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:
- Read files from disk using the
file_pathfrom the database - Query the database for pending photos:
SELECT * FROM pending_photos WHERE status = 'pending' ORDER BY submitted_at; - 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 servernpm run build- Build for productionnpm run start- Start production servernpm run lint- Run ESLintnpm run check:permissions- Check database permissions and provide fix instructions
Prisma Commands
npx prisma generate- Generate Prisma clientnpx 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:
-
Check permissions:
npm run check:permissions -
Grant permissions (WORKING METHOD - tested and confirmed):
PGPASSWORD=punimtag_password psql -h localhost -U punimtag -d punimtag -f grant_readonly_permissions.sqlAlternative 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 -
Or check health endpoint:
curl http://localhost:3001/api/health
Database Connection Issues
- Verify
DATABASE_URLis 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:
-
HTTP/HTTPS URLs (SharePoint, CDN, etc.)
- If
photo.pathstarts withhttp://orhttps://, images are served directly - Next.js Image optimization is applied automatically
- Configure allowed domains in
next.config.ts→remotePatterns
- If
-
File System Paths (Local storage)
- If
photo.pathis 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
- If
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:
-
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
-
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
-
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
-
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