feat: add modal shop and lottery management

This commit is contained in:
ngfchl
2026-07-07 13:22:33 +08:00
parent a6923924e7
commit 2ee9fc575c
11 changed files with 933 additions and 55 deletions
+87 -2
View File
@@ -12,10 +12,10 @@ from fastapi import FastAPI, Query, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from db.models import Action, Ban, GameScore, Message, LotteryEvent, ShopItem
from db.models import Action, Ban, GameScore, Message, LotteryEvent, LotteryParticipant, ShopItem, ShopTransaction
from services.stats_service import get_daily_stats
from services.time_utils import format_dt, from_timestamp, now_tz
from services.game_service import list_shop_items, upsert_shop_item, draw_lottery_event
from services.game_service import list_shop_items, upsert_shop_item, delete_shop_item, list_shop_transactions, draw_lottery_event
BASE_DIR = Path(__file__).parent
@@ -117,6 +117,17 @@ async def api_stats(day: str = Query(default=None, pattern=r"^\d{4}-\d{2}-\d{2}$
return JSONResponse(await get_daily_stats(day, chat_id=chat_id))
def parse_draw_at(value):
if not value:
return None
try:
dt = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
if dt.tzinfo is None:
dt = dt.replace(tzinfo=now_tz().tzinfo)
return dt
except Exception:
return None
def normalize_message(row: dict) -> dict:
row = dict(row)
row["time"] = row.get("created_at")
@@ -213,6 +224,17 @@ async def api_save_shop_item(request: Request):
return JSONResponse(json_safe({"ok": True, "item": {"id": item.id, "chat_id": item.chat_id, "item_key": item.item_key, "name": item.name, "cost": item.cost, "desc": item.desc, "enabled": item.enabled}}))
@app.delete("/api/shop-items/{item_key}")
async def api_delete_shop_item(item_key: str, chat_id: int = 0):
ok = await delete_shop_item(chat_id, item_key)
return JSONResponse({"ok": ok})
@app.get("/api/shop-transactions")
async def api_shop_transactions(chat_id: int = 0, limit: int = Query(default=100, ge=1, le=500)):
return JSONResponse(json_safe(await list_shop_transactions(chat_id, limit)))
@app.get("/api/lotteries")
async def api_lotteries(chat_id: int | None = None, limit: int = Query(default=100, ge=1, le=300)):
qs = LotteryEvent.all()
@@ -222,6 +244,69 @@ async def api_lotteries(chat_id: int | None = None, limit: int = Query(default=1
return JSONResponse(json_safe(list(rows)))
@app.post("/api/lotteries")
async def api_create_lottery(request: Request):
data = await request.json()
event = await LotteryEvent.create(
chat_id=int(data.get("chat_id") or 0),
creator_id=int(data.get("creator_id") or 0),
creator_name=str(data.get("creator_name") or "web-admin"),
title=str(data.get("title") or data.get("prize") or "抽奖活动"),
description=str(data.get("description") or ""),
prize=str(data.get("prize") or data.get("title") or "奖品"),
condition_type=str(data.get("condition_type") or "all"),
condition_value=int(data.get("condition_value") or 0),
draw_type=str(data.get("draw_type") or data.get("end_type") or "manual"),
draw_at=parse_draw_at(data.get("draw_at")),
end_type=str(data.get("end_type") or data.get("draw_type") or "manual"),
end_value=int(data.get("end_value") or data.get("max_participants") or 0),
max_participants=int(data.get("max_participants") or 0),
status=str(data.get("status") or "draft"),
)
return JSONResponse(json_safe({"ok": True, "event": {
"id": event.id, "chat_id": event.chat_id, "title": event.title, "description": event.description,
"prize": event.prize, "condition_type": event.condition_type, "condition_value": event.condition_value,
"draw_type": event.draw_type, "end_type": event.end_type, "end_value": event.end_value,
"max_participants": event.max_participants, "draw_at": event.draw_at, "status": event.status, "created_at": event.created_at,
}}))
@app.put("/api/lotteries/{event_id}")
async def api_update_lottery(event_id: int, request: Request):
event = await LotteryEvent.filter(id=event_id).first()
if not event:
return JSONResponse({"ok": False, "error": "抽奖不存在"}, status_code=404)
data = await request.json()
for key in ["title", "description", "prize", "condition_type", "draw_type", "end_type", "status"]:
if key in data:
setattr(event, key, str(data.get(key) or ""))
if "draw_at" in data:
event.draw_at = parse_draw_at(data.get("draw_at"))
for key in ["condition_value", "end_value", "max_participants"]:
if key in data:
setattr(event, key, int(data.get(key) or 0))
await event.save()
return JSONResponse(json_safe({"ok": True, "event": {
"id": event.id, "chat_id": event.chat_id, "title": event.title, "description": event.description,
"prize": event.prize, "condition_type": event.condition_type, "condition_value": event.condition_value,
"draw_type": event.draw_type, "end_type": event.end_type, "end_value": event.end_value,
"max_participants": event.max_participants, "draw_at": event.draw_at, "status": event.status, "created_at": event.created_at,
}}))
@app.delete("/api/lotteries/{event_id}")
async def api_delete_lottery(event_id: int):
await LotteryParticipant.filter(event_id=event_id).delete()
ok = await LotteryEvent.filter(id=event_id).delete()
return JSONResponse({"ok": bool(ok)})
@app.get("/api/lotteries/{event_id}/participants")
async def api_lottery_participants(event_id: int):
rows = await LotteryParticipant.filter(event_id=event_id).order_by("created_at").values()
return JSONResponse(json_safe(list(rows)))
@app.post("/api/lotteries/{event_id}/cancel")
async def api_cancel_lottery(event_id: int):
event = await LotteryEvent.filter(id=event_id).first()