Some checks failed
Deploy XIP / deploy (push) Failing after 37s
Fonctionnel
- Backend messages : GET /api/messages/:id (detail) + recherche (q),
pagination par curseur (before/limit) avec enveloppe { items, nextCursor,
hasMore } ; le flux temps reel garde l'ancien format quand aucun parametre.
- Explorer (/explorer) : catalogue distant, recherche debouncee + annulable
(AbortController), filtre, defilement infini, etat garde (keep-alive).
- Details par id : /message/:id et /shop/p/:id (consomment route.params).
- Favoris (/favoris) : liste perso persistee en localStorage, notation
(note/rating/statut) via modale, refletee partout (bouton favori).
- Mes stats (/mes-stats) : agregats derives des favoris (note moyenne, top
pays/auteurs, statuts), auto-mis a jour, route gardee si liste vide.
- Routeur : pages secondaires en lazy-load + repli, garde beforeEnter.
Technique
- Slots : PrefSection (slot defaut + slot nomme) enveloppe les 5 sections
"Mes Persos" ; Modal (Teleport + slots).
- v-model custom : SearchBox (defineModel + debounce).
- Directive custom : v-click-outside.
- Tests Vitest : 25 tests (etat, fonctions, composants), ~86% du code metier.
- Retrait d'Ionic (inutilise). Script typecheck backend ; tsconfig @types/bun.
- Correctif type : garde stockLimit nullable dans l'achat (catalog.ts).
- README complet (URL, stack, run, tests, secrets, deploiement, mention IA).
41 lines
1.1 KiB
TypeScript
41 lines
1.1 KiB
TypeScript
import { Hono } from "hono";
|
|
import { listActiveAds, recordImpressions } from "../lib/ads";
|
|
|
|
const ads = new Hono();
|
|
|
|
// GET /api/ads?kind=band → active ad set for that slot (client rotates).
|
|
ads.get("/", async (c) => {
|
|
const kind = c.req.query("kind") === "casino" ? "casino" : "band";
|
|
const list = await listActiveAds(kind);
|
|
// Expose only what the UI needs.
|
|
return c.json(
|
|
list.map((a) => ({
|
|
id: a.id,
|
|
brand: a.brand,
|
|
subtitle: a.subtitle,
|
|
url: a.url,
|
|
cta: a.cta,
|
|
icon: a.icon,
|
|
tone: a.tone,
|
|
kind: a.kind,
|
|
ownerIp: a.ownerIp,
|
|
imageUrl: a.imageUrl,
|
|
}))
|
|
);
|
|
});
|
|
|
|
// POST /api/ads/impressions { ids: [...] }
|
|
ads.post("/impressions", async (c) => {
|
|
let body: { ids?: string[] } = {};
|
|
try {
|
|
body = await c.req.json();
|
|
} catch {
|
|
return c.json({ error: "JSON invalide" }, 400);
|
|
}
|
|
const ids = Array.isArray(body.ids) ? body.ids.filter((x) => typeof x === "string") : [];
|
|
await recordImpressions(ids);
|
|
return c.json({ ok: true, counted: ids.length });
|
|
});
|
|
|
|
export default ads;
|