Add TMDB search and media detail pages, HDHive resource ingestion flow, unified error handling, Docker single-container runtime, and project docs/config updates for local deployment. Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
4.6 KiB
Python
147 lines
4.6 KiB
Python
import time
|
|
|
|
from config import Config
|
|
from http_client import request_json
|
|
from error_handling import AppServiceError
|
|
|
|
_CMS_TOKEN_CACHE = {"token": "", "expires_at": 0}
|
|
|
|
|
|
def _resolve_login_url():
|
|
if Config.CMS_LOGIN_URL:
|
|
return Config.CMS_LOGIN_URL
|
|
if Config.CMS_BASE_URL:
|
|
return f"{Config.CMS_BASE_URL.rstrip('/')}/api/auth/login"
|
|
raise AppServiceError(
|
|
"CMS login url is not configured",
|
|
category="validation",
|
|
code="CMS_CONFIG_MISSING",
|
|
provider="cms",
|
|
)
|
|
|
|
|
|
def _resolve_add_share_url():
|
|
if Config.CMS_ADD_SHARE_URL:
|
|
return Config.CMS_ADD_SHARE_URL
|
|
if Config.CMS_BASE_URL:
|
|
return f"{Config.CMS_BASE_URL.rstrip('/')}/api/cloud/add_share_down"
|
|
raise AppServiceError(
|
|
"CMS add share url is not configured",
|
|
category="validation",
|
|
code="CMS_CONFIG_MISSING",
|
|
provider="cms",
|
|
)
|
|
|
|
|
|
def _headers(token):
|
|
headers = {"Content-Type": "application/json"}
|
|
if token:
|
|
headers["Authorization"] = f"Bearer {token}"
|
|
return headers
|
|
|
|
|
|
def _extract_cms_token(login_result):
|
|
data = login_result.get("data") or {}
|
|
token = (data.get("data") or {}).get("token")
|
|
if not token:
|
|
raise AppServiceError(
|
|
"CMS login succeeded but token missing",
|
|
category="upstream",
|
|
code="CMS_TOKEN_MISSING",
|
|
provider="cms",
|
|
detail={"response": data},
|
|
)
|
|
return token
|
|
|
|
|
|
def _login_and_get_token():
|
|
if Config.CMS_TOKEN:
|
|
return Config.CMS_TOKEN
|
|
if not Config.CMS_USERNAME or not Config.CMS_PASSWORD:
|
|
raise AppServiceError(
|
|
"CMS username/password is required when CMS_TOKEN is not provided",
|
|
category="validation",
|
|
code="CMS_CONFIG_MISSING",
|
|
provider="cms",
|
|
)
|
|
login_result = request_json(
|
|
_resolve_login_url(),
|
|
method="POST",
|
|
payload={"username": Config.CMS_USERNAME, "password": Config.CMS_PASSWORD},
|
|
headers={"Content-Type": "application/json"},
|
|
max_retry=Config.MAX_RETRY,
|
|
retry_delay_ms=Config.RETRY_DELAY_MS,
|
|
provider="cms",
|
|
)
|
|
token = _extract_cms_token(login_result)
|
|
_CMS_TOKEN_CACHE["token"] = token
|
|
_CMS_TOKEN_CACHE["expires_at"] = int(time.time()) + 3500
|
|
return token
|
|
|
|
|
|
def _get_cached_token():
|
|
if Config.CMS_TOKEN:
|
|
return Config.CMS_TOKEN
|
|
if _CMS_TOKEN_CACHE["token"] and _CMS_TOKEN_CACHE["expires_at"] > int(time.time()):
|
|
return _CMS_TOKEN_CACHE["token"]
|
|
return _login_and_get_token()
|
|
|
|
|
|
def _should_refresh_token(response_data):
|
|
code = (response_data or {}).get("code")
|
|
msg = (response_data or {}).get("msg") or (response_data or {}).get("message") or ""
|
|
return code != 200 and msg != "提取分享链接失败"
|
|
|
|
|
|
def _add_share(url_value, token):
|
|
return request_json(
|
|
_resolve_add_share_url(),
|
|
method="POST",
|
|
payload={"url": url_value},
|
|
headers=_headers(token),
|
|
max_retry=Config.MAX_RETRY,
|
|
retry_delay_ms=Config.RETRY_DELAY_MS,
|
|
provider="cms",
|
|
)
|
|
|
|
|
|
def create_resource(payload):
|
|
resource = payload.get("resource") or {}
|
|
share_url = resource.get("unlockUrl") or payload.get("url")
|
|
if not share_url:
|
|
raise AppServiceError(
|
|
"CMS ingest requires unlockUrl",
|
|
category="validation",
|
|
code="CMS_INPUT_INVALID",
|
|
provider="cms",
|
|
)
|
|
|
|
token = _get_cached_token()
|
|
first_result = _add_share(share_url, token)
|
|
first_data = first_result.get("data") or {}
|
|
if _should_refresh_token(first_data):
|
|
refreshed_token = _login_and_get_token()
|
|
second_result = _add_share(share_url, refreshed_token)
|
|
second_data = second_result.get("data") or {}
|
|
if (second_data.get("code") or 0) != 200:
|
|
raise AppServiceError(
|
|
second_data.get("msg") or second_data.get("message") or "CMS ingest failed",
|
|
category="upstream",
|
|
code=str(second_data.get("code") or "CMS_INGEST_FAILED"),
|
|
provider="cms",
|
|
detail={"response": second_data},
|
|
)
|
|
return second_result
|
|
|
|
if (first_data.get("code") or 0) != 200:
|
|
raise AppServiceError(
|
|
first_data.get("msg") or first_data.get("message") or "CMS ingest failed",
|
|
category="business_rule"
|
|
if (first_data.get("msg") == "提取分享链接失败")
|
|
else "upstream",
|
|
code=str(first_data.get("code") or "CMS_INGEST_FAILED"),
|
|
provider="cms",
|
|
detail={"response": first_data},
|
|
)
|
|
return first_result
|