New section 'Working with the deployed system (server)': public app and Casdoor panel URLs (aramesh / casdoor .bennu-api-bahrestan.ir) and step-by-step MinIO console access via an SSH tunnel (ssh -L 9001:127.0.0.1:9001 root@171.22.25.75 -> http://localhost:9001), including the aramesh-minio credentials, the procurement-files bucket, why the console is loopback-only (port 9000 is reserved for the deploy-webhook receiver), the secret-handling warning and the tunnel teardown. Also refreshed sections that went stale this session: the login form description no longer mentions the removed optional organization field; the restore section now documents the committed admin-only seed dumps in backups/علی محقق/ (the old restore.ps1/sh scripts and demo dumps were deleted) with the current login and the still-available demo seeders.
350 lines
15 KiB
Markdown
350 lines
15 KiB
Markdown
# Procurement Stack v1.3 — Dockerized (PostgreSQL + FastAPI + React + MinIO)
|
||
|
||
Self-contained ship package: everything needed to run the stack is in this
|
||
folder — no external files or sibling folders required.
|
||
|
||
| Service | Container | Tech | Exposed port |
|
||
|---|---|---|---|
|
||
| **postgres** | `procurement-postgres` | PostgreSQL 18 (one instance: `procurement` + `casdoor` DBs) | `5433` |
|
||
| **minio** | `procurement-minio` | MinIO object storage (S3-compatible) | `9000` (API), `9001` (console) |
|
||
| **casdoor** | `procurement-casdoor` | Casdoor (OIDC identity provider) | `8080` |
|
||
| **api** | `procurement-api` | FastAPI + SQLAlchemy (asyncpg) | `8000` |
|
||
| **frontend** | `procurement-frontend` | React 19 + Vite, served by nginx (internal) | via Traefik |
|
||
| **traefik** | `procurement-traefik` | Traefik v3 — public reverse proxy (edge) | `80` (web), `443` (websecure) |
|
||
|
||
```
|
||
Browser ──► Traefik :80 (edge)
|
||
│
|
||
├── Host(localhost) ──► frontend (nginx SPA :80)
|
||
│ │
|
||
│ └── /api/* proxy ──► api :8000 (/api/v1)
|
||
│
|
||
├── Host(api.localhost) ──► api :8000
|
||
├── Host(auth.localhost) ──► casdoor :8000 (OIDC login)
|
||
└── Host(s3/storage.localhost) ──► minio (API / console)
|
||
│
|
||
├── postgres :5432 (procurement + casdoor DBs)
|
||
└── minio :9000 (invoice/proforma files)
|
||
```
|
||
|
||
---
|
||
|
||
## 📁 Layout
|
||
|
||
```text
|
||
v1.3/
|
||
├── docker-compose.yml ← all paths are relative to this folder
|
||
├── docker-compose.prod.yml ← production overlay (real domains + HTTPS via Let's Encrypt)
|
||
├── start.ps1 ← PowerShell helper (build + start + status)
|
||
├── mc.cmd ← MinIO Client helper (inspect the bucket)
|
||
├── README.md
|
||
├── sql/
|
||
│ ├── migration.sql ← DB schema v2.6 (auto-runs on first boot)
|
||
│ └── 02-casdoor-db.sql ← creates the `casdoor` role+DB on the same instance
|
||
├── infra/
|
||
│ └── traefik-dynamic.yml ← Traefik shared middlewares (HTTP→HTTPS redirect)
|
||
├── docs/ ← ERDs + migration plan + auth guide
|
||
│ ├── erd.txt (v2.4 — old procurement schema)
|
||
│ ├── new erd.txt (v2.5 — material → supplier → category)
|
||
│ ├── NewERD.txt (v2.6 — integrated procurement + inventory)
|
||
│ ├── casdoor-auth.md (v2.6 — Casdoor login, roles, failure scenarios)
|
||
│ └── material-supplier-category-migration-plan.md
|
||
├── backend/ ← build context of the `api` service
|
||
│ ├── Dockerfile
|
||
│ ├── requirements.txt
|
||
│ ├── .dockerignore
|
||
│ ├── app/ (source code — procurement + inventory + auth)
|
||
│ └── tools/ (smoke-test + seed scripts, copied into the image)
|
||
└── frontend/ ← build context of the `frontend` service
|
||
├── Dockerfile
|
||
├── nginx.conf
|
||
├── .dockerignore
|
||
├── .env.example (copy to .env if you need a custom API base URL)
|
||
├── index.html
|
||
├── package.json
|
||
├── package-lock.json
|
||
├── tsconfig*.json
|
||
├── vite.config.ts
|
||
├── public/
|
||
└── src/
|
||
└── pages/
|
||
├── Dashboard/ (procurement office)
|
||
├── Wizard/ (add new offer)
|
||
├── Suppliers/ (suppliers & topics)
|
||
├── Projects/ (projects)
|
||
├── Units/ (unit conversions)
|
||
└── Inventory/ (warehouse management panel — React port)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 Login
|
||
|
||
The whole system is behind a login page (`/login`). Login is **in-page**:
|
||
the form (username / password) is sent to
|
||
`POST /api/v1/auth/login`; the backend exchanges it with Casdoor
|
||
(password grant), verifies the returned JWT, and starts the session —
|
||
no browser redirect to Casdoor.
|
||
|
||
- **مدیر پروژه (PM)** → lands in the inventory panel (their project) — can record
|
||
stock changes, manage the project's warehouses and confirm arrivals («کالاهای در راه»).
|
||
- **مدیر تدارکات / ادمین** → lands in the procurement office — can manage
|
||
quotes, suppliers, projects, and in the inventory panel: dashboard, daily control,
|
||
«کالاهای در راه» (receiving included) and «سازماندهی» (users, PMs, warehouses).
|
||
The «انبارها» and «تغییر موجودی» sections are PM-only.
|
||
|
||
**Developer Mode** (top-left button on the login page) is a 3-step flow for
|
||
the admin (super user): ① superuser auth gate (admin credentials only) →
|
||
② role switcher (ادمین / مدیر تدارکات / مدیر پروژه + project) → ③ session
|
||
activation by role (admin → **پنل مدیریت ادمین** at `/admin`, مدیر تدارکات →
|
||
procurement office, مدیر پروژه → their project's inventory panel). It is
|
||
controlled by the `VITE_ENABLE_DEV_LOGIN` env var (in Docker: the
|
||
`DEV_LOGIN_ENABLED` compose build arg, default `true` — set it to `false`
|
||
for production builds).
|
||
|
||
| What | Where | Credentials |
|
||
|---|---|---|
|
||
| App | http://localhost (via Traefik) | — |
|
||
| Casdoor console | http://localhost:8080 | `admin` / `123` |
|
||
| Seeded app users (org `inventory`) | `razaei` (admin), `mousavi` (مدیر تدارکات), `mohaghegh` (مدیر پروژه), `tester` (مدیر پروژه، پروژه «تست») | password `123456` |
|
||
|
||
Run `backend/tools/seed_casdoor.py` once after the first boot to create the
|
||
Casdoor organization, application and users (idempotent). Full details —
|
||
addresses, credentials, role model, failure scenarios and the checklist —
|
||
are in [`docs/casdoor-auth.md`](docs/casdoor-auth.md).
|
||
|
||
> The **currently committed seed** (`backups/علی محقق/`) instead ships a
|
||
> single admin — `ali.mohaghegh` («علی محقق») — see
|
||
> [Restoring the committed seed data](#restoring-the-committed-seed-data).
|
||
|
||
---
|
||
|
||
## 🌐 Working with the deployed system (server)
|
||
|
||
The production deployment of this stack runs on the server and is reachable
|
||
at these public addresses (HTTPS):
|
||
|
||
| What | Address |
|
||
|---|---|
|
||
| **App (ERP)** | https://aramesh.bennu-api-bahrestan.ir/ |
|
||
| **Casdoor admin panel** | https://casdoor.bennu-api-bahrestan.ir/ |
|
||
|
||
Log in to the app with your Casdoor account (org `inventory`); the role is
|
||
assigned inside the app (`users.role` — see «Login» above and
|
||
[`docs/casdoor-auth.md`](docs/casdoor-auth.md)).
|
||
|
||
### MinIO console on the server (SSH tunnel)
|
||
|
||
The MinIO console is deliberately **loopback-only** on the server
|
||
(`127.0.0.1:9001`) — verified unreachable from the internet — so the way in
|
||
is an SSH tunnel from your machine:
|
||
|
||
1. Open PowerShell and start the tunnel (**keep the window open** while you
|
||
use the panel):
|
||
|
||
```powershell
|
||
ssh -L 9001:127.0.0.1:9001 root@171.22.25.75
|
||
```
|
||
|
||
You will also land in a normal SSH session — that is fine; the tunnel
|
||
lives as long as the window stays open.
|
||
|
||
2. In your browser, open: **http://localhost:9001**
|
||
|
||
3. Log in with:
|
||
|
||
| Username | Password |
|
||
|---|---|
|
||
| `aramesh-minio` | `feb10fadb5441019dfc33bf12a9b0f74e7b6c478` |
|
||
|
||
You will see the **`procurement-files`** bucket — the invoices/proformas the
|
||
ERP stores, browsable and downloadable from there.
|
||
|
||
⚠️ These credentials are **full admin access to the file storage**. Treat
|
||
them as a production secret (this README is inside the repo, so anyone with
|
||
repo access can read them — rotate them on the server if they ever leak).
|
||
|
||
Close the PowerShell window (or `Ctrl+C`) when you are done to drop the
|
||
tunnel.
|
||
|
||
Notes from the server setup:
|
||
|
||
- The panel is published only on the server's loopback, so the internet
|
||
cannot reach it at all — the tunnel is what carries it to your browser.
|
||
- Host port `9000` stays reserved for the deploy-webhook receiver, which is
|
||
why the console uses `9001`.
|
||
- If you ever need the raw S3 API from your machine, or a clickable URL for
|
||
the console (e.g. `storage.aramesh.bennu-api-bahrestan.ir` behind Traefik
|
||
with a password prompt on top), that can be arranged on request — the
|
||
tunnel remains the safer default.
|
||
|
||
---
|
||
|
||
## Prerequisites (target machine)
|
||
|
||
- **Docker Desktop** (Windows) or Docker Engine + Compose plugin (Linux/macOS)
|
||
- ~3–4 GB free disk for images (`postgres:18`, `python:3.13-slim`, `node:22-alpine`, `nginx:alpine`, `minio/minio`, `minio/mc`)
|
||
- Internet access on first build (image + `npm ci` downloads)
|
||
|
||
---
|
||
|
||
## Start the whole stack
|
||
|
||
```powershell
|
||
cd <this-folder>
|
||
docker compose up -d --build
|
||
```
|
||
|
||
First run downloads base images and builds the frontend (takes a few minutes).
|
||
Subsequent runs: `docker compose up -d`.
|
||
|
||
Wait until all services are up:
|
||
|
||
```powershell
|
||
docker compose ps
|
||
```
|
||
|
||
All should show `Up` (postgres also `(healthy)`).
|
||
|
||
---
|
||
|
||
## Verify
|
||
|
||
| What | Where |
|
||
|---|---|
|
||
| Frontend UI | http://localhost |
|
||
| API docs (Swagger) | http://localhost:8000/docs |
|
||
| API health | http://localhost:8000/health → `{"status":"ok","service":"procurement-api"}` |
|
||
| MinIO console | http://localhost:9001 (user/pass `minioadmin`/`minioadmin`) |
|
||
| Database | `localhost:5433`, db `procurement`, user/pass `postgres`/`postgres` (Casdoor uses the same instance, db `casdoor`) |
|
||
|
||
Quick end-to-end check (Traefik → frontend proxy → API → DB):
|
||
|
||
```powershell
|
||
curl http://localhost/api/v1/quotes?expand=supplier,project,line_items,termsheets,events,decision,receipt
|
||
```
|
||
|
||
---
|
||
|
||
## MinIO (object storage for invoice / proforma files)
|
||
|
||
| Item | Value |
|
||
|---|---|
|
||
| S3 API endpoint | `localhost:9000` (from the API container: `minio:9000`) |
|
||
| Web console | http://localhost:9001 |
|
||
| Root user / password | `minioadmin` / `minioadmin` (dev — change for production) |
|
||
| Bucket | `procurement-files` (created automatically by the `minio-init` container) |
|
||
| Object key layout | `{quote_id}/{kind}/{attachment_id}` where kind = `invoice` \| `proforma` |
|
||
|
||
File **bytes** live in MinIO; the `quote_attachments` table holds the metadata.
|
||
|
||
Ad-hoc bucket inspection from the host (see `mc.cmd`):
|
||
|
||
```powershell
|
||
.\mc.cmd ls --recursive
|
||
```
|
||
|
||
---
|
||
|
||
## Database credentials / connection
|
||
|
||
```text
|
||
Host: localhost
|
||
Port: 5433
|
||
Database: procurement
|
||
Username: postgres
|
||
Password: postgres
|
||
```
|
||
|
||
```powershell
|
||
docker exec -it procurement-postgres psql -U postgres -d procurement
|
||
```
|
||
|
||
---
|
||
|
||
## Restoring the committed seed data
|
||
|
||
The databases are **not** in git — Docker volumes hold the data. The repo
|
||
ships recovery dumps in `backups/علی محقق/`:
|
||
|
||
| File | Contents |
|
||
|---|---|
|
||
| `casdoor.dump` | Casdoor schema + console admin (`admin` / `123`), org `inventory`, app `inventory-app`, the JWT signing cert, and the single org user `ali.mohaghegh` |
|
||
| `procurement.dump` | Procurement schema + the single `users` row «علی محقق» with role `admin`; all other tables empty (categories/units re-seed at API startup) |
|
||
|
||
⚠️ This **replaces all data** in the `procurement` and `casdoor` databases.
|
||
On a running stack:
|
||
|
||
```powershell
|
||
docker compose stop api casdoor
|
||
docker cp "backups/علی محقق/procurement.dump" procurement-postgres:/tmp/
|
||
docker cp "backups/علی محقق/casdoor.dump" procurement-postgres:/tmp/
|
||
docker exec procurement-postgres pg_restore -U postgres -d procurement --clean --if-exists /tmp/procurement.dump
|
||
docker exec procurement-postgres pg_restore -U postgres -d casdoor --clean --if-exists /tmp/casdoor.dump
|
||
docker compose start casdoor api
|
||
```
|
||
|
||
Login afterwards: `ali.mohaghegh` / `hulkfuck002` (Casdoor rejects spaces
|
||
in usernames, so «ali mohaghegh» is not a valid login name).
|
||
|
||
Want demo data instead? Two seeders are still available (they create
|
||
different users — see the Login table above):
|
||
|
||
1. **App seeder** (realistic demo dataset; keeps the Casdoor org as-is):
|
||
```powershell
|
||
docker exec procurement-api sh -c "cd /srv/backend && python -m tools.seed"
|
||
```
|
||
2. **Casdoor seeder** (creates org `inventory` + app + users razaei /
|
||
mousavi / mohaghegh / tester — run once after a fresh volume):
|
||
```powershell
|
||
python backend/tools/seed_casdoor.py # from the host, or from the api container
|
||
```
|
||
|
||
To create a fresh dump of your own current state: stop `api` + `casdoor`,
|
||
`docker exec procurement-postgres pg_dump -U postgres -d procurement -Fc`
|
||
(+ the `casdoor` DB) into `backups/`, then start them again.
|
||
|
||
## Common operations
|
||
|
||
```powershell
|
||
docker compose logs -f api # see logs
|
||
docker compose down # stop (keep data)
|
||
docker compose up -d --build # rebuild after code changes
|
||
docker compose down -v # ⚠️ full reset — wipes DB + MinIO volumes
|
||
```
|
||
|
||
---
|
||
|
||
## Known behavior
|
||
|
||
- Authentication is Casdoor-based (JWT verified via JWKS): `/auth`, `/users`,
|
||
`/inventory` and `/pm` are protected (401 without a token, 403 without a role).
|
||
The procurement CRUD endpoints (quotes, suppliers, materials, …) are still
|
||
open — dev placeholder, do not expose to the public internet.
|
||
- Roles live in the app's `users` table (Casdoor is identity-only): `pm`,
|
||
`procurement_manager`, `admin`. Role changes are **admin-only** (RBAC);
|
||
the procurement manager can only assign projects to project managers.
|
||
The admin has a dedicated panel at `/admin` (user sync from Casdoor +
|
||
grant/revoke «مدیر تدارکات»).
|
||
- **Partial goods receipt** («رسید جزیی») is supported in «کالاهای در راه»:
|
||
at least one item with a valid quantity is enough to submit; other items
|
||
stay in transit. The procurement ledger shows «رسیده X از Y» / «در راه Z»
|
||
badges and a «رسید جزیی» badge on the quote.
|
||
- **One PostgreSQL instance** serves both the app (`procurement`) and Casdoor
|
||
(`casdoor`) — the separate casdoor-postgres container is gone (data was
|
||
dumped/restored; see `docs/casdoor-auth.md`).
|
||
- **Traefik v3** is the public edge (ports 80/443); the frontend nginx only
|
||
serves the SPA and proxies `/api` internally. Production (real domains,
|
||
HTTPS via Let's Encrypt, HTTP→HTTPS redirect):
|
||
`docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build`
|
||
(see the prod file header for the required env vars).
|
||
- Schema runs automatically ONLY on the first boot of an empty `postgres_data` volume
|
||
(see `docker-entrypoint-initdb.d`); the API also runs `CREATE TABLE IF NOT EXISTS`
|
||
for the units-dashboard tables and `quote_attachments` at startup.
|
||
- Invoice upload requires an `accepted`/`arrived` quote; proforma has no restriction.
|
||
- The supplier detail page (`/suppliers/:id`) shows the purchase history with
|
||
invoice/proforma download links from MinIO.
|
||
- If builds fail with disk-full errors: `docker system prune -a`, then rebuild.
|
||
- If the frontend shows «اتصال به سرور برقرار نشد» → the api container is not
|
||
reachable; check `docker compose ps` and `docker compose logs api`.
|