feat(server): modularize backend and admin console
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from app.core.security import admin_auth, token_digest
|
||||
from app.repositories import license_repository
|
||||
from app.schemas.license import LicenseCreateRequest, LicenseDeleteRequest, LicenseStatusRequest
|
||||
from app.services.common_service import validate_channel_code
|
||||
from app.services.license_service import decrypt_license_key, encrypt_license_key
|
||||
|
||||
|
||||
router = APIRouter(tags=["admin-license"])
|
||||
|
||||
|
||||
@router.post("/admin/license/create")
|
||||
def admin_license_create(body: LicenseCreateRequest, auth=Depends(admin_auth)):
|
||||
app_id = body.app_id.strip()
|
||||
channel = body.channel.strip()
|
||||
customer = body.customer_name.strip()
|
||||
max_devices = int(body.max_devices or 1)
|
||||
valid_until = body.valid_until.strip()
|
||||
try:
|
||||
expiry = datetime.fromisoformat(valid_until.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="valid_until 必须是 ISO-8601 时间")
|
||||
if not app_id or not customer or not validate_channel_code(channel) or max_devices < 1 or expiry <= datetime.now(timezone.utc):
|
||||
raise HTTPException(status_code=400, detail="授权参数无效")
|
||||
license_id = "lic_" + secrets.token_hex(12)
|
||||
license_key = "MARSCO-" + secrets.token_urlsafe(24)
|
||||
result = license_repository.insert_license(
|
||||
license_id,
|
||||
token_digest(license_key),
|
||||
encrypt_license_key(license_key),
|
||||
customer,
|
||||
app_id,
|
||||
channel,
|
||||
max_devices,
|
||||
valid_until,
|
||||
)
|
||||
if result == "channel_not_found":
|
||||
raise HTTPException(status_code=400, detail={"error": "channel_not_found", "msg": f"渠道 {channel} 不存在"})
|
||||
if result == "channel_disabled":
|
||||
raise HTTPException(status_code=403, detail={"error": "channel_disabled", "msg": f"渠道 {channel} 已停用"})
|
||||
return {"license_id": license_id, "license_key": license_key, "msg": "授权已创建;密钥已加密保存,可在授权列表中再次查看"}
|
||||
|
||||
|
||||
@router.get("/admin/license/list")
|
||||
def admin_license_list(app_id: str = "", include_deleted: bool = False, auth=Depends(admin_auth)):
|
||||
rows = license_repository.list_licenses(app_id, include_deleted)
|
||||
result = []
|
||||
for r in rows:
|
||||
license_key = decrypt_license_key(r["license_key_cipher"] if "license_key_cipher" in r.keys() else "")
|
||||
result.append(
|
||||
{k: r[k] for k in ("license_id", "customer_name", "app_id", "channel_code", "max_devices", "valid_until", "status", "created_at")}
|
||||
| {"used_devices": r["used_devices"], "license_key": license_key, "license_key_available": bool(license_key)}
|
||||
)
|
||||
return {"list": result}
|
||||
|
||||
|
||||
@router.post("/admin/license/set-status")
|
||||
def admin_license_set_status(body: LicenseStatusRequest, auth=Depends(admin_auth)):
|
||||
status = body.status
|
||||
license_id = body.license_id
|
||||
if status not in ("active", "disabled"):
|
||||
raise HTTPException(status_code=400, detail="授权状态无效")
|
||||
updated = license_repository.set_license_status(license_id, status)
|
||||
if updated != 1:
|
||||
raise HTTPException(status_code=404, detail="授权不存在")
|
||||
return {"msg": "授权状态已更新"}
|
||||
|
||||
|
||||
@router.post("/admin/license/delete")
|
||||
def admin_license_delete(body: LicenseDeleteRequest, auth=Depends(admin_auth)):
|
||||
license_id = body.license_id.strip()
|
||||
if not license_id:
|
||||
raise HTTPException(status_code=400, detail="license_id 不能为空")
|
||||
updated = license_repository.soft_delete_license(license_id)
|
||||
if updated != 1:
|
||||
raise HTTPException(status_code=404, detail="授权不存在")
|
||||
return {"msg": "授权已删除,历史记录仍会保留"}
|
||||
Reference in New Issue
Block a user