feat(db): migrate accounts/credentials/platforms to v2 schema

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 09:53:49 +08:00
parent 27d687becd
commit 00e3116339
4 changed files with 230 additions and 33 deletions

View File

@@ -10,7 +10,9 @@ import time
from datetime import datetime
from typing import Any, Optional
from db.auth_strategy import ALL_AUTH_STRATEGIES, AUTH_STRATEGY_PASSWORD_AUTO
from db.connection import get_conn, init_db
from db.platform_defaults import PLATFORM_DEFAULT_AUTH_STRATEGY
from util.logging_config import get_skill_logger
@@ -32,8 +34,30 @@ def _unix_to_iso_local(ts: Optional[int]) -> Optional[str]:
return None
_UNSET = object()
def resolve_auth_strategy_for_platform(conn, platform_key: str) -> str:
"""Pick auth_strategy from platforms.default_auth_strategy, built-in map, or password_auto."""
cur = conn.cursor()
cur.execute(
"SELECT default_auth_strategy FROM platforms WHERE platform_key = ?",
(platform_key,),
)
row = cur.fetchone()
if row and row[0]:
s = str(row[0])
if s in ALL_AUTH_STRATEGIES:
return s
mapped = PLATFORM_DEFAULT_AUTH_STRATEGY.get(platform_key)
if mapped and mapped in ALL_AUTH_STRATEGIES:
return mapped
return AUTH_STRATEGY_PASSWORD_AUTO
def _acc_row_to_dict(row) -> dict[str, Any]:
"""Convert an accounts table row tuple to a dict matching the v2 schema."""
dev_fp = row[20]
return {
"id": row[0],
"platform_key": row[1],
@@ -53,8 +77,11 @@ def _acc_row_to_dict(row) -> dict[str, Any]:
"last_error_code": row[15] or "",
"last_error_message": row[16] or "",
"extra_json": _safe_json_loads(row[17]),
"created_at": _unix_to_iso_local(row[18]),
"updated_at": _unix_to_iso_local(row[19]),
"auth_strategy": row[18],
"session_persistent": int(row[19]) if row[19] is not None else 1,
"device_fingerprint": dev_fp if dev_fp is not None else None,
"created_at": _unix_to_iso_local(row[21]),
"updated_at": _unix_to_iso_local(row[22]),
}
@@ -74,14 +101,14 @@ def list_platforms_from_db(conn, domain: Optional[str] = None) -> list[dict]:
cur.execute(
"SELECT platform_key, display_name, domain, provider_code, "
"default_url, aliases_json, capabilities_json, enabled, "
"created_at, updated_at FROM platforms WHERE domain = ? ORDER BY platform_key",
"default_auth_strategy, created_at, updated_at FROM platforms WHERE domain = ? ORDER BY platform_key",
(domain,),
)
else:
cur.execute(
"SELECT platform_key, display_name, domain, provider_code, "
"default_url, aliases_json, capabilities_json, enabled, "
"created_at, updated_at FROM platforms ORDER BY platform_key"
"default_auth_strategy, created_at, updated_at FROM platforms ORDER BY platform_key"
)
rows = cur.fetchall()
result = []
@@ -95,8 +122,9 @@ def list_platforms_from_db(conn, domain: Optional[str] = None) -> list[dict]:
"aliases": _safe_json_loads(r[5], []),
"capabilities": _safe_json_loads(r[6], {}),
"enabled": bool(r[7]),
"created_at": _unix_to_iso_local(r[8]),
"updated_at": _unix_to_iso_local(r[9]),
"default_auth_strategy": r[8],
"created_at": _unix_to_iso_local(r[9]),
"updated_at": _unix_to_iso_local(r[10]),
})
return result
@@ -106,7 +134,7 @@ def get_platform_from_db(conn, platform_key: str) -> Optional[dict]:
cur.execute(
"SELECT platform_key, display_name, domain, provider_code, "
"default_url, aliases_json, capabilities_json, enabled, "
"created_at, updated_at FROM platforms WHERE platform_key = ?",
"default_auth_strategy, created_at, updated_at FROM platforms WHERE platform_key = ?",
(platform_key,),
)
r = cur.fetchone()
@@ -121,8 +149,9 @@ def get_platform_from_db(conn, platform_key: str) -> Optional[dict]:
"aliases": _safe_json_loads(r[5], []),
"capabilities": _safe_json_loads(r[6], {}),
"enabled": bool(r[7]),
"created_at": _unix_to_iso_local(r[8]),
"updated_at": _unix_to_iso_local(r[9]),
"default_auth_strategy": r[8],
"created_at": _unix_to_iso_local(r[9]),
"updated_at": _unix_to_iso_local(r[10]),
}
@@ -144,20 +173,30 @@ def insert_account(
url: Optional[str],
extra_json: Optional[str],
now: int,
auth_strategy: str,
session_persistent: int = 1,
device_fingerprint: Optional[str] = None,
) -> int:
if auth_strategy not in ALL_AUTH_STRATEGIES:
raise ValueError(f"invalid auth_strategy: {auth_strategy!r}")
if session_persistent not in (0, 1):
raise ValueError("session_persistent must be 0 or 1")
cur = conn.cursor()
cur.execute(
"""
INSERT INTO accounts
(platform_key, account_label, login_id, login_id_type, tenant_id,
environment, role, provider_code, profile_dir, url,
status, session_status, extra_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 'unknown', ?, ?, ?)
status, session_status, extra_json,
auth_strategy, session_persistent, device_fingerprint,
created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 'unknown', ?, ?, ?, ?, ?, ?)
""",
(
platform_key, account_label, login_id, login_id_type, tenant_id,
environment, role, provider_code, profile_dir, url,
extra_json or "{}", now, now,
extra_json or "{}", auth_strategy, session_persistent, device_fingerprint,
now, now,
),
)
return int(cur.lastrowid)
@@ -176,6 +215,7 @@ def get_account_by_id(account_id) -> Optional[dict]:
profile_dir, url, status, session_status,
last_used_at, last_login_check_at,
last_error_code, last_error_message, extra_json,
auth_strategy, session_persistent, device_fingerprint,
created_at, updated_at
FROM accounts WHERE id = ?
""",
@@ -232,7 +272,9 @@ def find_accounts(
"SELECT id, platform_key, account_label, login_id, login_id_type, "
"tenant_id, environment, role, provider_code, profile_dir, url, "
"status, session_status, last_used_at, last_login_check_at, "
"last_error_code, last_error_message, extra_json, created_at, updated_at "
"last_error_code, last_error_message, extra_json, "
"auth_strategy, session_persistent, device_fingerprint, "
"created_at, updated_at "
f"FROM accounts {where} ORDER BY updated_at DESC, id DESC LIMIT ?"
)
params.append(int(limit))
@@ -328,6 +370,42 @@ def update_account_error(
conn.close()
def update_account(
conn,
account_id: int,
*,
auth_strategy: Optional[str] = None,
session_persistent: Optional[int] = None,
device_fingerprint: Any = _UNSET,
) -> None:
"""Optional field updates for accounts v2 columns."""
sets: list[str] = []
params: list[Any] = []
if auth_strategy is not None:
if auth_strategy not in ALL_AUTH_STRATEGIES:
raise ValueError(f"invalid auth_strategy: {auth_strategy!r}")
sets.append("auth_strategy = ?")
params.append(auth_strategy)
if session_persistent is not None:
if session_persistent not in (0, 1):
raise ValueError("session_persistent must be 0 or 1")
sets.append("session_persistent = ?")
params.append(session_persistent)
if device_fingerprint is not _UNSET:
sets.append("device_fingerprint = ?")
params.append(device_fingerprint)
if not sets:
return
sets.append("updated_at = ?")
params.append(_now_unix())
params.append(int(account_id))
cur = conn.cursor()
cur.execute(
f"UPDATE accounts SET {', '.join(sets)} WHERE id = ?",
tuple(params),
)
def account_exists_with_login_id(conn, platform_key: str, login_id: str, environment: str, role: str) -> bool:
cur = conn.cursor()
cur.execute(