Files
marketplace/docs/architecture.md
delta-lynx-89e8 dbbbbd26f4 Add rental system: listings, bookings, payments, payouts, reviews
Full rental marketplace with 6 categories (apartment, house, car, motorcycle, bicycle, ebike).
Booking workflow: create → confirm → pay → active → complete → payout.
Landlord dashboard, admin moderation, availability calendar, Stripe Connect payouts.
14 QA bugs found and fixed including validator schemas, API response types, HTTP methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:33:29 -08:00

18 KiB

Architecture

High-Level Overview

┌──────────────────────────────────────────────────────────────┐
│                        Client (React)                        │
│                  http://localhost:5173 (Vite)                 │
│                                                              │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  ┌────────────┐  │
│  │  Pages   │  │Components│  │  Context   │  │  API Layer │  │
│  └──────────┘  └──────────┘  └───────────┘  └─────┬──────┘  │
│                                                    │         │
└────────────────────────────────────────────────────┼─────────┘
                   REST API (HTTP)  │    WebSocket   │
                                   │   (Socket.io)  │
┌──────────────────────────────────┼─────────────────┼─────────┐
│                       Server (Express)             │         │
│                   http://localhost:3000             │         │
│                                                    │         │
│  ┌───────────┐  ┌────────────┐  ┌──────────────┐  │         │
│  │ Middleware │  │   Routes   │  │  Socket.io   │◄─┘         │
│  │ (auth,    │  │ (REST API) │  │  (real-time) │            │
│  │  upload,  │  └─────┬──────┘  └──────┬───────┘            │
│  │  validate)│        │                │                     │
│  └───────────┘        │                │                     │
│                  ┌────┴────────────────┴──────┐              │
│                  │       Prisma ORM           │              │
│                  └────────────┬───────────────┘              │
│                               │                              │
└───────────────────────────────┼──────────────────────────────┘
                                │
┌───────────────────────────────┼──────────────────────────────┐
│                    PostgreSQL 17 (Docker)                     │
│              marketplace-postgres container                   │
│              Volume: marketplace-pgdata                       │
└──────────────────────────────────────────────────────────────┘

External Services:
  ┌─────────┐    ┌──────────────┐
  │  Stripe │    │ Google Maps  │
  │ (pay)   │    │ (location)   │
  └─────────┘    └──────────────┘

Client-Server Communication

REST API

The client communicates with the server via a JSON REST API. All endpoints are prefixed with /api/.

Prefix Purpose
/api/auth Registration, login, token refresh, logout
/api/users User profiles, settings, privacy
/api/listings CRUD for buy/sell listings, search, favorites
/api/offers Create, counter, accept, decline offers
/api/chat Conversations and message history
/api/notifications Fetch and mark notifications as read
/api/payments Stripe payment intents, webhooks
/api/location Google Maps autocomplete proxy
/api/reports Content/user reporting
/api/subscriptions Seller subscription management
/api/promotions Listing promotion/boost
/api/admin Admin dashboard, user/listing management
/api/rentals Rental listing CRUD and search
/api/bookings Booking lifecycle management
/api/rental-payments Rental payment processing and webhooks
/api/payouts Landlord payout management
/api/rental-reviews Tenant/landlord reviews
/api/health Health check endpoint

Socket.io (WebSocket)

Socket.io provides the real-time communication layer. The server creates a Socket.io instance attached to the same HTTP server as Express.

Events flow:

  • The client connects with an auth token after login
  • The server authenticates the socket connection and associates it with a user
  • Real-time events are emitted for: new messages, notifications (offers, bookings, moderation), typing indicators, and online status

Database Layer

Prisma ORM

The database schema is defined in server/prisma/schema.prisma. Prisma generates a type-safe client used throughout the server routes.

Key models:

