feat: add modal shop and lottery management
This commit is contained in:
+87
-2
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user