- Add /connections page showing all integrations with online/offline status - Add background health checker that polls Evotor API every 10 minutes - Add is_online and last_checked_at fields to evotor_connections table - Replace Evotor navbar link with unified Connections link - Redirect connect/disconnect flows to /connections - Add Alembic migration for new columns Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.1 KiB
VK OAuth Connection Feature
Context
EvoSync syncs product catalogs from Evotor to VK. Users already connect their Evotor account via OAuth. Now we need the same for VK — users authorize via VK OAuth, we store the access token, and show connection status on the connections dashboard.
VK OAuth Flow (Web)
- Authorize URL:
https://oauth.vk.com/authorize - Token URL:
https://oauth.vk.com/access_token - Verify endpoint:
GET https://api.vk.com/method/users.get?access_token={token}&v=5.131- Error code 5 = token invalid/expired
- Scopes:
market groups offline(offline = permanent token, no expiry) - Token response fields:
access_token,user_id,expires_in(0 if offline scope used)
With offline scope, tokens don't expire — no refresh logic needed. If a user revokes access on VK's side, the health checker will detect it.
Plan
1. New Model — VkConnection in web/models.py
class VkConnection(Base):
__tablename__ = "vk_connections"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), unique=True, nullable=False)
access_token = Column(Text, nullable=False)
vk_user_id = Column(String(50), nullable=True) # VK user ID from token response
first_name = Column(String(255), nullable=True) # VK profile first name
last_name = Column(String(255), nullable=True) # VK profile last name
is_online = Column(Boolean, default=False, server_default="0", nullable=False)
last_checked_at = Column(DateTime, nullable=True)
connected_at = Column(DateTime, server_default=func.now(), nullable=False)
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
user = relationship("User", back_populates="vk_connection")
Add to User model:
vk_connection = relationship("VkConnection", back_populates="user", uselist=False)
2. Alembic Migration
Generate migration for the new vk_connections table and the relationship.
3. Config — web/config.py
Add:
VK_CLIENT_ID: str = ""
VK_CLIENT_SECRET: str = ""
VK_SCOPES: str = "market groups offline"
VK_API_VERSION: str = "5.131"
4. VK Route — web/routes/vk.py (new)
Follow the same pattern as web/routes/evotor.py:
Constants:
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"
Endpoints:
-
GET /vk— Connection page. Shows connected state (VK profile name, user_id) or disconnected state with explanation and connect button. -
GET /vk/connect— Generate state token, save in session, redirect to:https://oauth.vk.com/authorize?client_id={id}&response_type=code &redirect_uri={BASE_URL}/vk/callback&scope={scopes}&state={state} &display=page&v=5.131 -
GET /vk/callback— OAuth callback:- Validate state from session
- Exchange code for token via GET to
https://oauth.vk.com/access_tokenwith params:client_id,client_secret,code,redirect_uri(NOTE: VK uses GET, not POST, and params in query string, not body) - Response contains:
access_token,user_id,expires_in - Fetch user profile via
users.getto get first_name, last_name - Save/update
VkConnectionrecord withis_online=True,last_checked_at=now() - Redirect to
/connections
-
POST /vk/disconnect— Delete VkConnection record, redirect to/vk
5. VK Template — web/templates/vk.html (new)
Same structure as evotor.html:
Connected state:
- Status badge: "Подключено" (green)
- VK profile: first_name + last_name
- VK user ID (monospace)
- Connected timestamp
- Buttons: "Переподключить", "Отключить аккаунт ВКонтакте"
Disconnected state:
- Explanation text: "Подключите ваш аккаунт ВКонтакте, чтобы система могла автоматически синхронизировать каталог товаров из Эвотор в вашу группу ВКонтакте."
- Bullet points: redirect to VK for auth, auto-setup after confirmation, can disconnect anytime
- Button: "Подключить ВКонтакте"
Error display: same pattern as evotor.html (invalid_state, token_exchange, no_token)
Back link: "Вернуться к подключениям" → /connections
6. Register Route — web/main.py
from web.routes import vk
app.include_router(vk.router)
7. Add to Connections Dashboard — web/routes/connections.py
Add VK entry to the connections list:
vk_conn = db.query(VkConnection).filter(VkConnection.user_id == user.id).first()
connections.append({
"name": "ВКонтакте",
"icon": "bi-chat-dots", # or another suitable Bootstrap icon
"connected": vk_conn is not None,
"is_online": vk_conn.is_online if vk_conn else False,
"last_checked_at": vk_conn.last_checked_at if vk_conn else None,
"details": f"{vk_conn.first_name} {vk_conn.last_name}" if vk_conn and vk_conn.first_name else None,
"connect_url": "/vk",
"disconnect_url": "/vk/disconnect",
})
8. Background Health Check — web/health_checker.py
Add VK check alongside existing Evotor check:
async def check_vk_connection(access_token: str) -> bool:
"""Call users.get to verify VK token is valid."""
async with httpx.AsyncClient() as client:
resp = await client.get(
"https://api.vk.com/method/users.get",
params={"access_token": access_token, "v": "5.131"},
timeout=10,
)
if resp.status_code != 200:
return False
data = resp.json()
# Error code 5 = invalid token
if "error" in data:
return False
return True
In run_health_checks(), add a loop over VkConnection rows with the same pattern as Evotor checks.
Files Summary
| File | Action |
|---|---|
web/models.py |
Modify — add VkConnection model + User relationship |
web/config.py |
Modify — add VK_* settings |
web/main.py |
Modify — register vk router |
web/routes/vk.py |
Create — OAuth flow (connect/callback/disconnect/page) |
web/routes/connections.py |
Modify — add VK to connections list |
web/health_checker.py |
Modify — add VK health check |
web/templates/vk.html |
Create — VK connection page |
| Alembic migration | Create — vk_connections table |
Env Config Needed
VK_CLIENT_ID=your_vk_app_id
VK_CLIENT_SECRET=your_vk_app_secret
VK_SCOPES=market groups offline
Verification
- Run
alembic upgrade head - Visit
/connections— should show VK as disconnected (grey) - Click VK → "Подключить ВКонтакте" → redirects to VK auth
- After VK auth → callback saves token → redirects to
/connections→ VK shows green - Visit
/vk— shows connected state with VK profile info - Disconnect → VK returns to grey on connections page
- Wait for health check cycle — verify
is_onlineandlast_checked_atupdate