Skip to main content

Architecture Overview โ€” Frontpics Image Marketplace

Frontpics is a TypeScript monorepo with a Next.js web app, NestJS API, PostgreSQL/PostGIS database, Redis/BullMQ queue, and runtime-selectable object storage.

Runtime Componentsโ€‹

Browser
|
v
Next.js web app (apps/web)
|
v
NestJS API (apps/api)
| |
v v
PostgreSQL/PostGIS Redis/BullMQ
|
v
Object storage through StorageService
r2 | s3 | local

In production, the app runs in Docker Compose blue/green stacks behind Nginx. PostgreSQL and Redis are shared infrastructure services.

Web Appโ€‹

apps/web serves all user surfaces:

  • public homepage and image marketplace;
  • buyer credits, billing, capture requests, and downloads;
  • photographer upload, jobs, images, earnings, payouts, and profile;
  • moderator moderation/upload tools;
  • payroll invoice and payout reports;
  • admin operations.

Route protection and locale routing belong in apps/web/src/proxy.ts.

The app uses:

  • Next.js App Router;
  • React client components for interactive flows;
  • TanStack Query for client server-state refresh;
  • Framer Motion for transitions;
  • Leaflet for map views;
  • centralized modal provider for image previews and docs.

APIโ€‹

apps/api is a NestJS 11 application using Fastify.

Main module groups:

  • auth and users;
  • images and upload;
  • orders, wallets, payouts, invoices;
  • shooting requests;
  • admin operations;
  • feedback and incoming emails;
  • storage;
  • queue/image processing;

All routes are under /api/v1.

JWT validation returns { id, email, role }. Services should use user.id.

Databaseโ€‹

The database package owns Prisma schema and generated client.

Important models:

  • User
  • PhotographerProfile
  • AgentProfile
  • Image
  • ImageFile
  • Location
  • Tag
  • Order / OrderItem
  • CreditWallet / CreditTransaction
  • Payout
  • ShootingRequest
  • AgencyBrand

Enums use lowercase snake_case values such as pending_review.

Storageโ€‹

Storage is abstracted behind StorageService. The active backend is set at runtime in Redis:

config:storage_backend = r2 | s3 | local

Existing files stay in their original backend. New uploads use the active backend.

Image Upload Flowโ€‹

Photographer selects files
-> web extracts EXIF/hash and resolves address/GPS
-> POST /upload/presigned
-> browser uploads directly to storage
-> POST /upload/check-duplicates
-> POST /upload/confirm with metadata
-> API creates Image/Location/Tag records
-> API queues BullMQ processing job
-> worker creates variants and ImageFile records
-> image moves to pending_review
-> admin approves to active

Purchase Flowโ€‹

Buyer selects one or more images
-> optional buyer billing reference
-> POST /orders
-> API validates wallet and duplicate purchases
-> transaction creates Order/OrderItems
-> buyer wallet is debited
-> photographer wallet is credited
-> downloads become available

The purchase UI is available on desktop and mobile through the image preview modal and grid selection bar.

Download and Branding Flowโ€‹

Buyer opens purchased image
-> GET /images/:id/download/:orderItemId
-> API returns signed download URL and agency brand
-> web optionally applies watermark and property pin
-> final image downloads in browser

Agency branding is centralized through AgencyBrand. Do not add per-agent watermark upload paths.

Shooting Request Flowโ€‹

Buyer creates capture request
-> address lookup or API geocoding stores coordinates
-> configured fixed cost is reserved from buyer credits
-> photographers see open jobs in list and map
-> photographer claims request
-> claimed photographer completes request
-> photographer wallet is credited

Completing a shooting request does not create a payout automatically.

Payout Flowโ€‹

Photographer requests payout
-> API validates bank details and minimum balance
-> Payout(status=pending) is created
-> photographer credits are deducted/reserved
-> optional photographer reference is stored on linked transaction
-> admin sees pending badge and reference
-> admin marks paid with payment reference or rejects/refunds

Operational Queuesโ€‹

Admin navigation badges are sourced from GET /admin/action-counts:

  • pending image moderation;
  • unread incoming emails;
  • open feedback;
  • pending payouts.