Model Purpose
User Accounts with roles, privacy settings, landlord status
Session JWT refresh token sessions
Listing Buy/sell items with category, condition, price
ListingImage Image attachments for listings
Offer Price negotiation between buyer and seller
Conversation Chat threads between two users
Message Individual chat messages
Favorite User bookmarks on listings
Notification In-app notifications (19 distinct types)
Payment Payment records (8 payment types)
Report Content/user reports for moderation
Subscription Seller tier subscriptions
PromotedListing Boosted listing placements
ModerationLog Audit trail for moderation actions
BlockedUser User blocking relationships
RentalListing Rental items (apartments, cars, bikes, etc.)
Booking Rental booking with status lifecycle
RentalReview Reviews for completed rentals
Payout Landlord payout tracking

Enums

The schema uses PostgreSQL enums extensively for type safety:

  • User roles: USER, MODERATOR, ADMIN, SUPER_ADMIN
  • Listing categories: ELECTRONICS, FURNITURE, CLOTHING, HOME_GARDEN, SPORTS, BOOKS, GAMES, VEHICLES, OTHER
  • Rental categories: APARTMENT, HOUSE, CAR, MOTORCYCLE, BICYCLE, EBIKE
  • Booking status: PENDING -> CONFIRMED -> ACTIVE -> COMPLETED (or CANCELLED_* / REJECTED / EXPIRED)
  • Cancellation policy: FLEXIBLE, MODERATE, STRICT

Authentication Flow

The application uses JWT-based authentication with access and refresh tokens.

┌────────┐                              ┌────────┐
│ Client │                              │ Server │
└───┬────┘                              └───┬────┘
    │  POST /api/auth/login                 │
    │  { email, password }                  │
    ├──────────────────────────────────────►│
    │                                       │ Verify password (bcryptjs)
    │                                       │ Generate access token (short-lived)
    │                                       │ Generate refresh token (long-lived)
    │                                       │ Store session in DB
    │  { accessToken, user }                │
    │  Set-Cookie: refreshToken (httpOnly)  │
    │◄──────────────────────────────────────┤
    │                                       │
    │  GET /api/listings                    │
    │  Authorization: Bearer <accessToken>  │
    ├──────────────────────────────────────►│
    │                                       │ auth middleware verifies JWT
    │                                       │ checkBanned middleware
    │  { listings: [...] }                  │
    │◄──────────────────────────────────────┤
    │                                       │
    │  POST /api/auth/refresh               │
    │  Cookie: refreshToken                 │
    ├──────────────────────────────────────►│
    │                                       │ Validate refresh token
    │                                       │ Issue new access token
    │  { accessToken }                      │
    │◄──────────────────────────────────────┤

Middleware Chain

Requests pass through several middleware layers:

  1. helmet -- Security headers
  2. cors -- Restricts origins to CLIENT_URL
  3. cookieParser -- Parses refresh token cookies
  4. express-rate-limit -- Rate limits auth endpoints (20 requests / 15 min)
  5. auth -- Verifies JWT access token, attaches user to request
  6. checkBanned -- Blocks banned users
  7. requireRole -- Role-based access control (e.g., admin-only routes)
  8. upload -- Multer file upload handling
  9. validate -- Zod schema validation for request bodies

Payment Flow

Marketplace Payments (Listings)

Stripe PaymentIntents are used for buy/sell transactions:

┌────────┐       ┌────────┐       ┌────────┐
│ Buyer  │       │ Server │       │ Stripe │
└───┬────┘       └───┬────┘       └───┬────┘
    │  Create PI     │                │
    ├───────────────►│ POST /api/     │
    │                │ payments       │
    │                ├───────────────►│ stripe.paymentIntents.create()
    │                │  clientSecret  │
    │                │◄───────────────┤
    │  clientSecret  │                │
    │◄───────────────┤                │
    │                │                │
    │  Confirm (Stripe.js)           │
    ├────────────────────────────────►│
    │                │                │
    │                │  Webhook event │
    │                │◄───────────────┤ payment_intent.succeeded
    │                │  Update DB     │
    │                │  Notify seller │
    │                │                │

Rental Payments

Rental bookings follow a similar flow with additional handling for:

  • Security deposits -- Held and refunded after completion
  • Landlord payouts -- Via Stripe Connect (stripeAccountId on User model)
  • Commissions -- Platform fee deducted from rental payments
  • Separate webhook endpoint -- /api/rental-payments/webhook

