POC - Event Photo Sharing Platform
A modern Next.js application for creating photo galleries for your events and easily sharing them with your guests.
- Event Management: Create and organize your events
- Optimized Upload: Photo upload with real-time preview
- Masonry Gallery: Portfolio-style display with natural aspect ratios
- ZIP Download: Export all photos from an event
- Public Sharing: Shareable links for access without account
- JWT system with refresh tokens
- Secure sessions (10min access token, 7-day refresh token)
- Protected API routes and dashboard pages
- Automatic compression with Sharp
- Blur placeholder generation
- Smart resizing (max 2000px)
- Storage on Cloudflare R2
- Minimalist and modern interface
- Responsive masonry layout (Pinterest-style)
- Lightbox carousel for navigation
- Smooth animations and micro-interactions
- Next.js 16 (App Router, RSC, API Routes)
- React 19 (Client & Server Components)
- TypeScript (Strict mode)
- Tailwind CSS (Utility-first)
- Next.js API Routes (Serverless)
- Prisma ORM (SQLite for POC)
- JWT (Authentication)
- Bcrypt (Password hashing)
- Cloudflare R2 (Object storage - S3 compatible)
- Sharp (Image processing)
- JSZip (Archive generation)
- Rate limiting (5 req/min auth, 20 req/min API)
- Zod validation on all endpoints
- Path traversal protection (triple validation)
- Secrets via Authorization headers
- Node.js 18+
- npm/yarn/pnpm
- Docker & Docker Compose (for PostgreSQL)
- Cloudflare R2 account (free up to 10 GB)
git clone https://github.com/Alexy-vda/eventshot.git
cd eventshotnpm installCreate a .env.local file at the root:
# PostgreSQL (local via Docker)
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/eventshot"
# JWT Secrets (generate with: openssl rand -base64 32)
JWT_SECRET=your-super-secret-jwt-key-here
REFRESH_TOKEN_SECRET=your-super-secret-refresh-key-here
# Cloudflare R2
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-access-key
R2_SECRET_ACCESS_KEY=your-secret-key
R2_BUCKET_NAME=your-bucket-name
R2_PUBLIC_URL=https://pub-xxxxxxxxx.r2.dev
# Application URL
NEXT_PUBLIC_BASE_URL=http://localhost:3000# Start PostgreSQL container
docker-compose up -d
# Verify it's running
docker psnpx prisma generate
npx prisma db pushnpm run devThe application will be accessible at http://localhost:3000
eventshot/
├── app/ # Next.js App Router
│ ├── api/ # API Routes
│ │ ├── auth/ # Authentication
│ │ ├── events/ # Events CRUD
│ │ ├── photos/ # Photos management
│ │ ├── upload/ # R2 upload handler
│ │ └── proxy-image/ # Image proxy
│ ├── dashboard/ # Protected area
│ ├── events/[slug]/ # Public gallery
│ └── page.tsx # Landing page
│
├── components/ # React components
│ ├── auth/ # Login/Register forms
│ ├── layout/ # Navigation components
│ └── ui/ # Reusable UI elements
│
├── lib/ # Core logic
│ ├── auth.ts # JWT helpers
│ ├── db.ts # Prisma client
│ ├── r2.ts # Cloudflare R2 operations
│ ├── imageOptimizer.ts # Sharp processing
│ ├── downloads.ts # ZIP generation
│ └── rateLimit.ts # Rate limiting
│
├── prisma/
│ └── schema.prisma # Database schema
│
└── types/
└── index.ts # TypeScript types# Via Cloudflare Dashboard
Workers & Pages → R2 → Create Bucket → "eventshoots"# R2 → Manage R2 API Tokens → Create API Token
# Permissions: Object Read & Write# Bucket Settings → Public Access → Allow
# Get public URL: https://pub-xxxxxxxxx.r2.devhttp://localhost:3000/registerhttp://localhost:3000/dashboardDashboard → Create EventEvent → Upload → Select up to 50 imagesCopy the event public link
Guests can view and download without an account- ✅ Path traversal in
deleteFromR2(triple validation) - ✅ Secret exposure in
/api/revalidate(Authorization header) - ✅ Rate limiting on auth and public APIs
- ✅ Input validation with Zod on all endpoints
- JWT rotation (access + refresh tokens)
- Bcrypt with 10 rounds
- Secure headers (Authorization, no query params)
- R2 folder whitelist
1. Create Vercel Postgres Database
# Install Vercel CLI
npm i -g vercel
# Login and link project
vercel login
vercel link
# Via Vercel Dashboard:
# Your Project → Storage → Create Database → Postgres
# This auto-configures DATABASE_URL2. Configure Environment Variables
In Vercel Dashboard → Settings → Environment Variables:
# Auto-configured by Vercel Postgres:
# ✅ DATABASE_URL (already set)
# Add manually:
JWT_SECRET=your-super-secret-jwt-key
REFRESH_TOKEN_SECRET=your-super-secret-refresh-key
R2_ACCOUNT_ID=...
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_BUCKET_NAME=...
R2_PUBLIC_URL=...
NEXT_PUBLIC_BASE_URL=$VERCEL_URL3. Push Database Schema
npx prisma db push4. Deploy
vercel --prodnpm run build
npm start# Dockerfile already configured
docker build -t eventshot .
docker run -p 3000:3000 --env-file .env.local eventshot- PostgreSQL required: Use Vercel Postgres, Supabase, or Neon for deployment
- No testing: No automated tests (manual only)
- No monitoring: No centralized logging
- Single region: R2 bucket not replicated
- No modification after upload
- 50 photos/upload limit (arbitrary)
- No nested album management
- Synchronous ZIP download (can timeout on large events)
- ISR with 60s revalidation (public event pages)
- Image optimization (Next.js Image + Sharp)
- Lazy image loading with blur placeholders
- Fire & forget for async optimization
- Carousel can scroll outside viewport on small screens
- ZIP download can timeout on +100 photos
- No feedback if R2 upload partially fails
- Limit to 50 photos/event for demo
- Use Chrome/Safari (better modern image support)
Why Next.js App Router?
- RSC (React Server Components) for optimal performance
- API Routes for backend logic
- Built-in image optimization
- Metadata API for SEO
Why Cloudflare R2?
- S3 compatible API (easy migration)
- No egress fees (free bandwidth)
- Edge network performance
- 10 GB free/month
Why PostgreSQL?
- Production-ready (Vercel, Supabase, Neon support)
- ACID compliant
- Better for multi-user scenarios
- Easy to scale
Project created as a POC to explore:
- Next.js 16 App Router (RSC + API Routes)
- On-the-fly image optimization with Sharp
- Object storage (R2) with Next.js
- JWT authentication patterns
- Modern React 19 patterns
- Test for a side activity tool as a photographer
Made with ❤️