diff --git a/web/health_checker.py b/web/health_checker.py index de35ecc..51dcae6 100644 --- a/web/health_checker.py +++ b/web/health_checker.py @@ -11,7 +11,7 @@ logger = logging.getLogger("uvicorn.error") EVOTOR_STORES_URL = "https://api.evotor.ru/stores" EVOTOR_TOKEN_URL = "https://oauth.evotor.ru/oauth/token" -VK_USERS_GET_URL = "https://api.vk.com/method/users.get" +VK_GROUPS_GET_URL = "https://api.vk.com/method/groups.getById" VK_API_VERSION = "5.131" # Refresh Evotor token if it expires within this window @@ -59,7 +59,7 @@ async def check_vk_connection(access_token: str) -> bool: try: async with httpx.AsyncClient() as client: resp = await client.get( - VK_USERS_GET_URL, + VK_GROUPS_GET_URL, params={"access_token": access_token, "v": VK_API_VERSION}, timeout=10, ) diff --git a/web/routes/vk.py b/web/routes/vk.py index 4a7af69..9d1c445 100644 --- a/web/routes/vk.py +++ b/web/routes/vk.py @@ -1,4 +1,3 @@ -import secrets from datetime import datetime import httpx @@ -15,15 +14,9 @@ from web.models import User, VkConnection router = APIRouter(prefix="/vk") -VK_AUTHORIZE_URL = "https://oauth.vk.com/authorize" -VK_TOKEN_URL = "https://oauth.vk.com/access_token" VK_API_URL = "https://api.vk.com/method" -def _redirect_uri() -> str: - return f"{settings.BASE_URL}/vk/callback" - - @router.get("") def vk_page( request: Request, @@ -43,102 +36,62 @@ def vk_page( }) -@router.get("/connect") -def vk_connect(request: Request, user: User | None = Depends(get_current_user)): - if not user: - return RedirectResponse("/login", 303) - - state = secrets.token_urlsafe(32) - request.session["vk_oauth_state"] = state - - params = ( - f"?client_id={settings.VK_CLIENT_ID}" - f"&response_type=code" - f"&redirect_uri={_redirect_uri()}" - f"&scope={settings.VK_SCOPES.replace(' ', '%20')}" - f"&state={state}" - f"&display=page" - f"&v={settings.VK_API_VERSION}" - ) - return RedirectResponse(VK_AUTHORIZE_URL + params, 302) - - -@router.get("/callback") -async def vk_callback( +@router.post("/token") +async def vk_token( request: Request, db: Session = Depends(get_db), user: User | None = Depends(get_current_user), ): + """Save a manually entered VK community access token.""" if not user: return RedirectResponse("/login", 303) - code = request.query_params.get("code") - state = request.query_params.get("state") - saved_state = request.session.pop("vk_oauth_state", None) + form = await request.form() + token = (form.get("token") or "").strip() + if not token: + return RedirectResponse("/vk?error=empty_token", 303) - if not code or not state or state != saved_state: - return RedirectResponse("/vk?error=invalid_state", 303) - - # Exchange code for token (VK uses GET with query params) + # Fetch community info to validate the token and get group name/id + group_id = None + group_name = None try: async with httpx.AsyncClient() as client: - token_response = await client.get( - VK_TOKEN_URL, - params={ - "client_id": settings.VK_CLIENT_ID, - "client_secret": settings.VK_CLIENT_SECRET, - "code": code, - "redirect_uri": _redirect_uri(), - }, + resp = await client.get( + f"{VK_API_URL}/groups.getById", + params={"access_token": token, "v": settings.VK_API_VERSION}, timeout=15, ) - token_response.raise_for_status() - token_data = token_response.json() - except Exception: - return RedirectResponse("/vk?error=token_exchange", 303) - - access_token = token_data.get("access_token") - vk_user_id = str(token_data.get("user_id", "")) or None - if not access_token: - return RedirectResponse("/vk?error=no_token", 303) - - # Fetch VK profile info - first_name = None - last_name = None - try: - async with httpx.AsyncClient() as client: - profile_response = await client.get( - f"{VK_API_URL}/users.get", - params={"access_token": access_token, "v": settings.VK_API_VERSION}, - timeout=15, - ) - if profile_response.status_code == 200: - profile_data = profile_response.json() - items = profile_data.get("response", []) - if items: - first_name = items[0].get("first_name") - last_name = items[0].get("last_name") + if resp.status_code == 200: + data = resp.json() + if "error" in data: + return RedirectResponse("/vk?error=invalid_token", 303) + groups = data.get("response", {}).get("groups", []) + if groups: + group_id = str(groups[0].get("id", "")) + group_name = groups[0].get("name") + elif resp.status_code == 401: + return RedirectResponse("/vk?error=invalid_token", 303) except Exception: pass - # Save or update connection connection = db.query(VkConnection).filter(VkConnection.user_id == user.id).first() + now = datetime.utcnow() if connection: - connection.access_token = access_token - connection.vk_user_id = vk_user_id - connection.first_name = first_name - connection.last_name = last_name + connection.access_token = token + connection.vk_user_id = group_id + connection.first_name = group_name + connection.last_name = None connection.is_online = True - connection.last_checked_at = datetime.utcnow() + connection.last_checked_at = now else: connection = VkConnection( user_id=user.id, - access_token=access_token, - vk_user_id=vk_user_id, - first_name=first_name, - last_name=last_name, + access_token=token, + vk_user_id=group_id, + first_name=group_name, + last_name=None, is_online=True, - last_checked_at=datetime.utcnow(), + last_checked_at=now, ) db.add(connection) db.commit() diff --git a/web/templates/vk.html b/web/templates/vk.html index 1bbbd39..f48f2ef 100644 --- a/web/templates/vk.html +++ b/web/templates/vk.html @@ -7,12 +7,10 @@ {% if error %}
- {% if error == "invalid_state" %} - Ошибка безопасности. Попробуйте подключить аккаунт заново. - {% elif error == "token_exchange" %} - Не удалось получить токен доступа от ВКонтакте. Попробуйте позже. - {% elif error == "no_token" %} - ВКонтакте не вернул токен доступа. Попробуйте позже. + {% if error == "invalid_token" %} + Токен недействителен. Убедитесь, что скопировали ключ доступа сообщества правильно. + {% elif error == "empty_token" %} + Введите ключ доступа. {% else %} Произошла ошибка при подключении: {{ error }} {% endif %} @@ -31,15 +29,15 @@ Статус Подключено - {% if connection.first_name or connection.last_name %} + {% if connection.first_name %}
  • - Профиль - {{ connection.first_name }} {{ connection.last_name }} + Сообщество + {{ connection.first_name }}
  • {% endif %} {% if connection.vk_user_id %}
  • - ID пользователя + ID сообщества {{ connection.vk_user_id }}
  • {% endif %} @@ -48,10 +46,18 @@ {{ connection.connected_at.strftime("%d.%m.%Y %H:%M") }} -
    - Переподключить + +
    - +
    @@ -59,17 +65,26 @@ {# ── NOT CONNECTED STATE ── #}

    - Подключите ваш аккаунт ВКонтакте, чтобы система могла автоматически синхронизировать - каталог товаров из Эвотор в вашу группу ВКонтакте. + Для подключения вам нужен ключ доступа сообщества ВКонтакте. + Синхронизация товаров работает только через сообщество.

    -
      -
    • Вы будете перенаправлены на сайт ВКонтакте для авторизации
    • -
    • После подтверждения доступа синхронизация будет настроена автоматически
    • -
    • Вы можете отключить доступ в любой момент
    • -
    - + +
      +
    1. Откройте vk.com и перейдите в управление вашим сообществом.
    2. +
    3. Перейдите в раздел Настройки → Работа с API.
    4. +
    5. Создайте ключ доступа с правами Управление товарами и Управление сообществом.
    6. +
    7. Скопируйте ключ и вставьте его в поле ниже.
    8. +
    + +
    +
    + + +
    +
    + +
    +
    {% endif %}