Code Farm economy rebalance: market saturation, infrastructure/defense/cosmetics, mastery track, secondary leaderboards, admin eras, underdog bonus and weekly contracts
Every purchase/upgrade path (new and pre-existing) is now race-safe against concurrent requests via atomic conditional SQL updates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import logging
|
||||
from typing import Annotated
|
||||
from fastapi import APIRouter, Form, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from devplacepy.models import GameEraStartForm
|
||||
from devplacepy.responses import respond, action_result, json_error, wants_json
|
||||
from devplacepy.schemas import AdminGameOut
|
||||
from devplacepy.seo import base_seo_context, site_url, website_schema
|
||||
from devplacepy.services.audit import record as audit
|
||||
from devplacepy.services.game import GameError, store
|
||||
from devplacepy.utils import require_admin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _era_context() -> dict:
|
||||
era = store.active_era()
|
||||
return {
|
||||
"era_active": bool(era),
|
||||
"era_name": era["name"] if era else "",
|
||||
"era_number": int(era["era_number"]) if era else 0,
|
||||
"era_started_at": era["started_at"] if era else "",
|
||||
"era_ends_at": era["ends_at"] if era else "",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/game", response_class=HTMLResponse)
|
||||
async def admin_game(request: Request):
|
||||
admin = require_admin(request)
|
||||
base = site_url(request)
|
||||
seo_ctx = base_seo_context(
|
||||
request,
|
||||
title="Code Farm - Admin",
|
||||
description="Manage Code Farm Eras.",
|
||||
robots="noindex,nofollow",
|
||||
breadcrumbs=[
|
||||
{"name": "Home", "url": "/feed"},
|
||||
{"name": "Admin", "url": "/admin"},
|
||||
{"name": "Code Farm", "url": "/admin/game"},
|
||||
],
|
||||
schemas=[website_schema(base)],
|
||||
)
|
||||
return respond(
|
||||
request,
|
||||
"admin_game.html",
|
||||
{
|
||||
**seo_ctx,
|
||||
"request": request,
|
||||
"user": admin,
|
||||
"admin_section": "game",
|
||||
**_era_context(),
|
||||
},
|
||||
model=AdminGameOut,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/game/era/start")
|
||||
async def admin_game_era_start(request: Request, data: Annotated[GameEraStartForm, Form()]):
|
||||
admin = require_admin(request)
|
||||
try:
|
||||
era = store.start_era(data.name, data.duration_days)
|
||||
except GameError as exc:
|
||||
logger.warning(f"Admin {admin['username']} failed to start Era: {exc}")
|
||||
if wants_json(request):
|
||||
return json_error(400, str(exc))
|
||||
return action_result(request, "/admin/game")
|
||||
audit.record(
|
||||
request,
|
||||
"admin.game.era_start",
|
||||
user=admin,
|
||||
metadata={"era_number": era["era_number"], "name": era["name"]},
|
||||
summary=f"admin {admin['username']} started Era {era['name']}",
|
||||
)
|
||||
return action_result(request, "/admin/game")
|
||||
|
||||
|
||||
@router.post("/game/era/end")
|
||||
async def admin_game_era_end(request: Request):
|
||||
admin = require_admin(request)
|
||||
try:
|
||||
result = store.end_era()
|
||||
except GameError as exc:
|
||||
logger.warning(f"Admin {admin['username']} failed to end Era: {exc}")
|
||||
if wants_json(request):
|
||||
return json_error(400, str(exc))
|
||||
return action_result(request, "/admin/game")
|
||||
audit.record(
|
||||
request,
|
||||
"admin.game.era_end",
|
||||
user=admin,
|
||||
metadata=result,
|
||||
summary=f"admin {admin['username']} ended Era {result['era_number']}",
|
||||
)
|
||||
return action_result(request, "/admin/game")
|
||||
Reference in New Issue
Block a user