Implementation Roadmap โ Frontpics Image Marketplace
Phase 1 โ Core Platform (MVP v1)โ
Goal: A working marketplace that photographers can upload to, buyers can purchase from, and admins can manage.
Target duration: 8โ10 weeks with 1โ2 developers.
Week 1โ2: Foundationโ
Monorepo & tooling setup
- Initialize
pnpmworkspace with Turborepo - Create
apps/api,apps/web,apps/worker,packages/database,packages/shared,packages/config - Configure shared TypeScript (
tsconfig.base.json) and ESLint across all packages - Set up Prettier,
.editorconfig
Infrastructure bootstrap
-
docker-compose.ymlwith PostgreSQL (PostGIS), Redis - NestJS app bootstrapped with Fastify adapter, Helmet, global pipes
- Next.js app bootstrapped with App Router, Tailwind, shadcn/ui
- BullMQ worker app bootstrapped
Database
- Write Prisma schema (all models from
05-database-schema.md) - Run initial
prisma migrate dev - Apply raw SQL migrations (PostGIS column + trigger, FTS trigger + GIN index, check constraints)
- Write seed script: default license types (3โ4), admin user, sample tags
Shared package
- Zod schemas for auth, image create/update
- TypeScript enums (Role, ImageStatus, etc.)
- Generic
ApiResponse<T>,PaginatedResponse<T>types
Week 3โ4: Authentication & User Profilesโ
Auth (NestJS)
-
POST /auth/registerโ create user + wallet + profile in transaction -
POST /auth/loginโ JWT access token + httpOnly refresh cookie -
POST /auth/refreshโ rotate refresh token (Redis) -
POST /auth/logoutโ invalidate refresh token -
POST /auth/verify-emailโ verify with one-time token from Redis -
POST /auth/forgot-password+POST /auth/reset-password -
JwtAuthGuard,RolesGuard,@Public(),@Roles()decorators - Rate limiting with
@nestjs/throttler+ Redis store
User & profile endpoints
-
GET/PATCH /users/me -
GET/PATCH /photographers/me/profile -
GET/PATCH /agents/me/profile
Next.js auth UI
-
/auth/login,/auth/registerpages with React Hook Form + Zod -
middleware.tsroute protection for/dashboard,/photographer,/admin -
useAuthhook with token refresh interceptor - Auth context (React Context or Zustand)
Week 5โ6: Images & Upload Pipelineโ
Upload pipeline (NestJS + Worker)
-
POST /upload/presignedโ generate R2 presigned PUT URLs -
POST /upload/confirmโ create Image + Location + Tags + enqueue jobs -
GET /upload/status/:jobIdโ poll job status - Worker: download from R2, validate MIME/size, extract EXIF
- Worker: Sharp variant generation (large, medium, thumbnail, watermarked with SVG overlay)
- Worker: upload variants to R2, set Cache-Control headers
- Worker: update
image_files+imagesstatus โpending_review - BullMQ retry strategy (3 attempts, exponential backoff)
Images API (NestJS)
-
GET /imagesโ browse with filters, FTS, keyset pagination, Redis caching -
GET /images/:idโ detail, increment view count -
GET /images/mineโ photographer's own images -
POST /imagesโ create after upload confirm -
PATCH /images/:idโ update metadata (own, draft/active only) -
DELETE /images/:idโ soft delete (own, no purchases) -
POST /images/:id/submitโ submit draft for review -
GET /images/:id/download/:orderItemIdโ generate signed download URL
Photographer upload UI (Next.js)
- Drag-and-drop
DropZonewith file list and per-file progress bars - Metadata form per image (title, description, tags, location, price, license, is_drone toggle)
- Batch submit with progress tracking via job polling
-
/photographer/imagesโ gallery view of own images with status badges -
/photographer/images/[id]/editโ metadata edit form
Buyer browse UI (Next.js)
-
/browseโ image grid with filter bar (search, city, tags, drone, price) - Infinite scroll via
useInfiniteQuery - URL-synced filters (shareable links)
-
/images/[id]โ image detail page (watermarked preview, EXIF, metadata, purchase button)
Week 7โ8: Credits, Orders & Adminโ
Orders & wallet (NestJS)
-
POST /ordersโ atomic purchase (pessimistic lock on wallet) -
GET /orders+GET /orders/:idโ buyer order history + download links -
GET /wallets/me+GET /wallets/me/transactions -
POST /payouts/requestโ photographer payout request
Admin module (NestJS)
-
GET /admin/stats - User management CRUD (list, view, suspend, role change, manual credit)
- Image moderation (pending queue, approve, reject with note)
- Order list + refund action
- Payout management (process, complete, fail)
- Tag CRUD
- License type CRUD
- Audit log viewer
Admin UI (Next.js)
- Admin sidebar layout
- Stats dashboard with KPI cards
- User list with DataTable (TanStack Table)
- Moderation queue with watermarked preview + approve/reject actions
- Orders table with refund action
- Payouts table with process/complete/fail actions
- Tags and License types tables with inline edit
Buyer dashboard UI
-
/dashboardโ wallet balance, recent orders -
/dashboard/ordersโ paginated order history - Download button generating fresh signed URL
Photographer earnings UI
-
/photographer/earningsโ credits earned, payout history, request payout form
Week 9โ10: CI/CD, Deployment, Polishโ
CI/CD
- GitHub Actions:
ci.yml(type-check, lint, test on PR) - GitHub Actions:
deploy.yml(build + push Docker images to GHCR) - Per-app Dockerfiles (api, web, worker) with multi-stage builds
-
docker-compose.blue.yml+docker-compose.green.yml -
docker-compose.infra.yml(Postgres + Redis โ shared, persistent)
VPS setup
- Install Docker, Nginx, Certbot on VPS
- Set up shared Docker network (
frontpics-net) - Configure Nginx with TLS (Let's Encrypt)
- Deploy script (
deploy.sh) + rollback script (rollback.sh) - First manual deploy, verify blue/green switch works
- Wire GitHub Actions to VPS SSH (secrets configured)
- Automated
pg_dumpbackup cron โ R2 backup bucket
Testing & polish
- Vitest unit tests for: credit purchase transaction, upload confirmation logic, search query builder
- Supertest integration tests for: auth flow, purchase flow, presigned URL generation
- Playwright E2E: register โ upload โ admin approve โ buyer purchase โ download
- Error handling review: global exception filter, meaningful error codes
- API response time check: all GET routes < 200ms for p95
- Lighthouse score: buyer browse page โฅ 80
Documentation
-
README.mdโ project overview, local dev setup instructions -
.env.exampleโ all required variables with descriptions -
CONTRIBUTING.mdโ PR process, coding standards
Phase 2 โ Marketplace Growthโ
Goal: Better discovery, payment infrastructure, and photographer tools.
Target duration: 6โ8 weeks.
Discovery & UXโ
- Map view โ PostGIS bounding box API + MapLibre GL + Supercluster client clustering
- Advanced filters โ filter by neighborhood, altitude (drone altitude from EXIF), shot date range
- Related images โ "More from this photographer", "More in this area"
- Image collections โ buyers can save images to private wishlists
- URL slugs for images (
/images/aerial-view-austin-tx-dronevia slug field)
Paymentsโ
- Stripe integration โ fiat-to-credits purchase (Stripe Checkout or Elements)
- Stripe webhooks for payment confirmation โ credit top-up
- Credit price configurable via admin settings table
- Invoice generation per order (PDF via
pdfmakeor Puppeteer)
Communicationโ
- Transactional email โ welcome, email verification, password reset, moderation result, payout processed
- Nodemailer templates with React Email or MJML
Authโ
- Google OAuth โ
passport-google-oauth20, link to existing account by email - Account linking page (for users who registered with email via Google)
Photographer Toolsโ
- Analytics dashboard: views/downloads per image over time (Chart.js)
- Image performance table (sorted by downloads, revenue)
- Batch metadata edit (edit tags/price across selected images)
- Custom watermark upload (replace platform watermark with own logo)
Adminโ
- Platform settings screen (credit price, max upload batch size, watermark config)
- Photographer verification workflow (submit ID โ admin mark verified)
- CSV export of orders, users, transactions
Phase 3 โ Scale & Ecosystemโ
Goal: Handle significant traffic, add ecosystem features, prepare for open-source community.
Target duration: Ongoing.
Search & Discoveryโ
- OpenSearch cluster for full-text search (replace PostgreSQL FTS at scale)
- Synonym support, spell correction, fuzzy matching
- Faceted search with aggregation counts (sidebar filters with numbers)
- Elasticsearch/OpenSearch index kept in sync via Prisma event hooks or CDC
Performance & Scaleโ
- PgBouncer connection pooling (or switch to managed RDS)
- Redis Cluster or Upstash for high-availability cache/queue
- CDN purge API integration (Cloudflare Cache API) on image approve/delete
- API horizontal scaling (multiple replicas behind Nginx upstream)
- Nginx upstream health checks and automatic removal of unhealthy replicas
Marketplace Featuresโ
- Bulk ZIP download (order items โ async job โ send email download link)
- Photographer subscription tiers (higher commission rate, featured placement)
- Exclusive licenses (image removed from marketplace after exclusive purchase)
- Image licensing history page (public โ shows license grants without buyer names)
- Referral program (photographer invites โ bonus credits)
API & Ecosystemโ
- Public REST API with API key authentication (for partner integrations)
- Rate limiting per API key (Redis-based sliding window)
- Webhook delivery to photographer endpoints on key events (image sold)
- OpenAPI / Swagger documentation (
@nestjs/swagger) - Mobile app readiness (review API design for React Native client)
Operationsโ
- Structured logging pipeline (Pino โ Loki โ Grafana)
- Metrics (Prometheus + Grafana) โ API response times, queue depth, error rates
- Alerting (PagerDuty or simple email on queue backup > 100 jobs)
- Ansible playbook for VPS provisioning (reproducible setup)
- Multi-language support (i18n via
next-intl)
Decision Logโ
Track decisions that are easy to reverse vs. hard to reverse:
| Decision | Reversibility | Notes |
|---|---|---|
| PostgreSQL FTS over OpenSearch | Easy โ same API contract | Migrate anytime by swapping service implementation |
| R2 over S3 | Medium โ S3-compatible API; key names may need adjustment | Object keys and SDK calls are compatible |
| NestJS over Express | Hard | Core framework choice. Not worth changing after phase 1. |
| Monorepo vs. polyrepo | Hard | Monorepo is the right choice; don't second-guess it. |
| Credits over direct payments | Medium โ Stripe can be added alongside | Credits simplify MVP; Stripe added in Phase 2 |
| Single-region VPS | Easy โ add more VPS nodes behind Nginx | Scale by adding replicas to the Nginx upstream |
| Blue/green on same VPS | Easy โ move to Kubernetes if needed | K8s is overkill for MVP; add when team size justifies it |
Definition of Done โ Phase 1โ
Phase 1 is done when:
- A photographer can register, verify email, upload 10 images in a batch, and see them appear in the moderation queue.
- An admin can log into
/admin, review and approve images, see them appear in/browse. - A buyer can search for "aerial Austin", find a drone image, purchase it with credits, and download the original.
- All three flows work end-to-end in the production environment (not just local).
- CI pipeline passes on every commit to
main. - A push to
mainautomatically deploys to production green/blue stack with zero downtime. - The
rollback.shscript restores the previous version in under 30 seconds. - The system handles 100 concurrent users with all API P95 response times under 500ms.
- No OWASP Top 10 vulnerabilities in the auth and purchase flows (manual review + basic automated scan).