docs: document setup and deployment
This commit is contained in:
319
README.md
319
README.md
@@ -1,54 +1,309 @@
|
||||
# ZarinPal Merchant Analytics — Backend
|
||||
|
||||
Go backend for merchant identity, dataset ingestion, deterministic analytics, authenticated APIs, and the bounded AvalAI-assisted growth analyst.
|
||||
Go backend for merchant authentication, one-time payment-data ingestion, deterministic analytics, Redis-coordinated background jobs, and optional AI-assisted insights.
|
||||
|
||||
Current state: the local MVP implements database migration, streaming dataset import,
|
||||
secure demo identity, deterministic dashboard analytics, merchant-scoped details/CSV,
|
||||
the authenticated HTTP API, and a Redis-connected worker boundary.
|
||||
## What runs in this repository
|
||||
|
||||
## Local development
|
||||
| Command | Purpose | Run mode |
|
||||
| ------------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------- |
|
||||
| `go run ./cmd/migrate` | Apply versioned PostgreSQL migrations | One-off, before the first start and after updates |
|
||||
| `go run ./cmd/importer` | Import the immutable `challenge_data.csv.gz` dataset | One-off bootstrap/recovery command |
|
||||
| `go run ./cmd/admin` | Create or reset the demo login from `DEMO_USERNAME` and `DEMO_PASSWORD` | One-off, after importing data |
|
||||
| `go run ./cmd/api` | Start the HTTP API on `HTTP_ADDR` | Long-running |
|
||||
| `go run ./cmd/worker` | Process queued growth reports, page insights, and growth-coach jobs | Long-running alongside the API |
|
||||
| `go run ./cmd/scheduler` | Enqueue due growth-coach work when `AI_COACH_ENABLED=true` | Optional long-running process |
|
||||
| `go run ./cmd/calendar --help` | Import, approve, or activate reviewed calendar catalogs | Administrative/optional |
|
||||
| `go run ./cmd/admin-reset-ai` | Reset AI-related administrative state | Administrative/recovery only |
|
||||
|
||||
Copy `.env.example` to the ignored `.env`, load its variables in your shell, and run:
|
||||
The importer is checksum-idempotent: importing the same archive again is a safe no-op. It is not a file watcher or scheduled ETL process. The worker does not replace the API; both processes should run at the same time.
|
||||
|
||||
```text
|
||||
go run ./cmd/migrate
|
||||
go run ./cmd/importer
|
||||
go run ./cmd/admin
|
||||
go run ./cmd/worker
|
||||
go run ./cmd/api
|
||||
## Local setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.24 or newer
|
||||
- PostgreSQL 18 (a compatible recent PostgreSQL release may also work)
|
||||
- Redis 7.4
|
||||
- Git
|
||||
- The source dataset as a gzip-compressed CSV
|
||||
|
||||
PostgreSQL stores application and analytics data. Redis is disposable coordination/cache state used by the API and worker. Both services must be reachable before starting the backend.
|
||||
|
||||
### 1. Start PostgreSQL and Redis
|
||||
|
||||
Use locally installed services, or start development-only containers:
|
||||
|
||||
```bash
|
||||
docker run --name zpa-postgres \
|
||||
-e POSTGRES_USER=postgres \
|
||||
-e POSTGRES_PASSWORD=change-me \
|
||||
-e POSTGRES_DB=zarinpal \
|
||||
-p 127.0.0.1:5432:5432 \
|
||||
-d postgres:18.4-alpine3.24
|
||||
|
||||
docker run --name zpa-redis \
|
||||
-p 127.0.0.1:6379:6379 \
|
||||
-d redis:7.4.10-alpine3.21 redis-server --appendonly yes
|
||||
```
|
||||
|
||||
`cmd/admin` idempotently creates or resets `DEMO_USERNAME` using `DEMO_PASSWORD`
|
||||
and assigns it to the imported merchant with the largest session population. It
|
||||
never prints the password or stores it outside the Argon2id hash. The local API
|
||||
uses the `zpa_session` HttpOnly cookie when `COOKIE_SECURE=false`; secure
|
||||
deployments use the contract's `__Host-zpa_session` cookie.
|
||||
On later runs, restart them with:
|
||||
|
||||
## Local dataset input
|
||||
```bash
|
||||
docker start zpa-postgres zpa-redis
|
||||
```
|
||||
|
||||
Place the provided archive at:
|
||||
These ports bind to localhost only. Do not expose PostgreSQL or Redis directly to the internet.
|
||||
|
||||
### 2. Configure the backend
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
PowerShell equivalent:
|
||||
|
||||
```powershell
|
||||
Copy-Item .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` and replace all placeholder values. For the documented local demo login, use:
|
||||
|
||||
```dotenv
|
||||
DATABASE_URL=postgresql://postgres:change-me@localhost:5432/zarinpal?sslmode=disable
|
||||
REDIS_ADDR=localhost:6379
|
||||
REDIS_PASSWORD=
|
||||
DEMO_USERNAME=merchant-0001
|
||||
DEMO_PASSWORD=merchant-demo-2026
|
||||
SESSION_SECRET=<at-least-32-random-characters>
|
||||
CURSOR_SECRET=<a-different-at-least-32-character-secret>
|
||||
FRONTEND_ORIGIN=http://127.0.0.1:5173
|
||||
COOKIE_SECURE=false
|
||||
```
|
||||
|
||||
Generate independent secrets, for example with `openssl rand -hex 32`. The Go commands read process environment variables; they do not automatically load `.env`.
|
||||
|
||||
Load `.env` in Bash before running commands:
|
||||
|
||||
```bash
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
```
|
||||
|
||||
For PowerShell, load the file into the current terminal:
|
||||
|
||||
```powershell
|
||||
Get-Content .env | ForEach-Object {
|
||||
if ($_ -match '^\s*([^#][^=]*)=(.*)$') {
|
||||
[Environment]::SetEnvironmentVariable($matches[1].Trim(), $matches[2], 'Process')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Keep `.env` private and uncommitted. AI features are optional and disabled in the example. If enabled, the provider key belongs only in the backend environment—never in the frontend.
|
||||
|
||||
### 3. Add the dataset
|
||||
|
||||
Place the archive at the configured `IMPORT_PATH`, which defaults to:
|
||||
|
||||
```text
|
||||
data/incoming/challenge_data.csv.gz
|
||||
```
|
||||
|
||||
The entire `data/incoming/` payload is ignored by Git. The one-time bootstrap importer will read from this location (or an explicitly supplied path), stream the compressed CSV, validate it, and write an import manifest containing the source checksum, schema version, row counts, quality results, and importer revision. It is an administrative bootstrap/recovery command, not a scheduled ingestion job. The hackathon MVP does not mutate payment facts after this import; any future ZarinPal live-write integration is outside this project's current scope. The raw file and resulting PostgreSQL data are not committed.
|
||||
The dataset directory is Git-ignored. Do not commit the archive, database dumps, import output, or credentials.
|
||||
|
||||
The proposed first-server Docker flow and Bash entrypoint are specified in [`docs/IMPORT-WORKFLOW-SPEC.md`](docs/IMPORT-WORKFLOW-SPEC.md). Implementation is authorized and must follow the dependency order in [`tasks/todo.md`](tasks/todo.md).
|
||||
### 4. Prepare the database and demo user
|
||||
|
||||
## Planned runnable boundaries
|
||||
Run these commands from the repository root and in this order:
|
||||
|
||||
One repository may expose several small `cmd/` entry points while sharing domain packages under `internal/`:
|
||||
```bash
|
||||
go mod download
|
||||
go run ./cmd/migrate
|
||||
go run ./cmd/importer
|
||||
go run ./cmd/admin
|
||||
```
|
||||
|
||||
- HTTP API
|
||||
- dataset importer
|
||||
- database migration command
|
||||
- HTTP API with authenticated SSE analytics delivery
|
||||
- on-demand analytics worker that calculates dashboard sections in parallel and caches versioned results in Redis
|
||||
`cmd/admin` idempotently creates or resets the configured demo user and attaches it to the imported merchant with the largest session population. Passwords are stored as Argon2id hashes and are not printed by the command.
|
||||
|
||||
The approved package-layout and manual-constructor decisions are implemented through the scaffold and provider tasks.
|
||||
Local demo credentials:
|
||||
|
||||
## Merchant API contract
|
||||
```text
|
||||
Username: merchant-0001
|
||||
Password: merchant-demo-2026
|
||||
```
|
||||
|
||||
- [`docs/API-SPEC.md`](docs/API-SPEC.md) defines the reviewable MVP capability, security boundaries, SSE recovery, detail pagination, and CSV-export policy.
|
||||
- [`docs/openapi.yaml`](docs/openapi.yaml) is the approved OpenAPI 3.1 interface source of truth for generated frontend types.
|
||||
These credentials work only when `.env` contains the same values before `cmd/admin` runs. They are public development credentials; change them for every internet-accessible deployment.
|
||||
|
||||
### 5. Start the worker and API
|
||||
|
||||
Open two terminals, load `.env` in both, and run:
|
||||
|
||||
```bash
|
||||
# Terminal 1: background jobs
|
||||
go run ./cmd/worker
|
||||
|
||||
# Terminal 2: HTTP API
|
||||
go run ./cmd/api
|
||||
```
|
||||
|
||||
The API is available at `http://127.0.0.1:8080`. Start the frontend separately at `http://127.0.0.1:5173`; its Vite server proxies `/api` to this API.
|
||||
|
||||
If the growth-coach scheduler is enabled, run it in a third terminal:
|
||||
|
||||
```bash
|
||||
go run ./cmd/scheduler
|
||||
```
|
||||
|
||||
## Verification and common commands
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8080/healthz
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go build ./cmd/...
|
||||
```
|
||||
|
||||
The equivalent Make targets are `make migrate`, `make import`, `make seed`, `make worker`, `make api`, `make test`, `make vet`, and `make build`.
|
||||
|
||||
## Production deployment on a remote server
|
||||
|
||||
Deploy the backend and frontend together through the `zarinpal-analytics-deployment` repository. Its Compose stack runs PostgreSQL, Redis, the API, worker, one-off tools, and the static frontend. A separate shared Caddy instance owns ports 80/443, TLS, and domain routing.
|
||||
|
||||
The examples below use `analytics.example.com`; replace it with your domain. Replace repository URL placeholders with your own template/fork URLs.
|
||||
|
||||
### 1. Prepare the server and DNS access
|
||||
|
||||
1. Provision an Ubuntu server with a public IP and enough disk/RAM for the dataset and PostgreSQL.
|
||||
2. Install Git, Docker Engine, and the Docker Compose plugin.
|
||||
3. Allow SSH only from trusted addresses and allow inbound TCP 80 and 443.
|
||||
4. Do not publish PostgreSQL port 5432 or Redis port 6379.
|
||||
|
||||
### 2. Install the shared Caddy router
|
||||
|
||||
Use the [Caddy deployment repository](https://git.amiirkhl.ir/Interanet/caddy-deployment.git) to install the main router:
|
||||
|
||||
```bash
|
||||
git clone https://git.amiirkhl.ir/Interanet/caddy-deployment.git ~/caddy-deployment
|
||||
cd ~/caddy-deployment
|
||||
```
|
||||
|
||||
Follow that repository's README to start Caddy. Ensure its Compose configuration creates/uses an external Docker network named `caddy_proxy` and attaches the Caddy container to it. If the repository does not create the network, create it once with `docker network create caddy_proxy` before starting Caddy.
|
||||
|
||||
### 3. Clone the three application repositories
|
||||
|
||||
The deployment script expects the backend and frontend as independent nested checkouts:
|
||||
|
||||
```bash
|
||||
git clone <deployment-repository-url> ~/zarinpal-analytics-deployment
|
||||
cd ~/zarinpal-analytics-deployment
|
||||
git clone <backend-repository-url> backend
|
||||
git clone <frontend-repository-url> frontend
|
||||
```
|
||||
|
||||
All three repositories must be on a clean `main` branch matching `origin/main`.
|
||||
|
||||
### 4. Create production environment files
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
cp backend/.env.production.example backend/.env
|
||||
cp frontend/.env.production.example frontend/.env
|
||||
chmod 600 .env backend/.env frontend/.env
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
- Replace every `change-me` value.
|
||||
- Set `FRONTEND_ORIGIN=https://analytics.example.com` in `backend/.env`.
|
||||
- Keep `COOKIE_SECURE=true`.
|
||||
- Generate different PostgreSQL, Redis, session, and cursor secrets with `openssl rand -hex 32`.
|
||||
- Set a new production `DEMO_PASSWORD`; do not reuse `merchant-demo-2026` on a public server.
|
||||
- Leave AI flags off unless a backend-only provider key, model, and positive budgets are configured.
|
||||
- Put no secret in `frontend/.env`; all `PUBLIC_*` values are embedded in the browser build.
|
||||
|
||||
### 5. Place the import archive
|
||||
|
||||
Copy the immutable archive to a private host path and make `IMPORT_FILE` in the deployment `.env` point to it:
|
||||
|
||||
```bash
|
||||
sudo install -d -m 750 -o "$USER" -g "$USER" /srv/zarinpal-data
|
||||
cp /path/to/challenge_data.csv.gz /srv/zarinpal-data/challenge_data.csv.gz
|
||||
chmod 440 /srv/zarinpal-data/challenge_data.csv.gz
|
||||
```
|
||||
|
||||
### 6. Perform the first application start
|
||||
|
||||
From `~/zarinpal-analytics-deployment`:
|
||||
|
||||
```bash
|
||||
docker compose config --quiet
|
||||
docker compose build backend frontend
|
||||
docker compose up -d postgres redis
|
||||
docker compose --profile tools run --rm migrate
|
||||
docker compose --profile import run --rm importer
|
||||
docker compose --profile tools run --rm admin
|
||||
docker compose up -d backend worker frontend
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Wait until `postgres`, `redis`, `backend`, `worker`, and `frontend` are healthy. Inspect failures with:
|
||||
|
||||
```bash
|
||||
docker compose logs --tail=200 postgres redis backend worker frontend
|
||||
```
|
||||
|
||||
### 7. Connect the domain through Caddy
|
||||
|
||||
Create an `A` record for `analytics.example.com` pointing to the server's public IPv4 address. Add an `AAAA` record only when IPv6 is configured and allowed through the firewall.
|
||||
|
||||
Add a site to the Caddy deployment's `Caddyfile` (or its equivalent templated configuration):
|
||||
|
||||
```caddyfile
|
||||
analytics.example.com {
|
||||
@backend path /api/* /healthz
|
||||
|
||||
handle @backend {
|
||||
reverse_proxy zarinpal-backend:8080 {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy zarinpal-frontend:8080
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Validate and reload Caddy using the commands documented by the Caddy repository. Caddy will obtain and renew HTTPS certificates automatically after public DNS resolves and ports 80/443 reach the server.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
curl -I https://analytics.example.com/
|
||||
curl https://analytics.example.com/healthz
|
||||
```
|
||||
|
||||
Then sign in with the production values of `DEMO_USERNAME` and `DEMO_PASSWORD` and verify that dashboard requests complete and the worker logs show no repeated failures.
|
||||
|
||||
### 8. Deploy later updates
|
||||
|
||||
```bash
|
||||
cd ~/zarinpal-analytics-deployment
|
||||
git pull --ff-only origin main
|
||||
git -C backend pull --ff-only origin main
|
||||
git -C frontend pull --ff-only origin main
|
||||
bash scripts/deploy.sh
|
||||
```
|
||||
|
||||
Normal updates apply migrations and restart the application, but do not rerun the importer or reset the demo password. Run those one-off profiles explicitly only when intended.
|
||||
|
||||
Never use `docker compose down -v` in production: `-v` deletes the PostgreSQL and Redis volumes. Back up PostgreSQL and rehearse restoration before treating the deployment as production-ready.
|
||||
|
||||
## API and architecture documentation
|
||||
|
||||
- [`docs/openapi.yaml`](docs/openapi.yaml) is the OpenAPI 3.1 contract used to generate frontend types.
|
||||
- [`docs/API-SPEC.md`](docs/API-SPEC.md) documents authentication, tenant isolation, SSE, pagination, and exports.
|
||||
- [`docs/IMPORT-WORKFLOW-SPEC.md`](docs/IMPORT-WORKFLOW-SPEC.md) documents the import boundary.
|
||||
- [`docs/METRIC-CATALOGUE.md`](docs/METRIC-CATALOGUE.md) defines analytics metrics.
|
||||
|
||||
Reference in New Issue
Block a user