Used novalidate + server-side validation so errors appear in the dialog
rather than as browser-native popups. Form fields retain submitted values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a dialog form on the users list. Validates email uniqueness,
password length. Creates user as active with confirmed email.
System role can assign admin role; admin role can only create users.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Connections/Catalog/VK/Sync nav links only shown for regular users or when
admin is viewing as a user. Admin/system users land on /admin/users after login.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove /register route and nav links (users created via Evotor webhook)
- Fix evotor_webhooks.py: use phone=None instead of phone="" to avoid unique constraint
- Add admin "view as user" feature: POST /admin/users/{id}/view-as sets viewed_user_id
in session; POST /admin/view-as/stop clears it
- catalog, vk_catalog, sync, connections GET routes use get_viewed_user() so admins
see another user's data while browsing
- Orange banner shown at top when admin is viewing as another user
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added store_filters_seeded / group_filters_seeded flags to SyncConfig.
_enabled_*_ids now returns None (all enabled) only before first toggle,
not when the filter table is empty due to all being disabled.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phone is optional — admin users created via script don't have one.
Added migration 0010 to alter the column, updated create_admin.py to
pass None instead of empty string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nginx/nginx.conf is symlinked directly into system nginx config.
No need for a per-domain generate script or template — edit the file,
run nginx -t && systemctl reload nginx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Simpler than auto-converting: just pass xn----8sbfwtmcso8g.xn--p1ai directly.
Updated usage comments in both scripts to reflect this.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
xn--e1afmapc4af.xn--p1af was wrong; correct punycode is xn----8sbfwtmcso8g.xn--p1ai.
generate-nginx-conf.sh now converts IDN domains to punycode before expanding the
template, so cert paths and server_name directives are always ASCII-safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MariaDB: limit innodb buffer pool to 128M, max 20 connections
- Celery worker: concurrency 2→1
- Flower: moved to 'flower' profile (opt-in, not started by default)
Start with: docker compose --profile flower up -d flower
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nginx/nginx.conf: pre-generated config for both domains (IDN punycode for .рф)
- scripts/generate-nginx-conf.sh: generates sites-available config from template per domain
- scripts/init-letsencrypt.sh: accepts domain as arg (falls back to .env)
- README.md: updated deploy section, removed stale VK_WEIGHT_PRICE_MULTIPLIER, added sync/logs routes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds evo_mirror_enabled and vk_mirror_enabled flags to SyncConfig.
Each of the three background tasks (Зеркало Эвотор / Зеркало ВК /
Синхронизация) can now be enabled independently from the /sync page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously only the create path checked allow_to_sell. The update path
kept syncing disabled products indefinitely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each product's description is now built as "Name (цена за M.)" using its
own measure_name. The global description_postfix setting is removed —
it couldn't handle per-product units.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds price_multiplier and description_postfix to SyncConfig. The sync
page at /sync lets users configure them. vk_sync reads these per-user
settings and applies the multiplier to price and appends the postfix
as "(postfix)" to the VK product description.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
VK Market API expects the price field in rubles (float), not kopecks.
Removing the *100 conversion that was inflating all prices by 100x.
Comparison with cached VK prices now also uses rubles consistently.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add api_logs table (migration 0007) and ApiLog model
- Add web/lib/api_logger.py — httpx wrapper that records every outbound call
- Wire api_logger into vk_sync, vk_catalog, and connections test endpoints
- Add /admin/logs page with filters (service, method, status, time range, URL search) and expandable request/response detail
- Add "Логи" nav link for admin users
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After refresh_vk_catalog syncs the album list from VK, remove any
VkCachedAlbum rows whose album_id was not returned by the API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
VK API v5.199 requires item_id not item_ids for market.removeFromAlbum.
The wrong parameter name caused silent failures — products were not
actually removed from their old album when moved to a new one.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- catalog.py: include parent_id as fallback for group_evotor_id (Evotor API returns parent_id instead of group/parentUuid)
- vk_sync.py: on product update, detect album change and call market.removeFromAlbum + market.addToAlbum to move product to the correct album
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add VK OAuth implicit flow: /vk-auth redirect, /vk-callback JS page,
/vk-callback/save endpoint with state validation
- Add VK_CLIENT_ID/VK_CLIENT_SECRET to config
- Add refresh_token/token_expires_at columns to vk_connections (migration 0006)
- Fix vk_catalog task: handle price/thumb_photo as string or dict (VK API v5.199)
- Fix connections/vk/test: use groups.getById instead of market.getAlbums
(works with both user and group tokens)
- Add orphan deletion to mirror_to_vk: VK products not in Evotor are removed
- Handle ungrouped Evotor products: push to "Без категории" VK album
- Respect SyncConfig.is_enabled in mirror_to_vk
- Add product count column to catalog groups page
- Add group name column to catalog products page
- Expand test suite: 73 new tests covering connections routes, catalog routes,
vk_sync task logic, and catalog task helpers (138 total, all passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- Evotor catalog: background Celery task syncing stores/groups/products
from Evotor API; UI pages with per-store and per-group sync toggles
- VK connection: manual token + group ID entry with inline test button
- Evotor connection: inline test button (calls /stores)
- VK catalog: background task syncing VK Market albums and products;
separate catalog UI at /vk-catalog/albums
- SyncFilter extended to support entity_type=group with parent_entity_id
- Migration 0004: vk_cached_albums + vk_cached_products tables
- Beat schedule updated to run both refresh_catalog and refresh_vk_catalog
- README updated with new schema, routes, tasks, and config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also add itsdangerous to requirements.txt (missing implicit dep of
starlette SessionMiddleware) and a create_admin.py script for
bootstrapping a system-role user with all permissions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FastAPI + Celery + Redis + MariaDB stack with 6-service docker-compose.
Includes project skeleton (config, database, models, tasks, migrations)
and health endpoint with passing test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace manual community token entry with OAuth button that redirects
to VK authorization and auto-saves token via /vk/callback
- Fix groups.get API call (was groups.getById) to correctly retrieve
admin group id and name from user token response
- Fix price comparison: VK price.amount is in roubles, not kopecks
- Keep manual token input as fallback when VK_CLIENT_ID is not set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add sync_engine.py: background asyncio loop syncing Evotor products to VK market
- Wire sync_loop into lifespan alongside health_check_loop
- Add SYNC_INTERVAL_SECONDS and VK_DEFAULT_PHOTO_PATH settings to config
- Mount default product image in docker-compose
- Add synced_at column to CachedProduct model + migration
- Show synced_at status in catalog products template
- Fix VK groups API response parsing (handle list vs dict)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set overflow: visible on table-responsive and use data-bs-strategy="fixed"
so the filter dropdown renders outside the scroll container.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
VK connection now uses manual community token entry, so OAuth credentials
are no longer needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves#4 — VK OAuth flow caused "Security Error" because market sync
requires a community access token, not a personal user token. Replaced
OAuth with manual token input (same pattern as Evotor). Added
step-by-step instructions. Updated health checker to validate community
tokens via groups.getById instead of users.get.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves#3 — widget is loaded on every page via base.html when
JIVOSITE_WIDGET_ID env var is set. Centralized Jinja2Templates instance
in web/templates_env.py with jivosite_widget_id as a global.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>