li / api /shop.py
xiaolongmr
套餐
fad8c19
Raw
History Blame Contribute Delete
9.78 kB
from __future__ import annotations
from fastapi import APIRouter, Header, HTTPException
from pydantic import BaseModel, Field
from api.support import require_admin, require_identity
from services.config import config
from services.auth_service import auth_service
from services.shop_service import shop_service
class RedeemRequest(BaseModel):
code: str = ""
class CodeBatchCreateRequest(BaseModel):
count: int = Field(default=1, ge=1, le=1000)
credits: int = Field(default=1, ge=1)
prefix: str = "CS"
batch: str = ""
note: str = ""
validity_days: int | None = Field(default=None, ge=1, le=3650)
credit_expires_days: int | None = Field(default=None, ge=1, le=3650)
batch_single_use: bool = False
code_type: str = "normal"
daily_gift_date: str = ""
package_name: str = ""
package_duration_days: int = Field(default=1, ge=1, le=3650)
package_daily_limit: int | None = Field(default=None, ge=0, le=1000000)
package_weekly_limit: int | None = Field(default=None, ge=0, le=1000000)
package_monthly_limit: int | None = Field(default=None, ge=0, le=1000000)
package_total_limit: int | None = Field(default=None, ge=0, le=1000000)
class CodeDisableRequest(BaseModel):
code_id: str = ""
class CodeCleanupRequest(BaseModel):
delete_expired: bool = True
delete_disabled: bool = False
archive_redeemed: bool = False
redeemed_older_than_days: int = Field(default=90, ge=1, le=3650)
limit: int = Field(default=1000, ge=1, le=20000)
dry_run: bool = True
class CreditAdjustRequest(BaseModel):
delta: int
note: str = ""
class ProfileUpdateRequest(BaseModel):
name: str = ""
def create_router() -> APIRouter:
router = APIRouter()
@router.get("/api/me/profile")
async def get_my_profile(authorization: str | None = Header(default=None)):
identity = require_identity(authorization)
profile = auth_service.get_profile(str(identity.get("id") or ""))
if profile is None and str(identity.get("role") or "").strip().lower() == "admin":
profile = {
"id": str(identity.get("id") or "admin"),
"name": str(identity.get("name") or "管理员"),
"role": "admin",
"created_at": None,
"account_pool_enabled": True,
"wechat_openid": "",
"wechat_bound_at": None,
}
if profile is None:
raise HTTPException(status_code=404, detail={"error": "当前用户不存在,请重新登录"})
return {"profile": profile}
@router.post("/api/me/profile")
async def update_my_profile(body: ProfileUpdateRequest, authorization: str | None = Header(default=None)):
identity = require_identity(authorization)
if str(identity.get("role") or "").strip().lower() != "normal":
raise HTTPException(status_code=403, detail={"error": "只有普通用户可以设置昵称"})
try:
item = auth_service.update_self_name(str(identity.get("id") or ""), body.name)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
return {"profile": auth_service.get_profile(str(item.get("id") or ""))}
@router.get("/api/me/credits")
async def get_my_credits(authorization: str | None = Header(default=None)):
identity = require_identity(authorization)
return {"credits": auth_service.credit_summary(identity)}
@router.get("/api/me/redeem-codes")
async def get_my_redeem_codes(authorization: str | None = Header(default=None)):
identity = require_identity(authorization)
if str(identity.get("role") or "").strip().lower() != "normal":
raise HTTPException(status_code=403, detail={"error": "只有普通用户可以查看自己的卡密"})
return {"items": shop_service.list_user_codes(str(identity.get("id") or ""))}
@router.get("/api/shop/public")
async def get_shop_public_config(authorization: str | None = Header(default=None)):
require_identity(authorization)
return {"purchase_url": config.shop_purchase_url}
@router.get("/api/me/api-key")
async def get_my_api_key(authorization: str | None = Header(default=None)):
identity = require_identity(authorization)
if str(identity.get("role") or "").strip().lower() not in {"normal", "user"}:
raise HTTPException(status_code=403, detail={"error": "只有微信用户或内部用户可以查看密钥"})
api_key = auth_service.get_api_key_display(str(identity.get("id") or ""))
if not api_key:
raise HTTPException(status_code=404, detail={"error": "当前用户密钥无法回显,请联系管理员重置密钥"})
return {"api_key": api_key}
@router.post("/api/redeem")
async def redeem_code(body: RedeemRequest, authorization: str | None = Header(default=None)):
identity = require_identity(authorization)
try:
return shop_service.redeem(body.code, identity)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
@router.post("/api/redeem/preview")
async def preview_redeem_code(body: RedeemRequest, authorization: str | None = Header(default=None)):
identity = require_identity(authorization)
try:
return shop_service.preview_code(body.code, identity)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
@router.get("/api/shop/codes")
async def list_codes(authorization: str | None = Header(default=None)):
require_admin(authorization)
return {"items": shop_service.list_codes()}
@router.post("/api/shop/codes")
async def create_codes(body: CodeBatchCreateRequest, authorization: str | None = Header(default=None)):
require_admin(authorization)
try:
return shop_service.generate_codes(
count=body.count,
credits=body.credits,
prefix=body.prefix,
batch=body.batch,
note=body.note,
validity_days=body.validity_days,
credit_expires_days=body.credit_expires_days,
batch_single_use=body.batch_single_use,
code_type=body.code_type,
daily_gift_date=body.daily_gift_date,
package_name=body.package_name,
package_duration_days=body.package_duration_days,
package_daily_limit=body.package_daily_limit,
package_weekly_limit=body.package_weekly_limit,
package_monthly_limit=body.package_monthly_limit,
package_total_limit=body.package_total_limit,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
@router.post("/api/shop/codes/disable")
async def disable_code(body: CodeDisableRequest, authorization: str | None = Header(default=None)):
require_admin(authorization)
try:
item = shop_service.disable_code(body.code_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
return {"item": item, "items": shop_service.list_codes()}
@router.post("/api/shop/codes/enable")
async def enable_code(body: CodeDisableRequest, authorization: str | None = Header(default=None)):
require_admin(authorization)
try:
item = shop_service.enable_code(body.code_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
return {"item": item, "items": shop_service.list_codes()}
@router.post("/api/shop/codes/delete")
async def delete_code(body: CodeDisableRequest, authorization: str | None = Header(default=None)):
require_admin(authorization)
try:
item = shop_service.delete_code(body.code_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
return {"item": item, "items": shop_service.list_codes()}
@router.post("/api/shop/codes/cleanup")
async def cleanup_codes(body: CodeCleanupRequest, authorization: str | None = Header(default=None)):
require_admin(authorization)
try:
return shop_service.cleanup_codes(
delete_expired=body.delete_expired,
delete_disabled=body.delete_disabled,
delete_redeemed=body.archive_redeemed,
redeemed_older_than_days=body.redeemed_older_than_days,
limit=body.limit,
dry_run=body.dry_run,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
@router.get("/api/shop/ledger")
async def list_ledger(authorization: str | None = Header(default=None)):
require_admin(authorization)
return {"items": shop_service.list_ledger()}
@router.post("/api/shop/users/{key_id}/credits")
async def adjust_user_credits(
key_id: str,
body: CreditAdjustRequest,
authorization: str | None = Header(default=None),
):
require_admin(authorization)
try:
item = shop_service.adjust_user_credits(key_id, body.delta, note=body.note)
except ValueError as exc:
raise HTTPException(status_code=400, detail={"error": str(exc)}) from exc
return {"item": item, "items": auth_service.list_keys(role="normal")}
return router