From 5b27647a0acbfbb737ef2fa0c1f9270334d7be19 Mon Sep 17 00:00:00 2001 From: ali_mohaghegh Date: Tue, 25 Aug 2026 19:08:02 +0330 Subject: [PATCH] chore: add repo scaffolding (compose, scripts, gitignore, env example) --- .gitignore | 30 +++++++ README.md | 192 ++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 146 ++++++++++++++++++++++++++++++++ frontend/.env.example | 5 ++ mc.cmd | 10 +++ start.ps1 | 32 +++++++ 6 files changed, 415 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 frontend/.env.example create mode 100644 mc.cmd create mode 100644 start.ps1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b44508e --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# ---- Node / frontend ---- +node_modules/ +dist/ +*.tsbuildinfo + +# ---- Python / backend ---- +__pycache__/ +*.pyc +*.pyo +.venv/ +venv/ + +# ---- Env / secrets ---- +frontend/.env +*.local + +# ---- Local session / backups ---- +.zcode/ +v1.2 - Copy/ +inv dash/ + +# ---- Screenshots (root) ---- +*.png +*.jpg +*.jpeg +!frontend/public/icons/*.png + +# ---- OS junk ---- +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b33fa4 --- /dev/null +++ b/README.md @@ -0,0 +1,192 @@ +# 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 | `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 | `3000` | + +``` +Browser ──► frontend :3000 (nginx SPA) + │ + └── /api/* proxy ──► api :8000 (FastAPI, /api/v1) + │ + ├── postgres :5432 (procurement DB) + ├── minio :9000 (invoice/proforma files, bucket `procurement-files`) + └── casdoor :8000 (OIDC — login for the inventory panel) +``` + +--- + +## 📁 Layout + +```text +v1.3/ +├── docker-compose.yml ← all paths are relative to this folder +├── 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) +├── docs/ ← ERDs + migration plan +│ ├── erd.txt (v2.4 — old procurement schema) +│ ├── new erd.txt (v2.5 — material → supplier → category) +│ ├── NewERD.txt (v2.6 — integrated procurement + inventory) +│ └── material-supplier-category-migration-plan.md +├── legacy/ +│ └── dash-prototype/ ← shipped HTML/JS inventory dash (reference only) +├── 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`): + +- **مدیر پروژه (PM)** → lands in the inventory panel (their project) — can record + stock changes and confirm arrivals («رسید کالاهای در راه»). +- **مدیر تدارکات / ادمین** → lands in the procurement office — can manage quotes, + suppliers and projects, and open the inventory panel in the read-only («فقط خواندنی») + admin view. + +Until Casdoor is fully seeded, use «ورود آزمایشی (توسعه)» on the login page +(role + project). Casdoor (org `inventory`, app `inventory-app`) is the planned +production identity source. + +--- + +## 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 +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:3000 | +| 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` | + +Quick end-to-end check (frontend proxy → API → DB): + +```powershell +curl http://localhost:3000/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 +``` + +--- + +## 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 + +- The backend has **no authentication** (dev placeholder) — do not expose to the public internet. +- 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`. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2acffea --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,146 @@ +services: + postgres: + image: postgres:18 + container_name: procurement-postgres + + environment: + POSTGRES_DB: procurement + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + + ports: + - "5433:5432" + + volumes: + - postgres_data:/var/lib/postgresql + - ./sql/migration.sql:/docker-entrypoint-initdb.d/01-migration.sql:ro + # - ./sql/seed.sql:/docker-entrypoint-initdb.d/02-seed.sql:ro + + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d procurement"] + interval: 5s + timeout: 5s + retries: 10 + + restart: unless-stopped + + minio: + image: minio/minio:latest + container_name: procurement-minio + + command: server /data --console-address ":9001" + + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + + ports: + - "9000:9000" # S3 API + - "9001:9001" # Web console + + volumes: + - minio_data:/data + + restart: unless-stopped + + # MinIO Client (mc): waits for the server, creates the bucket, exits. + # Use for ad-hoc ops too: docker compose run --rm minio-init ... + minio-init: + image: minio/mc:latest + container_name: procurement-minio-init + + depends_on: + minio: + condition: service_started + + environment: + MINIO_ENDPOINT: minio:9000 + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + MINIO_BUCKET: procurement-files + + entrypoint: > + /bin/sh -c " + until mc alias set local http://$${MINIO_ENDPOINT} $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD}; do + echo 'waiting for minio server...'; sleep 2; + done; + mc mb --ignore-existing local/$${MINIO_BUCKET}; + echo bucket $${MINIO_BUCKET} ready; + " + + restart: "no" + + # ========================================================================== + # v2.6 — Casdoor (OIDC identity provider) + # Console: http://localhost:8080 (admin / 123) + # Application "inventory-app" (org "inventory", client inventory-app-client) + # is seeded by casdoor/seed.py — see tools/seed_casdoor.py in the repo. + # ========================================================================== + casdoor-postgres: + image: postgres:16 + container_name: procurement-casdoor-postgres + environment: + POSTGRES_DB: casdoor + POSTGRES_USER: casdoor + POSTGRES_PASSWORD: casdoor + volumes: + - casdoor_postgres_data:/var/lib/postgresql + restart: unless-stopped + + casdoor: + image: casbin/casdoor:latest + container_name: procurement-casdoor + depends_on: + - casdoor-postgres + environment: + RUNNING_IN_DOCKER: "true" + driverName: postgres + dataSourceName: "user=casdoor password=casdoor host=casdoor-postgres port=5432 sslmode=disable dbname=casdoor" + dbName: casdoor + origin: "http://localhost:8080" + ports: + - "8080:8000" + restart: unless-stopped + + api: + build: + context: ./backend + dockerfile: Dockerfile + container_name: procurement-api + + environment: + PROCUREMENT_DATABASE_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/procurement + PROCUREMENT_MINIO_ENDPOINT: minio:9000 + PROCUREMENT_MINIO_ACCESS_KEY: minioadmin + PROCUREMENT_MINIO_SECRET_KEY: minioadmin + PROCUREMENT_MINIO_BUCKET: procurement-files + PROCUREMENT_MINIO_SECURE: "false" + + ports: + - "8000:8000" + + depends_on: + postgres: + condition: service_healthy + minio-init: + condition: service_completed_successfully + + restart: unless-stopped + + frontend: + build: + context: ./frontend + container_name: procurement-frontend + + ports: + - "3000:80" + + depends_on: + - api + + restart: unless-stopped + +volumes: + postgres_data: + minio_data: + casdoor_postgres_data: diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..55b08b6 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,5 @@ +# Base URL of the backend API. +# Empty = same-origin proxy (nginx → /api/v1 in Docker). +# For local dev against a remote backend, set e.g.: +# VITE_API_BASE_URL=http://localhost:8000/api/v1 +VITE_API_BASE_URL= diff --git a/mc.cmd b/mc.cmd new file mode 100644 index 0000000..8841a76 --- /dev/null +++ b/mc.cmd @@ -0,0 +1,10 @@ +@echo off +rem MinIO Client (mc) helper — wraps the minio/mc container on the compose network. +rem Usage from the project root: +rem mc.cmd ls --recursive +rem mc.cmd ls invoices/ +rem mc.cmd admin info local +rem +rem Uses `docker compose run` so it works regardless of the folder name +rem (the container joins the compose network and talks to the `minio` service). +docker compose run --rm --entrypoint /bin/sh minio-init -c "mc alias set local http://minio:9000 minioadmin minioadmin >/dev/null 2>&1 && mc %*" diff --git a/start.ps1 b/start.ps1 new file mode 100644 index 0000000..cca184a --- /dev/null +++ b/start.ps1 @@ -0,0 +1,32 @@ +# Start the full procurement stack (postgres + api + frontend) +# Run this from the my-project folder (or double-click). +# +# powershell -ExecutionPolicy Bypass -File .\start.ps1 + +$ErrorActionPreference = "Stop" +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $here + +Write-Host "" +Write-Host "==============================================" -ForegroundColor Cyan +Write-Host " Procurement stack — starting Docker..." -ForegroundColor Cyan +Write-Host "==============================================" -ForegroundColor Cyan + +docker compose up -d --build + +if ($LASTEXITCODE -ne 0) { + Write-Host "Build/start failed. Check 'docker compose logs'." -ForegroundColor Red + exit 1 +} + +Write-Host "" +Write-Host "Waiting for services..." -ForegroundColor Yellow +Start-Sleep -Seconds 5 +docker compose ps + +Write-Host "" +Write-Host "Frontend : http://localhost:3000" -ForegroundColor Green +Write-Host "API docs : http://localhost:8000/docs" -ForegroundColor Green +Write-Host "DB : localhost:5432 (procurement / postgres / postgres)" -ForegroundColor Green +Write-Host "" +Write-Host "Stop: docker compose down | Full reset: docker compose down -v && up -d --build" -ForegroundColor DarkGray