mirror of
https://github.com/andwati/cymelle.git
synced 2026-11-05 16:32:16 +00:00
454 lines
12 KiB
Markdown
454 lines
12 KiB
Markdown
# Cymelle
|
|
|
|
Cymelle is a commerce, delivery, and ride operations application. Customers can browse products, place orders, request delivery rides, and complete a simulated checkout. Admins manage products, inventory, orders, users, and rides. Drivers accept and complete delivery rides.
|
|
|
|
## Architecture
|
|
|
|
The current system is a three-tier application: a React frontend, a Spring Boot API, and PostgreSQL.
|
|
|
|
```text
|
|
+-----------------------------+
|
|
| Browser |
|
|
| Customer / Admin / Driver |
|
|
+-------------+---------------+
|
|
|
|
|
| HTTP
|
|
| http://localhost:3000
|
|
v
|
|
+-----------------------------+
|
|
| orders-frontend |
|
|
| React + TypeScript |
|
|
| TanStack Router + Query |
|
|
| Vite dev server |
|
|
+-------------+---------------+
|
|
|
|
|
| REST API
|
|
| VITE_API_BASE_URL
|
|
| http://localhost:8080/api
|
|
v
|
|
+-----------------------------+
|
|
| orders-backend |
|
|
| Spring Boot REST API |
|
|
| Security + Services + JPA |
|
|
| Swagger/OpenAPI |
|
|
+--------+--------------------+
|
|
|
|
|
| JDBC
|
|
v
|
|
+----------------+
|
|
| PostgreSQL |
|
|
| app data |
|
|
+----------------+
|
|
```
|
|
|
|
Local Docker Compose runs the same shape with three services:
|
|
|
|
```text
|
|
+------------------------------------------------------------+
|
|
| Docker Compose project: cymelle |
|
|
| |
|
|
| web: node:24 api: temurin 17 |
|
|
| pnpm dev --host 0.0.0.0 ./gradlew bootRun |
|
|
| localhost:3000 -------------> localhost:8080 |
|
|
| | | |
|
|
| v v |
|
|
| orders-frontend/ db: postgres:18 |
|
|
| bind mount localhost:5432 |
|
|
| | |
|
|
| v |
|
|
| postgres-data volume |
|
|
+------------------------------------------------------------+
|
|
```
|
|
|
|
## Project Layout
|
|
|
|
- `orders-frontend/`: React web app, route files, API clients, hooks, shared UI, dashboard and storefront screens.
|
|
- `orders-backend/`: Spring Boot API, controllers, services, repositories, DTOs, entities, security, OpenAPI, and seed data.
|
|
- `compose.yml`: Local development stack for Postgres, API, and web.
|
|
- `.env.example`: Local environment variables consumed by Docker Compose.
|
|
- `render.yaml`: Render Blueprint for Docker-backed deployment.
|
|
- `DEPLOYMENT.md`: Render and Neon deployment notes.
|
|
- `PART_2.md`: Real-time location tracking backend design walkthrough.
|
|
|
|
## Tech Stack
|
|
|
|
### Frontend
|
|
|
|
- React 19
|
|
- TypeScript
|
|
- Vite
|
|
- TanStack Router for file-based routing
|
|
- TanStack Query for server state
|
|
- TanStack Table and TanStack Form
|
|
- Tailwind CSS 4
|
|
- Radix UI, local UI components, Lucide React, and Tabler Icons
|
|
- pnpm
|
|
- Biome for linting and formatting
|
|
- Vitest for tests
|
|
- Nitro output for production server builds
|
|
|
|
### Backend
|
|
|
|
- Java 17
|
|
- Spring Boot 3.5
|
|
- Spring Web
|
|
- Spring Security
|
|
- Spring Data JPA
|
|
- Bean Validation
|
|
- PostgreSQL driver
|
|
- H2 test dependency
|
|
- Springdoc OpenAPI / Swagger UI
|
|
- Gradle Kotlin DSL
|
|
|
|
### Infrastructure
|
|
|
|
- Docker Compose for local development
|
|
- PostgreSQL 18 Alpine locally
|
|
- Render Docker web services for deployment
|
|
- Neon PostgreSQL for deployed database
|
|
- Simulated checkout for local and deployed testing
|
|
|
|
## Domain Model
|
|
|
|
```text
|
|
AppUser
|
|
id, username, displayName, passwordHash, role, enabled
|
|
role: ADMIN | CUSTOMER | DRIVER
|
|
|
|
|
+--> CUSTOMER owns Orders and requested Rides
|
|
+--> DRIVER can accept and complete Rides
|
|
+--> ADMIN manages products, users, orders, inventory, and rides
|
|
|
|
Product
|
|
id, name, sku, price, currency, active
|
|
|
|
|
+-- one-to-one --> InventoryItem
|
|
|
|
|
+-- many-to-one from --> OrderItem
|
|
|
|
InventoryItem
|
|
id, product, availableQuantity, reservedQuantity, reorderLevel
|
|
status: IN_STOCK | LOW_STOCK | OUT_OF_STOCK
|
|
|
|
Order
|
|
id, customerName, customer, status, totalAmount, currency, createdAt
|
|
status: PENDING | SHIPPED | DELIVERED | CANCELLED
|
|
|
|
|
+-- one-to-many --> OrderItem
|
|
|
|
|
+-- optional delivery association --> Ride
|
|
|
|
|
+-- one-to-one --> Payment
|
|
|
|
OrderItem
|
|
id, order, product, productName, quantity, unitPrice, lineTotal
|
|
stores product name and price snapshot at checkout time
|
|
|
|
Ride
|
|
id, customer, driver, order, pickupLocation, dropoffLocation
|
|
distanceKm, fareAmount, currency, status
|
|
status: REQUESTED | ACCEPTED | COMPLETED | CANCELLED
|
|
|
|
Payment
|
|
id, order, ride, reference, productSubtotal, rideFare
|
|
amountMajor, amountSubunits, currency, status
|
|
status: PENDING | PAID | FAILED | ABANDONED | REFUNDED
|
|
```
|
|
|
|
## Main Application Flows
|
|
|
|
### Authentication
|
|
|
|
```text
|
|
User registers or logs in
|
|
|
|
|
v
|
|
Spring Security authenticates credentials
|
|
|
|
|
v
|
|
Frontend calls /api/auth/me
|
|
|
|
|
v
|
|
Routes and backend endpoints use the user's role
|
|
```
|
|
|
|
Roles drive most behavior:
|
|
|
|
- Customers can browse, order, pay, request rides, and view their own orders.
|
|
- Drivers can see requested rides, accept rides, and complete assigned rides.
|
|
- Admins can manage users, products, orders, inventory, and ride status.
|
|
|
|
### Product and Inventory
|
|
|
|
```text
|
|
Admin creates product
|
|
|
|
|
v
|
|
Product stored with SKU, price, currency, active flag
|
|
|
|
|
v
|
|
InventoryItem tracks available quantity and reorder level
|
|
|
|
|
v
|
|
Inventory status is derived from available quantity
|
|
```
|
|
|
|
Inventory status is computed:
|
|
|
|
- `OUT_OF_STOCK` when available quantity is `0`
|
|
- `LOW_STOCK` when available quantity is at or below the reorder level
|
|
- `IN_STOCK` otherwise
|
|
|
|
### Checkout Quote
|
|
|
|
```text
|
|
Customer submits cart + optional delivery ride
|
|
|
|
|
v
|
|
Backend validates products and stock
|
|
|
|
|
v
|
|
Backend calculates product subtotal
|
|
|
|
|
+--> optional fare calculation for delivery ride
|
|
|
|
|
v
|
|
Quote returns productSubtotal, rideFare, grandTotal, currency, expiresAt
|
|
```
|
|
|
|
The quote endpoint is useful before checkout so the customer can see the final total.
|
|
|
|
### Order Placement
|
|
|
|
```text
|
|
Customer places order
|
|
|
|
|
v
|
|
Backend loads active products
|
|
|
|
|
v
|
|
Backend locks inventory rows for update
|
|
|
|
|
+--> insufficient stock -> request fails
|
|
|
|
|
v
|
|
Inventory is deducted
|
|
|
|
|
v
|
|
Order and OrderItems are created as PENDING
|
|
|
|
|
+--> optional delivery ride is created as REQUESTED
|
|
```
|
|
|
|
Order items store `productName`, `unitPrice`, and `lineTotal`, so historical orders keep their checkout-time product snapshot even if the product changes later.
|
|
|
|
### Simulated Checkout
|
|
|
|
```text
|
|
Customer starts checkout
|
|
|
|
|
v
|
|
Backend calculates quote and creates pending order
|
|
|
|
|
v
|
|
Payment row is created with unique reference
|
|
|
|
|
v
|
|
Backend marks the local simulated payment as PAID
|
|
|
|
|
v
|
|
Frontend clears the cart and sends the customer to orders
|
|
```
|
|
|
|
No external payment provider is required for local or deployed testing.
|
|
|
|
### Order Status
|
|
|
|
```text
|
|
PENDING -> SHIPPED -> DELIVERED
|
|
|
|
|
+-> CANCELLED
|
|
```
|
|
|
|
Rules in the domain model:
|
|
|
|
- Only pending orders can be cancelled by the normal cancellation flow.
|
|
- Cancelling restores inventory quantities.
|
|
- Final orders cannot be moved to another status.
|
|
- Completing an attached ride marks the order as delivered.
|
|
|
|
### Ride Status
|
|
|
|
```text
|
|
REQUESTED -> ACCEPTED -> COMPLETED
|
|
|
|
|
+-> CANCELLED
|
|
```
|
|
|
|
Ride behavior:
|
|
|
|
- Customers request rides for their own orders.
|
|
- Drivers see requested rides plus rides assigned to them.
|
|
- Drivers accept requested rides.
|
|
- Drivers complete rides assigned to them.
|
|
- Completing a ride also marks the attached order as delivered.
|
|
- Admins can cancel or complete rides.
|
|
|
|
## API Surface
|
|
|
|
All backend endpoints are under `/api`.
|
|
|
|
| Area | Endpoints |
|
|
| --- | --- |
|
|
| Auth | `POST /auth/register`, `POST /auth/login`, `POST /auth/logout`, `GET /auth/me`, `GET /auth/csrf` |
|
|
| Products | `GET /products`, `GET /products/{id}`, `POST /products`, `PUT /products/{id}`, `DELETE /products/{id}` |
|
|
| Inventory | `GET /inventory`, `GET /inventory/low-stock` |
|
|
| Orders | `POST /orders`, `GET /orders`, `GET /orders/{id}`, `DELETE /orders/{id}`, `PATCH /orders/{id}/status` |
|
|
| Checkout | `POST /checkout/quote`, `POST /checkout/simulate` |
|
|
| Rides | `GET /rides`, `POST /rides`, `POST /rides/{id}/accept`, `POST /rides/{id}/complete`, `DELETE /rides/{id}`, `PATCH /rides/{id}/status` |
|
|
| Users | `GET /users`, `GET /users/customers`, `GET /users/drivers`, `POST /users`, `PUT /users/{id}`, `DELETE /users/{id}` |
|
|
| Fare | `GET /fare/calculate` |
|
|
| Health | `GET /health` |
|
|
|
|
Swagger UI is available locally at `http://localhost:8080/swagger-ui.html`.
|
|
|
|
## Seed Data
|
|
|
|
Local development seeds demo data when `SEED_DATA=true`.
|
|
|
|
Demo users:
|
|
|
|
| Role | Username | Password |
|
|
| --- | --- | --- |
|
|
| Admin | `admin` | `admin123` |
|
|
| Driver | `driver` | `driver123` |
|
|
| Driver | `driver2` | `driver123` |
|
|
| Driver | `driver3` | `driver123` |
|
|
| Customer | `customer` | `customer123` |
|
|
| Customer | `customer2` | `customer123` |
|
|
| Customer | `customer3` | `customer123` |
|
|
| Customer | `customer4` | `customer123` |
|
|
|
|
Seeded products include accessories and office equipment such as wireless mice, keyboards, cables, stands, webcams, headphones, monitors, docks, backpacks, power banks, drawing tablets, and chairs.
|
|
|
|
## Local Development
|
|
|
|
### Prerequisites
|
|
|
|
- Docker and Docker Compose
|
|
|
|
### Run Everything With Docker Compose
|
|
|
|
```sh
|
|
cp .env.example .env
|
|
docker compose up
|
|
```
|
|
|
|
Services:
|
|
|
|
- Web: `http://localhost:3000`
|
|
- API base URL: `http://localhost:8080/api`
|
|
- Swagger UI: `http://localhost:8080/swagger-ui.html`
|
|
- OpenAPI JSON: `http://localhost:8080/api-docs`
|
|
- Postgres: `localhost:5432`
|
|
- Default database/user/password: `cymelle` / `cymelle` / `cymelle`
|
|
|
|
Seeded test credentials are available when `SEED_DATA=true`:
|
|
|
|
| Role | Username | Password |
|
|
| --- | --- | --- |
|
|
| Admin | `admin` | `admin123` |
|
|
| Driver | `driver` | `driver123` |
|
|
| Customer | `customer` | `customer123` |
|
|
|
|
Useful commands:
|
|
|
|
```sh
|
|
docker compose logs -f api
|
|
docker compose logs -f web
|
|
docker compose restart api
|
|
docker compose down
|
|
docker compose down -v
|
|
```
|
|
|
|
The web service mounts `orders-frontend/`, so frontend edits reload through Vite. Restart the `api` service after backend code changes. Use `docker compose down -v` only when you want to delete local Postgres data and dependency cache volumes.
|
|
|
|
### Environment Variables
|
|
|
|
Copy `.env.example` to `.env`, then adjust values as needed.
|
|
|
|
```sh
|
|
POSTGRES_DB=cymelle
|
|
POSTGRES_USER=cymelle
|
|
POSTGRES_PASSWORD=cymelle
|
|
POSTGRES_PORT=5432
|
|
|
|
API_PORT=8080
|
|
WEB_PORT=3000
|
|
VITE_API_BASE_URL=http://localhost:8080/api
|
|
APP_CORS_ALLOWED_ORIGINS=http://localhost:3000
|
|
SEED_DATA=true
|
|
|
|
LOW_STOCK_THRESHOLD=10
|
|
|
|
FARE_BASE_FARE=100.00
|
|
FARE_PER_KM_RATE=50.00
|
|
FARE_MINIMUM_FARE=150.00
|
|
FARE_DEFAULT_SURGE_MULTIPLIER=1.0
|
|
FARE_CURRENCY=KES
|
|
```
|
|
|
|
### Run Services Manually
|
|
|
|
Docker Compose is recommended, but the services can run separately.
|
|
|
|
Backend:
|
|
|
|
```sh
|
|
cd orders-backend
|
|
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/cymelle \
|
|
SPRING_DATASOURCE_USERNAME=cymelle \
|
|
SPRING_DATASOURCE_PASSWORD=cymelle \
|
|
APP_CORS_ALLOWED_ORIGINS=http://localhost:3000 \
|
|
./gradlew bootRun
|
|
```
|
|
|
|
Frontend:
|
|
|
|
```sh
|
|
cd orders-frontend
|
|
corepack enable
|
|
pnpm install
|
|
VITE_API_BASE_URL=http://localhost:8080/api pnpm dev
|
|
```
|
|
|
|
## Development Checks
|
|
|
|
Backend tests:
|
|
|
|
```sh
|
|
cd orders-backend
|
|
./gradlew test
|
|
```
|
|
|
|
Frontend checks:
|
|
|
|
```sh
|
|
cd orders-frontend
|
|
pnpm test
|
|
pnpm lint
|
|
pnpm check
|
|
```
|
|
|
|
## Deployment
|
|
|
|
See [DEPLOYMENT.md](./DEPLOYMENT.md) for Render deployment steps. The deployed setup uses:
|
|
|
|
- `cymelle-api`: Spring Boot API Docker service
|
|
- `cymelle-web`: TanStack/Nitro frontend Docker service
|
|
- Neon PostgreSQL through `DATABASE_URL`
|
|
|
|
## Real-Time Location Design
|
|
|
|
See [PART_2.md](./PART_2.md) for the proposed backend architecture for real-time location updates across delivery and ride trips.
|