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:
UserPhotographerProfileAgentProfileImageImageFileLocationTagOrder/OrderItemCreditWallet/CreditTransactionPayoutShootingRequestAgencyBrand
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.