# retoor <retoor@molodetz.nl>
from typing import Annotated
from fastapi import APIRouter, Form, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from devplacepy.models import (
GameCosmeticForm,
GameInfraForm,
GameLegacyForm,
GameMasteryForm,
GamePerkForm,
GamePlantForm,
GameQuestForm,
GameSlotForm,
)
from devplacepy.database import mark_notifications_read_by_target
from devplacepy.responses import json_error, respond, wants_json
from devplacepy.schemas import GameLeaderboardOut, GameStateOut
from devplacepy.services.game import GameError, economy, store
from devplacepy.services.audit import record as audit
from devplacepy.utils import award_rewards, get_current_user, require_user, track_action
from ._shared import action_error, game_seo, notify_farm, state_payload
router = APIRouter()
@router.get("", response_class=HTMLResponse)
async def game_home(request: Request):
user = require_user(request)
mark_notifications_read_by_target(user["uid"], "/game")
farm = state_payload(user)
error = request.query_params.get("error", "")
seo_ctx = game_seo(
request,
"Code Farm",
"Grow software projects on your Code Farm, harvest coins and XP, "
"upgrade your CI, and water your friends' builds.",
)
return respond(
request,
"game.html",
{**seo_ctx, "request": request, "user": user, "farm": farm, "game_error": error},
model=GameStateOut,
)
@router.get("/state")
async def game_state(request: Request):
user = require_user(request)
return JSONResponse(GameStateOut(farm=state_payload(user)).model_dump(mode="json"))
@router.get("/leaderboard")
async def game_leaderboard(request: Request, board: str = "score"):
get_current_user(request)
entries = store.leaderboard_for(board, 25)
return JSONResponse(
GameLeaderboardOut(entries=entries).model_dump(mode="json")
)
async def _respond_action(request: Request, user: dict, fn, on_success=None):
try:
result = fn()
except GameError as exc:
return action_error(request, str(exc), "/game")
if on_success:
on_success(result)
await notify_farm(user.get("username", ""))
if wants_json(request):
return JSONResponse(
GameStateOut(farm=state_payload(user)).model_dump(mode="json")
)
return RedirectResponse(url="/game", status_code=302)
@router.post("/plant")
async def game_plant(request: Request, data: Annotated[GamePlantForm, Form()]):
user = require_user(request)
return await _respond_action(
request, user, lambda: store.plant(user, data.slot, data.crop)
)
@router.post("/harvest")
async def game_harvest(request: Request, data: Annotated[GameSlotForm, Form()]):
user = require_user(request)
def reward(result):
track_action(user["uid"], "harvest")
award_rewards(user["uid"], economy.site_xp_for(result.get("xp", 0)))
return await _respond_action(
request, user, lambda: store.harvest(user, data.slot), reward
)
@router.post("/buy-plot")
async def game_buy_plot(request: Request):
user = require_user(request)
return await _respond_action(request, user, lambda: store.buy_plot(user))
@router.post("/upgrade")
async def game_upgrade(request: Request):
user = require_user(request)
return await _respond_action(request, user, lambda: store.upgrade_ci(user))
@router.post("/fertilize")
async def game_fertilize(request: Request, data: Annotated[GameSlotForm, Form()]):
user = require_user(request)
return await _respond_action(request, user, lambda: store.fertilize(user, data.slot))
@router.post("/daily")
async def game_daily(request: Request):
user = require_user(request)
return await _respond_action(request, user, lambda: store.claim_daily(user))
@router.post("/grant")
async def game_claim_grant(request: Request):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.grant.claim",
user=user,
metadata=result,
summary=f"{user['username']} claimed a {result['amount']} coin community grant",
)
return await _respond_action(request, user, lambda: store.claim_grant(user), recorded)
@router.post("/perk")
async def game_perk(request: Request, data: Annotated[GamePerkForm, Form()]):
user = require_user(request)
return await _respond_action(
request, user, lambda: store.upgrade_perk(user, data.perk)
)
@router.post("/prestige")
async def game_prestige(request: Request):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.prestige",
user=user,
metadata=result,
summary=(
f"{user['username']} refactored to prestige {result['prestige']} "
f"for {result['fee']} coins"
),
)
return await _respond_action(request, user, lambda: store.prestige(user), recorded)
@router.post("/legacy")
async def game_legacy(request: Request, data: Annotated[GameLegacyForm, Form()]):
user = require_user(request)
return await _respond_action(
request, user, lambda: store.upgrade_legacy(user, data.key)
)
@router.post("/quests/claim")
async def game_claim_quest(request: Request, data: Annotated[GameQuestForm, Form()]):
user = require_user(request)
def reward(result):
award_rewards(user["uid"], economy.site_xp_for(result.get("reward_xp", 0)))
return await _respond_action(
request, user, lambda: store.claim_quest(user, data.quest, data.scope), reward
)
@router.post("/defense/upgrade")
async def game_upgrade_defense(request: Request):
user = require_user(request)
def reward(result):
track_action(user["uid"], "defense_upgraded")
audit.record(
request,
"game.defense.upgrade",
user=user,
metadata=result,
summary=(
f"{user['username']} bought Code Farm defense level "
f"{result['defense_level']} for {result['spent']} coins"
),
)
return await _respond_action(request, user, lambda: store.upgrade_defense(user), reward)
@router.post("/defense/downgrade")
async def game_downgrade_defense(request: Request):
user = require_user(request)
def recorded(result):
audit.record(
request,
"game.defense.downgrade",
user=user,
metadata=result,
summary=(
f"{user['username']} dropped Code Farm defense to level "
f"{result['defense_level']}"
),
)
return await _respond_action(
request, user, lambda: store.downgrade_defense(user), recorded
)
@router.post("/infrastructure/buy")
async def game_buy_infrastructure(request: Request, data: Annotated[GameInfraForm, Form()]):
user = require_user(request)
def reward(result):
track_action(user["uid"], "infra_bought")
audit.record(
request,
"game.infrastructure.buy",
user=user,
metadata=result,
summary=(
f"{user['username']} bought Code Farm infrastructure {result['key']} "
f"for {result['spent']} coins"
),
)
return await _respond_action(
request, user, lambda: store.buy_infrastructure(user, data.key), reward
)
@router.post("/mastery")
async def game_upgrade_mastery(request: Request, data: Annotated[GameMasteryForm, Form()]):
user = require_user(request)
return await _respond_action(
request, user, lambda: store.upgrade_mastery(user, data.key)
)
@router.post("/cosmetics/buy")
async def game_buy_cosmetic(request: Request, data: Annotated[GameCosmeticForm, Form()]):
user = require_user(request)
def reward(result):
track_action(user["uid"], "cosmetic_bought")
audit.record(
request,
"game.cosmetic.buy",
user=user,
metadata=result,
summary=(
f"{user['username']} bought Code Farm cosmetic {result['key']} "
f"for {result['spent']} coins"
),
)
return await _respond_action(
request, user, lambda: store.buy_cosmetic(user, data.key), reward
)
@router.post("/cosmetics/equip")
async def game_equip_cosmetic(request: Request, data: Annotated[GameCosmeticForm, Form()]):
user = require_user(request)
return await _respond_action(
request, user, lambda: store.equip_title(user, data.key)
)