feat: VK OAuth flow, catalog sync improvements, and expanded test suite

- 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>
This commit is contained in:
mguschin
2026-05-12 15:09:47 +03:00
parent 4f4081c54c
commit 7b4f52b005
16 changed files with 1624 additions and 32 deletions

View File

@@ -17,21 +17,45 @@ celery_app.conf.update(
broker_connection_retry_on_startup=True,
task_routes={
"web.tasks.sync.*": {"queue": "sync"},
"web.tasks.vk_sync.*": {"queue": "sync"},
"web.tasks.health.*": {"queue": "health"},
"web.tasks.catalog.*": {"queue": "default"},
"web.tasks.vk_catalog.*": {"queue": "default"},
"web.notifications.tasks.*": {"queue": "notifications"},
},
beat_schedule={
"refresh-catalog": {
"task": "web.tasks.catalog.refresh_catalog",
"schedule": timedelta(seconds=settings.CATALOG_REFRESH_INTERVAL_SECONDS),
},
"refresh-vk-catalog": {
"task": "web.tasks.vk_catalog.refresh_vk_catalog",
# Chain: fetch Evotor → fetch VK catalog → mirror Evotor→VK
# Beat fires the launcher task which chains all three sequentially.
"sync-pipeline": {
"task": "web.tasks.celery_app.run_sync_pipeline",
"schedule": timedelta(seconds=settings.CATALOG_REFRESH_INTERVAL_SECONDS),
},
},
)
# Register task modules so beat/worker can discover them
celery_app.autodiscover_tasks(["web.tasks.catalog", "web.tasks.vk_catalog"])
celery_app.autodiscover_tasks([
"web.tasks.catalog",
"web.tasks.vk_catalog",
"web.tasks.vk_sync",
"web.tasks.celery_app",
])
@celery_app.task(name="web.tasks.celery_app.run_sync_pipeline", queue="default")
def run_sync_pipeline() -> str:
"""
Beat entry point. Chains refresh_catalog → refresh_vk_catalog → mirror_to_vk
so that mirror only runs after both catalog fetches are complete.
"""
from celery import chain
from web.tasks.catalog import refresh_catalog
from web.tasks.vk_catalog import refresh_vk_catalog
from web.tasks.vk_sync import mirror_to_vk
chain(
refresh_catalog.si(),
refresh_vk_catalog.si(),
mirror_to_vk.si(),
).apply_async()
return "pipeline dispatched"