Payment Types

The system tracks eight distinct payment types:

Type Description
LISTING_FEE Fee to publish a listing
COMMISSION Platform commission on sales
PROMOTION Listing boost/promotion fee
SUBSCRIPTION Seller subscription payment
RENTAL_BOOKING Tenant rental payment
RENTAL_COMMISSION Platform fee on rentals
RENTAL_DEPOSIT Security deposit hold
RENTAL_DEPOSIT_REFUND Deposit refund after completion
RENTAL_PAYOUT Payment to landlord via Connect

File Uploads

File uploads are handled by Multer with local disk storage.

  • Configuration: server/src/middleware/upload.ts
  • Storage directory: Configurable via UPLOAD_DIR env var (default: ./uploads)
  • Serving: Static files served at /uploads/* via express.static
  • Usage: Listing images, rental listing photos, user avatars

Files are stored on the local filesystem. In production, this should be replaced with cloud storage (e.g., S3, Cloudflare R2).

Real-Time Communication

Socket.io is configured in server/src/socket/index.ts and attached to the HTTP server alongside Express.

Connection Flow

  1. Client establishes WebSocket connection after successful login
  2. Server authenticates the connection using the JWT token
  3. User is registered as "online" and associated with their socket ID

Event Types

Category Events
Chat New message, typing indicator, read receipts
Notifications New offer, offer response, booking updates, payout status, moderation actions
Presence User online/offline status

Architecture

Client A ──WebSocket──┐
                      ├──► Socket.io Server ──► Emit to Client B
Client B ──WebSocket──┘         │
                                │
                          ┌─────┴─────┐
                          │  Express   │
                          │  routes    │
                          │  can emit  │
                          │  via io    │
                          └───────────┘

Express routes access the Socket.io instance via app.get('io') to emit events from REST handlers (e.g., sending a notification when an offer is created via the REST API).

Security

Concern Implementation
HTTP headers Helmet (XSS, clickjacking, MIME sniffing)
CORS Restricted to CLIENT_URL
Rate limiting 20 req/15 min on /api/auth/login and /register
Input validation Zod schemas on all mutation endpoints
Password hashing bcryptjs
Token storage Refresh token in httpOnly cookie
Role-based access requireRole middleware for admin/mod routes
User banning checkBanned middleware on protected routes
Webhook verification Stripe webhook signature verification

Deployment Considerations

Database

  • Use a managed PostgreSQL service (e.g., AWS RDS, Supabase, Neon) in production
  • Run npx prisma migrate deploy to apply migrations
  • The Docker setup is intended for local development only

File Storage

  • Replace local Multer storage with S3-compatible storage (AWS S3, Cloudflare R2, MinIO)
  • Update the upload middleware and static file serving accordingly

Environment

  • Set strong, unique values for JWT_SECRET and JWT_REFRESH_SECRET
  • Use production Stripe keys and configure webhook endpoints
  • Set CLIENT_URL to the deployed frontend URL

Scaling

  • Horizontal scaling: Socket.io requires a Redis adapter (@socket.io/redis-adapter) for multi-instance deployments
  • Static assets: Serve the built React app via a CDN or reverse proxy (Nginx, Cloudflare)
  • API: The Express server is stateless (JWT-based auth) and can be scaled horizontally behind a load balancer
                    ┌───────────┐
                    │   CDN /   │
                    │  Nginx    │
                    └─────┬─────┘
                          │
              ┌───────────┴───────────┐
              │                       │
     ┌────────▼─────┐      ┌─────────▼────────┐
     │ React Static │      │  Express API x N  │
     │   (built)    │      │  (load balanced)  │
     └──────────────┘      └────────┬──────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    │               │               │
             ┌──────▼──┐    ┌──────▼──┐    ┌───────▼──┐
             │PostgreSQL│    │  Redis  │    │ S3 / R2  │
             │(managed) │    │(sockets)│    │ (files)  │
             └──────────┘    └─────────┘    └──────────┘