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()
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/static/icon.svg" />
|
||||
<title>TG Spam Guard</title>
|
||||
<script type="module" crossorigin src="/static/assets/index-B7wPhEjS.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/index-zEILxixw.css">
|
||||
<script type="module" crossorigin src="/static/assets/index-CfwMEsjZ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/index-CCHZcUDy.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Reference in New Issue
Block a user