Files
update-server/app/api/routes/admin_license.py
T

82 lines
3.7 KiB
Python

import secrets
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from app.core.security import require_permission, 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(require_permission("license:manage"))):
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(require_permission("license:view"))):
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(require_permission("license:manage"))):
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(require_permission("license:manage"))):
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": "授权已删除,历史记录仍会保留"}