feat: rozšíření chytání motýlků o progres, mince a škůdce
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 23s
CI / Build server (push) Successful in 27s
CI / Build client (push) Successful in 46s
CI / Playwright E2E tests (push) Successful in 1m25s
CI / Build and push Docker image (push) Successful in 44s
CI / Notify (push) Successful in 2s

- serverová perzistence statistik (mince, úrovně, zlatí motýli) + týmový žebříček
- prémiová zlatá síťka za milník (větší, magnetická, odolná proti ptákům)
- perzistentní škůdci: vosy na zaklikání a ptáci trhající síťku (F5-proof)
- placený plašič ptáků s odpočtem, denní bonus, kombo
- WOW zlatý motýl + osobní statistiky v modalu žebříčku
- oprava zobrazení ikonek (root-absolutní cesty k public assetům)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stánek Pavel
2026-07-22 11:59:17 +02:00
co-authored by Claude Opus 4.8
parent c8c5ecc60c
commit c5ca6c9d41
25 changed files with 2454 additions and 205 deletions
+88
View File
@@ -0,0 +1,88 @@
import express, { Request } from "express";
import { getLogin } from "../auth";
import { parseToken } from "../utils";
import {
getStats,
recordCatches,
repairNet,
killWasp,
buyRepellent,
reportNetTorn,
getLeaderboard,
InsufficientCoinsError,
} from "../butterflies";
import { CatchButterfliesData } from "../../../types";
const router = express.Router();
router.get("/", async (req: Request, res, next) => {
try {
const login = getLogin(parseToken(req));
const data = await getStats(login);
res.status(200).json(data);
} catch (e: any) { next(e) }
});
router.post("/catch", async (req: Request<{}, any, CatchButterfliesData["body"]>, res, next) => {
try {
const login = getLogin(parseToken(req));
if (typeof req.body?.normal !== 'number' || typeof req.body?.golden !== 'number') {
return res.status(400).json({ error: "Chybné parametry volání" });
}
const data = await recordCatches(login, req.body.normal, req.body.golden);
res.status(200).json(data);
} catch (e: any) { next(e) }
});
router.post("/repair", async (req: Request, res, next) => {
try {
const login = getLogin(parseToken(req));
const data = await repairNet(login);
res.status(200).json(data);
} catch (e: any) {
if (e instanceof InsufficientCoinsError) {
return res.status(402).json({ error: e.message });
}
next(e);
}
});
router.post("/killWasp", async (req: Request, res, next) => {
try {
const login = getLogin(parseToken(req));
const data = await killWasp(login);
res.status(200).json(data);
} catch (e: any) { next(e) }
});
router.post("/repellent", async (req: Request, res, next) => {
try {
const login = getLogin(parseToken(req));
const data = await buyRepellent(login);
res.status(200).json(data);
} catch (e: any) {
if (e instanceof InsufficientCoinsError) {
return res.status(402).json({ error: e.message });
}
next(e);
}
});
router.post("/tear", async (req: Request, res, next) => {
try {
const login = getLogin(parseToken(req));
const data = await reportNetTorn(login);
res.status(200).json(data);
} catch (e: any) { next(e) }
});
router.get("/leaderboard", async (req: Request, res, next) => {
try {
getLogin(parseToken(req));
const limit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : undefined;
const data = await getLeaderboard(Number.isFinite(limit) ? limit : undefined);
res.status(200).json(data);
} catch (e: any) { next(e) }
});
export default router;