feat: add VK OAuth connection with health checks

- Add VkConnection model with is_online/last_checked_at fields
- Add /vk OAuth flow (connect/callback/disconnect/page)
- Add VK entry to connections dashboard
- Extend background health checker to check VK tokens via users.get
- Add Alembic migration for vk_connections table
- Add VK_CLIENT_ID/SECRET/SCOPES/API_VERSION config settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mguschin
2026-03-06 15:29:42 +03:00
parent 379f781e1e
commit cfc7229daf
8 changed files with 358 additions and 9 deletions

86
web/templates/vk.html Normal file
View File

@@ -0,0 +1,86 @@
{% extends "base.html" %}
{% block title %}Подключение ВКонтакте — EvoSync{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-sm-10 col-md-7 col-lg-6">
{% if error %}
<div class="alert alert-danger mt-4">
{% if error == "invalid_state" %}
<i class="bi bi-exclamation-triangle me-2"></i>Ошибка безопасности. Попробуйте подключить аккаунт заново.
{% elif error == "token_exchange" %}
<i class="bi bi-exclamation-triangle me-2"></i>Не удалось получить токен доступа от ВКонтакте. Попробуйте позже.
{% elif error == "no_token" %}
<i class="bi bi-exclamation-triangle me-2"></i>ВКонтакте не вернул токен доступа. Попробуйте позже.
{% else %}
<i class="bi bi-exclamation-triangle me-2"></i>Произошла ошибка при подключении: {{ error }}
{% endif %}
</div>
{% endif %}
<div class="card shadow-sm mt-4">
<div class="card-header">
<h1 class="h5 mb-0">Подключение ВКонтакте</h1>
</div>
{% if connection %}
{# ── CONNECTED STATE ── #}
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="text-muted small">Статус</span>
<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Подключено</span>
</li>
{% if connection.first_name or connection.last_name %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="text-muted small">Профиль</span>
<span>{{ connection.first_name }} {{ connection.last_name }}</span>
</li>
{% endif %}
{% if connection.vk_user_id %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="text-muted small">ID пользователя</span>
<span class="font-monospace small text-muted">{{ connection.vk_user_id }}</span>
</li>
{% endif %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="text-muted small">Подключено</span>
<span class="small">{{ connection.connected_at.strftime("%d.%m.%Y %H:%M") }}</span>
</li>
</ul>
<div class="card-body d-grid gap-2">
<a href="/vk/connect" class="btn btn-primary">Переподключить</a>
<form method="post" action="/vk/disconnect">
<button type="submit" class="btn btn-outline-danger w-100">Отключить аккаунт ВКонтакте</button>
</form>
</div>
{% else %}
{# ── NOT CONNECTED STATE ── #}
<div class="card-body">
<p class="text-muted mb-3">
Подключите ваш аккаунт ВКонтакте, чтобы система могла автоматически синхронизировать
каталог товаров из Эвотор в вашу группу ВКонтакте.
</p>
<ul class="text-muted small mb-4">
<li>Вы будете перенаправлены на сайт ВКонтакте для авторизации</li>
<li>После подтверждения доступа синхронизация будет настроена автоматически</li>
<li>Вы можете отключить доступ в любой момент</li>
</ul>
<div class="d-grid">
<a href="/vk/connect" class="btn btn-primary btn-lg">Подключить ВКонтакте</a>
</div>
</div>
{% endif %}
</div>
<div class="mt-3 text-center">
<a href="/connections" class="text-muted small">
<i class="bi bi-arrow-left me-1"></i>Вернуться к подключениям
</a>
</div>
</div>
</div>
{% endblock %}