# EvoSync Web service for syncing a product catalog from Evotor POS → VK Market. Users connect their Evotor account and a VK community; products from the cash register then appear automatically in the VK store. --- ## Architecture ``` ┌─────────────────────────────────────────┐ │ Docker Compose │ │ │ Browser / Evotor ─────► web :8000 (FastAPI + Uvicorn) │ :8080 │ │ │ │ ├── MariaDB :3306 (primary DB) │ │ ├── Redis :6379 (Celery broker) │ │ │ │ │ ├── worker (Celery worker) │ │ ├── beat (Celery beat scheduler) │ │ └── flower :5555 (queue monitor) │ └─────────────────────────────────────────┘ ``` ### Services | Service | Image / Dockerfile | Purpose | External port | |----------|---------------------|-----------------------------------------------|---------------| | `web` | `Dockerfile.web` | FastAPI app, runs Alembic migrations on start | 8080 → 8000 | | `worker` | `Dockerfile.web` | Celery worker (sync, health, notifications…) | — | | `beat` | `Dockerfile.web` | Celery Beat — periodic task scheduler | — | | `flower` | `Dockerfile.web` | Celery queue monitoring UI | 5555 | | `db` | `mariadb:11.4` | Primary relational database | — | | `redis` | `redis:7-alpine` | Celery broker and result backend | — | ### Stack - **Python 3.12**, FastAPI 0.115, Uvicorn - **SQLAlchemy 2** + Alembic, MariaDB (PyMySQL) - **Celery 5** + Redis — background tasks, periodic catalog sync - **Jinja2** — server-side HTML rendering (`web/templates/`) - **Pydantic Settings** — configuration from env vars / `.env` - bcrypt — password hashing - python-json-logger — structured JSON logs to stdout --- ## Database Schema | Table | Purpose | |-----------------------|--------------------------------------------------------------------------------------| | `users` | User accounts (roles: system / admin / user; statuses: pending / active / suspended) | | `evotor_connections` | User ↔ Evotor link (access_token, api_token returned to Evotor webhooks) | | `vk_connections` | User ↔ VK link (user access token + VK community ID) | | `sync_configs` | Per-user sync settings | | `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 | | `vk_cached_albums` | Cached VK Market albums (product groups) | | `vk_cached_products` | Cached VK Market products | | `roles` | RBAC roles | | `permissions` | RBAC permissions | | `role_permissions` | M2M: role ↔ permission | | `user_roles` | M2M: user ↔ role | --- ## Background Tasks 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` | **Evotor sync 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:** 1. `market.getAlbums` → upsert `vk_cached_albums` 2. `market.get` (extended=1, paginated) → upsert `vk_cached_products` with album membership 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. --- ## Routes ### Connections | Method | Path | Description | |--------|-----------------------------------|------------------------------------------| | GET | `/connections` | View Evotor and VK connection status | | POST | `/connections/evotor` | Save / update Evotor API token manually | | POST | `/connections/evotor/disconnect` | Remove Evotor connection | | POST | `/connections/evotor/test` | Test Evotor connection (JSON) | | POST | `/connections/vk` | Save / update VK token and group ID | | POST | `/connections/vk/disconnect` | Remove VK connection | | POST | `/connections/vk/test` | Test VK connection (JSON) | ### Public / Authentication | Method | Path | Description | |----------|--------------------|----------------------------------------| | GET | `/` | Redirects to `/profile` or `/login` | | GET | `/health` | Health check (JSON) | | GET/POST | `/register` | User registration | | GET | `/confirm-email` | Email confirmation via token | | GET | `/resend-confirm` | Resend confirmation email | | GET/POST | `/login` | Login | | GET | `/logout` | Logout | | GET/POST | `/forgot-password` | Request password reset | | GET/POST | `/reset-password` | Reset password via token | | GET/POST | `/invite` | Complete registration via invite link | ### Profile (requires session) | Method | Path | Description | |----------|----------------------------|----------------------| | GET | `/profile` | View profile | | GET/POST | `/profile/edit` | Edit profile | | GET/POST | `/profile/change-password` | Change password | | GET/POST | `/profile/delete` | Delete account | ### Admin panel (`/admin`, roles: admin / system) | Method | Path | Description | |--------|------------------------------------|---------------------------| | GET | `/admin/users` | User list | | GET | `/admin/users/{id}` | User detail | | POST | `/admin/users/{id}/activate` | Activate user | | POST | `/admin/users/{id}/suspend` | Suspend user | | POST | `/admin/users/{id}/reset-password` | Reset user password | | POST | `/admin/users/{id}/send-invite` | Send invite email | | POST | `/admin/users/{id}/edit` | Edit user data | | POST | `/admin/users/{id}/delete` | Delete user | | GET | `/admin/roles` | Roles and permissions | | POST | `/admin/roles/{id}/permissions` | Update role permissions | ### Evotor Webhooks (Bearer `EVOTOR_WEBHOOK_SECRET`) | Method | Path | Description | |--------|----------------|-----------------------------------------------------------| | POST | `/user/create` | Evotor creates/links a user; returns api_token | | POST | `/user/verify` | Evotor verifies user credentials; returns api_token | | POST | `/user/token` | Evotor delivers its own access_token for a user | ### Evotor Catalog (requires session) | Method | Path | Description | |--------|---------------------------------------------------|--------------------------------------------| | GET | `/catalog` | Redirects to `/catalog/stores` | | GET | `/catalog/stores` | Evotor stores with per-store sync toggle | | GET | `/catalog/stores/{id}/groups` | Product groups with per-group sync toggle | | GET | `/catalog/stores/{id}/products` | Products (filterable by group) | | POST | `/catalog/stores/{id}/toggle` | Enable / disable store sync | | POST | `/catalog/stores/{id}/groups/{gid}/toggle` | Enable / disable group sync | ### VK Catalog (requires session) | Method | Path | Description | |--------|---------------------------------------|------------------------------------| | GET | `/vk-catalog/albums` | VK Market albums (product groups) | | GET | `/vk-catalog/albums/{id}/products` | Products in a VK album | ### API Docs | Path | Description | |----------|-------------| | `/docs` | Swagger UI | | `/redoc` | ReDoc | --- ## Configuration All settings are read from environment variables or a `.env` file: | Variable | Default | Description | |------------------------------------|-------------------------------------|---------------------------------------| | `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 | | `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) | | `INVITE_EXPIRE_HOURS` | `48` | Invite link TTL in hours | | `EMAIL_PROVIDER` | `console` | Email provider (console / smtp / …) | | `SMS_PROVIDER` | `console` | SMS provider | | `FLOWER_USER` / `FLOWER_PASSWORD` | `admin` / `changeme` | Basic Auth credentials for Flower | --- ## Running ```bash cp .env.example .env # fill in your values docker compose up -d --build ``` App is available at `http://localhost:8080`. Flower (queue monitor) at `http://localhost:5555`. ### Development ```bash pip install -r requirements.txt alembic upgrade head uvicorn web.main:app --reload --port 8000 ``` --- ## Tests ```bash pytest --cov=web ```