config: make domain configurable via DOMAIN env var
Replace hardcoded evosync.ru with a DOMAIN variable read from .env. nginx.conf is now generated from nginx.conf.template via envsubst; init-letsencrypt.sh reads DOMAIN from .env and fails loudly if unset. README documents the new variable and first-deploy TLS workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
59
README.md
59
README.md
@@ -55,7 +55,7 @@ Web service for syncing a product catalog from Evotor POS → VK Market. Users c
|
||||
| `sync_filters` | Store / group inclusion filters (entity_type: store / group) |
|
||||
| `cached_stores` | Cached list of Evotor stores |
|
||||
| `cached_groups` | Cached Evotor product groups |
|
||||
| `cached_products` | Cached Evotor product catalog |
|
||||
| `cached_products` | Cached Evotor products; `vk_product_id` stores the VK market item ID after first push |
|
||||
| `vk_cached_albums` | Cached VK Market albums (product groups) |
|
||||
| `vk_cached_products` | Cached VK Market products |
|
||||
| `roles` | RBAC roles |
|
||||
@@ -69,20 +69,40 @@ Web service for syncing a product catalog from Evotor POS → VK Market. Users c
|
||||
|
||||
Periodic tasks run via **Celery Beat** and are executed by the **worker** service.
|
||||
|
||||
| Task | Schedule | Description |
|
||||
|------|----------|-------------|
|
||||
| `web.tasks.catalog.refresh_catalog` | Every `CATALOG_REFRESH_INTERVAL_SECONDS` | Fetches stores, product groups, and products from the Evotor API for every connected user; upserts into `cached_stores`, `cached_groups`, `cached_products` |
|
||||
| `web.tasks.vk_catalog.refresh_vk_catalog` | Every `CATALOG_REFRESH_INTERVAL_SECONDS` | Fetches Market albums and products from VK API for every connected user; upserts into `vk_cached_albums`, `vk_cached_products` |
|
||||
Beat fires a single **sync pipeline** every `CATALOG_REFRESH_INTERVAL_SECONDS`. The three tasks run as a Celery chain — each step starts only after the previous one completes:
|
||||
|
||||
**Evotor sync sequence per user:**
|
||||
```
|
||||
run_sync_pipeline (beat entry)
|
||||
└─► refresh_catalog — fetch Evotor stores / groups / products
|
||||
└─► refresh_vk_catalog — fetch VK Market albums / products
|
||||
└─► mirror_to_vk — push Evotor → VK
|
||||
```
|
||||
|
||||
| Task | Queue | Description |
|
||||
|------|-------|-------------|
|
||||
| `web.tasks.celery_app.run_sync_pipeline` | default | Beat entry point; dispatches the chain |
|
||||
| `web.tasks.catalog.refresh_catalog` | default | Fetches Evotor catalog for all connected users; upserts `cached_stores`, `cached_groups`, `cached_products` |
|
||||
| `web.tasks.vk_catalog.refresh_vk_catalog` | default | Fetches VK Market albums and products for all connected users; upserts `vk_cached_albums`, `vk_cached_products` |
|
||||
| `web.tasks.vk_sync.mirror_to_vk` | sync | Mirrors enabled Evotor groups/products → VK Market (create or conditional update) |
|
||||
|
||||
**Evotor fetch sequence per user:**
|
||||
1. `GET /stores` → upsert `cached_stores`
|
||||
2. For each store: `GET /stores/{id}/product-groups` → upsert `cached_groups`
|
||||
3. For each store: `GET /stores/{id}/products` → upsert `cached_products`
|
||||
|
||||
**VK sync sequence per user:**
|
||||
**VK fetch sequence per user:**
|
||||
1. `market.getAlbums` → upsert `vk_cached_albums`
|
||||
2. `market.get` (extended=1, paginated) → upsert `vk_cached_products` with album membership
|
||||
|
||||
**Mirror logic per user (`mirror_to_vk`):**
|
||||
- Skips stores not in enabled store filters; skips groups not in enabled group filters
|
||||
- For each enabled group: ensures a matching VK album exists (creates via `market.addAlbum` if missing)
|
||||
- For each product in the group:
|
||||
- **Create** (`allow_to_sell=true`, no `vk_product_id` yet): uploads default photo once per run, calls `market.add`, assigns to album, stores returned `vk_product_id`
|
||||
- **Update** (has `vk_product_id`): calls `market.edit` only if name, price, description, or stock_amount changed vs the cached VK state
|
||||
- **Skip**: product disabled and never pushed, or nothing changed
|
||||
- Price for weight measures (`г`, `гр`, etc.) is multiplied by `VK_WEIGHT_PRICE_MULTIPLIER` before conversion to kopecks
|
||||
|
||||
Per-user failures are logged and skipped — one broken token does not block other users.
|
||||
Evotor stores that return `402 Payment Required` (subscription limit) are silently skipped at debug log level.
|
||||
|
||||
@@ -185,13 +205,17 @@ All settings are read from environment variables or a `.env` file:
|
||||
| `DATABASE_URL` | `mysql+pymysql://…@db:3306/evosync` | MariaDB connection string |
|
||||
| `REDIS_URL` | `redis://redis:6379/0` | Redis connection string |
|
||||
| `SECRET_KEY` | `change-me-in-production` | Session signing key |
|
||||
| `BASE_URL` | `http://localhost:8000` | Public URL of the service |
|
||||
| `DOMAIN` | — | Public domain name (e.g. `example.com`); used to derive `BASE_URL` and nginx config |
|
||||
| `BASE_URL` | `https://${DOMAIN}` | Public URL of the service |
|
||||
| `EVOTOR_APP_ID` | — | Evotor application ID |
|
||||
| `EVOTOR_WEBHOOK_SECRET` | — | Bearer secret for webhook endpoints |
|
||||
| `JIVOSITE_WIDGET_ID` | — | JivoSite widget ID |
|
||||
| `VK_DEFAULT_PHOTO_PATH` | `/app/default_product.png` | Fallback image path for VK products |
|
||||
| `VK_API_VERSION` | `5.199` | VK API version |
|
||||
| `CATALOG_REFRESH_INTERVAL_SECONDS` | `3600` | Evotor + VK catalog sync interval (s) |
|
||||
| `CATALOG_REFRESH_INTERVAL_SECONDS` | `3600` | Sync pipeline interval in seconds |
|
||||
| `VK_CATEGORY_ID` | `40932` | VK Market category ID for all products |
|
||||
| `VK_STOCK_AMOUNT` | `1000` | Stock amount set for in-sale products |
|
||||
| `VK_WEIGHT_PRICE_MULTIPLIER` | `10` | Price multiplier for weight-unit products (г, гр, …) |
|
||||
| `INVITE_EXPIRE_HOURS` | `48` | Invite link TTL in hours |
|
||||
| `EMAIL_PROVIDER` | `console` | Email provider (console / smtp / …) |
|
||||
| `SMS_PROVIDER` | `console` | SMS provider |
|
||||
@@ -202,13 +226,28 @@ All settings are read from environment variables or a `.env` file:
|
||||
## Running
|
||||
|
||||
```bash
|
||||
cp .env.example .env # fill in your values
|
||||
cp .env.example .env # set DOMAIN and other values
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
App is available at `http://localhost:8080`.
|
||||
Flower (queue monitor) at `http://localhost:5555`.
|
||||
|
||||
### First deploy (TLS)
|
||||
|
||||
```bash
|
||||
# 1. Generate nginx config from template
|
||||
DOMAIN=yourdomain.com envsubst '${DOMAIN}' < nginx/nginx.conf.template > nginx/nginx.conf
|
||||
|
||||
# 2. Obtain TLS certificate (reads DOMAIN from .env automatically)
|
||||
sudo ./scripts/init-letsencrypt.sh
|
||||
|
||||
# 3. Reload nginx
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
To change the domain later, repeat all three steps with the new domain.
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user