feat(onboarding): add first-run membership activation

This commit is contained in:
chengma
2026-07-23 17:29:16 +08:00
parent a0862dacbd
commit 7dabeb9971
15 changed files with 855 additions and 36 deletions
+28 -8
View File
@@ -60,6 +60,14 @@ class SubscriptionStatus:
def allows_product_workflows(self) -> bool:
return self.state in {STATUS_ACTIVE, STATUS_GRACE, STATUS_LEGACY}
@property
def credentials_accepted(self) -> bool:
return self.state not in {
STATUS_NOT_CONFIGURED,
STATUS_KEY_INVALID,
STATUS_UNAVAILABLE,
}
@property
def interface_available(self) -> bool:
return self.state != STATUS_LEGACY
@@ -84,6 +92,8 @@ def check_status(
config=None,
*,
cmhub_config_path=None,
base_url=None,
api_key=None,
request_json=None,
) -> SubscriptionStatus:
"""Look up the current API-key account subscription without leaking secrets.
@@ -95,25 +105,32 @@ def check_status(
cfg = appconfig.load_config() if config is None else config
try:
cmhub = appconfig.cmhub_config(cfg)
base_url = appconfig.normalize_cmhub_base_url(cmhub.get("base_url"))
configured_base_url = (
cmhub.get("base_url") if base_url is None else base_url
)
resolved_base_url = appconfig.normalize_cmhub_base_url(configured_base_url)
key_path = (
cmhub_config_path
or cfg.get("cmhub_config_path")
or appconfig.cmhub_config_file_path(cfg)
)
api_key = appconfig.get_cmhub_api_key(path=key_path)
resolved_api_key = (
appconfig.get_cmhub_api_key(path=key_path)
if api_key is None
else str(api_key or "").strip()
)
except Exception:
return SubscriptionStatus(STATUS_NOT_CONFIGURED)
if not base_url or not api_key:
if not resolved_base_url or not resolved_api_key:
return SubscriptionStatus(STATUS_NOT_CONFIGURED)
requester = request_json or ai.request_cmhub_json
try:
data = requester(
"GET",
base_url,
resolved_base_url,
"/api/v1/cmshopee/subscription/status",
api_key,
resolved_api_key,
connect_timeout=cmhub.get(
"connect_timeout",
appconfig.CMHUB_CONNECT_TIMEOUT_DEFAULT,
@@ -125,7 +142,7 @@ def check_status(
return _status_from_cmhub_error(exc)
except Exception:
return SubscriptionStatus(STATUS_UNAVAILABLE)
return _parse_status_response(data, base_url)
return _parse_status_response(data, resolved_base_url)
def format_expiry(value: str) -> str:
@@ -173,7 +190,7 @@ def _parse_status_response(data, base_url: str) -> SubscriptionStatus:
plan_name = str(plan.get("display_name") or "").strip()
expires_at = str(data.get("expires_at") or "").strip()
grace_expires_at = str(data.get("grace_expires_at") or "").strip()
manage_url = _safe_manage_url(data.get("manage_url"), base_url)
manage_url = safe_manage_url(data.get("manage_url"), base_url)
notice_id = str(data.get("notice_id") or "").strip()
if normalized in {STATUS_ACTIVE, STATUS_GRACE}:
@@ -213,7 +230,7 @@ def _normalize_remote_state(value) -> str:
}.get(normalized, "")
def _safe_manage_url(value, base_url: str) -> str:
def safe_manage_url(value, base_url: str) -> str:
text = str(value or "").strip()
if not text:
return ""
@@ -229,3 +246,6 @@ def _safe_manage_url(value, base_url: str) -> str:
):
return ""
return text
_safe_manage_url = safe_manage_url