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>
333 lines
18 KiB
Markdown
333 lines
18 KiB
Markdown
# 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
|
|
|
|
### Recommended Production Stack
|
|
|
|
```
|
|
┌───────────┐
|
|
│ CDN / │
|
|
│ Nginx │
|
|
└─────┬─────┘
|
|
│
|
|
┌───────────┴───────────┐
|
|
│ │
|
|
┌────────▼─────┐ ┌─────────▼────────┐
|
|
│ React Static │ │ Express API x N │
|
|
│ (built) │ │ (load balanced) │
|
|
└──────────────┘ └────────┬──────────┘
|
|
│
|
|
┌───────────────┼───────────────┐
|
|
│ │ │
|
|
┌──────▼──┐ ┌──────▼──┐ ┌───────▼──┐
|
|
│PostgreSQL│ │ Redis │ │ S3 / R2 │
|
|
│(managed) │ │(sockets)│ │ (files) │
|
|
└──────────┘ └─────────┘ └──────────┘
|
|
```
|