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": "授权已删除,历史记录仍会保留"}