Files

358 lines
11 KiB
Python
Raw Permalink Normal View History

2026-05-14 20:01:26 +08:00
"""Operations/admin API routes."""
from fastapi import APIRouter, Request, Response
2026-05-14 20:01:26 +08:00
2026-06-08 20:54:20 +08:00
from web.core import FeedbackRewardRequest, GrantPointsRequest
2026-05-14 20:01:26 +08:00
from web.services.ops_api import (
2026-06-23 17:07:25 +08:00
create_ops_refund_case,
extend_ops_subscription,
2026-05-14 20:01:26 +08:00
get_ops_analytics_funnel,
2026-05-31 03:21:58 +08:00
get_ops_billing_risk,
get_ops_config,
2026-05-30 20:33:30 +08:00
get_ops_sensitive_config,
get_ops_memberships_growth,
2026-05-29 17:22:33 +08:00
get_ops_memberships_overview,
get_ops_health_check,
get_ops_logs,
2026-06-08 14:39:49 +08:00
get_ops_observation_collector_status,
2026-05-31 03:21:58 +08:00
get_ops_source_health,
2026-05-14 20:01:26 +08:00
get_ops_truth_history,
get_ops_weekly_leaderboard,
2026-06-23 17:07:25 +08:00
list_ops_audit_log,
2026-06-01 21:10:37 +08:00
list_ops_feedback,
2026-06-23 17:07:25 +08:00
list_ops_refund_cases,
2026-05-20 14:12:35 +08:00
get_ops_user_subscriptions,
2026-05-14 20:01:26 +08:00
grant_ops_points,
transfer_ops_points,
grant_ops_subscription,
2026-05-14 20:01:26 +08:00
list_ops_memberships,
list_ops_payment_incidents,
resolve_ops_payment_incident,
2026-06-23 17:07:25 +08:00
update_ops_refund_case,
2026-05-20 14:12:35 +08:00
list_ops_payments,
2026-05-14 20:01:26 +08:00
search_ops_users,
update_ops_config,
2026-06-08 20:54:20 +08:00
grant_ops_feedback_reward,
2026-05-30 20:33:30 +08:00
update_ops_sensitive_config,
2026-06-01 21:10:37 +08:00
update_ops_feedback_status,
get_ops_training_accuracy,
2026-05-20 17:12:32 +08:00
get_ops_telegram_audit,
2026-05-14 20:01:26 +08:00
)
from web.services.request_timing import ServerTimingRecorder, attach_server_timing_header
2026-05-14 20:01:26 +08:00
router = APIRouter(tags=["ops"])
2026-05-26 08:40:17 +08:00
@router.get("/api/ops/online-users")
async def ops_online_users(request: Request, response: Response):
timer = ServerTimingRecorder(
request,
log_name="ops_online_users_timing",
prefix="ops_online_users",
state_attr="ops_online_users_server_timing",
)
outcome = "ok"
status_code = 200
try:
from src.utils.online_tracker import online_count
return {"online": timer.measure("online_count", online_count)}
except Exception:
outcome = "exception"
status_code = 500
raise
finally:
timer.finish(outcome=outcome, status_code=status_code)
attach_server_timing_header(
response,
request,
"ops_online_users_server_timing",
)
2026-05-26 08:40:17 +08:00
2026-05-14 20:01:26 +08:00
@router.get("/api/ops/users")
async def ops_search_users(request: Request, q: str = "", limit: int = 20):
return search_ops_users(request, q=q, limit=limit)
@router.get("/api/ops/leaderboard/weekly")
async def ops_weekly_leaderboard(request: Request, limit: int = 20):
return get_ops_weekly_leaderboard(request, limit=limit)
2026-06-23 17:07:25 +08:00
@router.get("/api/ops/audit-log")
async def ops_audit_log(
request: Request,
limit: int = 100,
action: str = "",
actor_email: str = "",
target_user_id: str = "",
):
return list_ops_audit_log(
request,
limit=limit,
action=action,
actor_email=actor_email,
target_user_id=target_user_id,
)
2026-06-01 21:10:37 +08:00
@router.get("/api/ops/feedback")
async def ops_feedback(request: Request, limit: int = 100, status: str = ""):
return list_ops_feedback(request, limit=limit, status=status)
@router.post("/api/ops/feedback/{feedback_id}/status")
async def ops_feedback_update_status(request: Request, feedback_id: int):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8") or "{}")
status = str(body.get("status") or "").strip()
return update_ops_feedback_status(request, feedback_id=feedback_id, status=status)
2026-06-08 20:54:20 +08:00
@router.post("/api/ops/feedback/{feedback_id}/reward")
async def ops_feedback_grant_reward(
request: Request,
feedback_id: int,
body: FeedbackRewardRequest,
):
return grant_ops_feedback_reward(
request,
feedback_id=feedback_id,
points=body.points,
reason=body.reason,
)
2026-05-14 20:01:26 +08:00
@router.get("/api/ops/memberships")
async def ops_memberships(request: Request, limit: int = 200):
return list_ops_memberships(request, limit=limit)
@router.get("/api/ops/memberships/growth")
async def ops_memberships_growth(request: Request, days: int = 90):
return get_ops_memberships_growth(request, days=days)
2026-05-29 17:22:33 +08:00
@router.get("/api/ops/memberships/overview")
async def ops_memberships_overview(
request: Request,
limit: int = 200,
days: int = 90,
):
return get_ops_memberships_overview(request, limit=limit, days=days)
2026-05-14 20:01:26 +08:00
@router.get("/api/ops/payments/incidents")
async def ops_payment_incidents(
request: Request,
limit: int = 50,
reason: str = "",
include_resolved: bool = False,
):
return list_ops_payment_incidents(
request,
limit=limit,
reason=reason,
include_resolved=include_resolved,
)
@router.post("/api/ops/payments/incidents/{event_id}/resolve")
async def ops_resolve_payment_incident(request: Request, event_id: int):
return resolve_ops_payment_incident(request, event_id)
2026-05-20 14:12:35 +08:00
@router.get("/api/ops/payments")
async def ops_payments(request: Request, limit: int = 50):
return list_ops_payments(request, limit=limit)
2026-06-23 17:07:25 +08:00
@router.get("/api/ops/refunds")
async def ops_refunds(request: Request, limit: int = 50, status: str = ""):
return list_ops_refund_cases(request, limit=limit, status=status)
@router.post("/api/ops/refunds")
async def ops_refund_create(request: Request):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8") or "{}")
return create_ops_refund_case(
request,
reason=str(body.get("reason") or "").strip(),
intent_id=str(body.get("intent_id") or "").strip(),
tx_hash=str(body.get("tx_hash") or "").strip(),
user_id=str(body.get("user_id") or "").strip(),
amount_usdc=str(body.get("amount_usdc") or "").strip(),
note=str(body.get("note") or "").strip(),
)
@router.patch("/api/ops/refunds/{case_id}")
async def ops_refund_update(request: Request, case_id: int):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8") or "{}")
return update_ops_refund_case(
request,
case_id=case_id,
status=str(body.get("status") or "").strip(),
note=str(body.get("note") or "").strip(),
)
2026-05-31 03:21:58 +08:00
@router.get("/api/ops/billing-risk")
async def ops_billing_risk(request: Request, days: int = 30, limit: int = 80):
return get_ops_billing_risk(request, days=days, limit=limit)
2026-05-14 20:01:26 +08:00
@router.post("/api/ops/users/grant-points")
async def ops_grant_points(request: Request, body: GrantPointsRequest):
return grant_ops_points(request, body)
@router.post("/api/ops/users/transfer-points")
async def ops_transfer_points(request: Request):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8"))
from_email = str(body.get("from_email") or "").strip()
to_email = str(body.get("to_email") or "").strip()
amount = int(body.get("amount") or 0)
return transfer_ops_points(request, from_email=from_email, to_email=to_email, amount=amount)
2026-05-14 20:01:26 +08:00
@router.get("/api/ops/analytics/funnel")
async def ops_analytics_funnel(request: Request, days: int = 30):
return get_ops_analytics_funnel(request, days=days)
@router.get("/api/ops/truth-history")
async def ops_truth_history(
request: Request,
city: str = "",
date_from: str = "",
date_to: str = "",
limit: int = 200,
):
return get_ops_truth_history(
request,
city=city,
date_from=date_from,
date_to=date_to,
limit=limit,
)
# ── Config ──────────────────────────────────────────────────────────
@router.get("/api/ops/config")
async def ops_config(request: Request):
return get_ops_config(request)
@router.put("/api/ops/config")
async def ops_update_config(request: Request):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8"))
key = str(body.get("key") or "").strip()
value = str(body.get("value") or "")
if not key:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail="key is required")
return update_ops_config(request, key, value)
2026-05-30 20:33:30 +08:00
@router.get("/api/ops/sensitive-config")
async def ops_sensitive_config(request: Request):
return get_ops_sensitive_config(request)
@router.put("/api/ops/sensitive-config")
async def ops_update_sensitive_config(request: Request):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8"))
key = str(body.get("key") or "").strip()
value = str(body.get("value") or "")
if not key:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail="key is required")
return update_ops_sensitive_config(request, key, value)
# ── Subscriptions ───────────────────────────────────────────────────
@router.post("/api/ops/subscriptions/grant")
async def ops_subscription_grant(request: Request):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8"))
email = str(body.get("email") or "").strip()
plan_code = str(body.get("plan_code") or "pro_monthly").strip()
days = int(body.get("days") or 30)
deduct_points = int(body.get("deduct_points") or 0)
return grant_ops_subscription(request, email=email, plan_code=plan_code, days=days, deduct_points=deduct_points)
@router.post("/api/ops/subscriptions/extend")
async def ops_subscription_extend(request: Request):
import json as _json
body_bytes = await request.body()
body = _json.loads(body_bytes.decode("utf-8"))
email = str(body.get("email") or "").strip()
days = int(body.get("additional_days") or 30)
return extend_ops_subscription(request, email=email, additional_days=days)
2026-05-20 14:12:35 +08:00
@router.get("/api/ops/subscriptions/user")
async def ops_user_subscriptions(request: Request, email: str = ""):
return get_ops_user_subscriptions(request, email=email)
# ── Logs ────────────────────────────────────────────────────────────
@router.get("/api/ops/logs")
async def ops_logs(
request: Request,
level: str = "",
lines: int = 100,
):
return get_ops_logs(request, level=level, lines=lines)
@router.get("/api/ops/health-check")
async def ops_health_check(request: Request):
return get_ops_health_check(request)
2026-05-31 03:21:58 +08:00
@router.get("/api/ops/source-health")
async def ops_source_health(
request: Request,
cities: str = "",
limit: int = 80,
):
return get_ops_source_health(request, cities=cities, limit=limit)
2026-06-08 14:39:49 +08:00
@router.get("/api/ops/observation-collector-status")
async def ops_observation_collector_status(
request: Request,
limit: int = 200,
):
return get_ops_observation_collector_status(request, limit=limit)
@router.get("/api/ops/training/accuracy")
async def ops_training_accuracy(request: Request):
return get_ops_training_accuracy(request)
2026-05-20 17:12:32 +08:00
@router.get("/api/ops/telegram/members-audit")
async def ops_telegram_audit(request: Request):
return get_ops_telegram_audit(request)