Pages détail numérique : sommaire flottant, nav ctx, shadoks geek, contenu enrichi
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- [slug].vue : sommaire sticky (overflow:clip sur parent), prev/next en haut, 6 shadoks geek (pinguin+USB, web-of-trust, rubber-duck, caféine, debugger loupe, rack serveur) - Nouveaux types de sections : territoire (bouquet sweethomeCloud, 2 modèles éco, tableau matériel dépliable), projet (carte gestation) - cloud-libre.yml : section sweethomeCloud complète avec infra 50 000 hab. (~2€/an/hab) - authentification-wot.yml : trustWallet, correction WoT Duniter (Ed25519+Scrypt, sigQty=5, stepMax=3), DID/VC standards - logiciel-libre.yml : carte projet wishBounty - home.yml + numerique.yml : cloud-libre → sweethomeCloud, description RGPD/local-first - AxisBlock.vue : bulles de présentation inline dans les cards (plus de tooltip absolu) - Analytics : useTracking.ts (Umami), docker-compose.umami.yml, /api/stats fédération - nuxt.config.ts : config Umami runtime Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
43
server/api/stats/index.get.ts
Normal file
43
server/api/stats/index.get.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* GET /api/stats
|
||||
* Public stats endpoint — proxies Umami for cross-instance federation / observatoires.
|
||||
* Each librodrome instance exposes its own metrics here.
|
||||
* Observatoires call this endpoint on each instance and aggregate.
|
||||
*
|
||||
* Env vars required (private, server-side):
|
||||
* NUXT_UMAMI_API_KEY — Umami API key (read-only)
|
||||
* NUXT_UMAMI_WEBSITE_ID — Umami website ID (internal, server-side)
|
||||
* NUXT_PUBLIC_UMAMI_URL — Umami base URL
|
||||
* NUXT_PUBLIC_TENANT_ID — e.g. "librodrome" or "librodrome-bordeaux"
|
||||
*/
|
||||
export default defineEventHandler(async () => {
|
||||
const config = useRuntimeConfig()
|
||||
const { umamiApiKey } = config
|
||||
const { umamiUrl, umamiWebsiteId, tenantId } = config.public
|
||||
|
||||
if (!umamiApiKey || !umamiUrl || !umamiWebsiteId) {
|
||||
return { tenant: tenantId, configured: false }
|
||||
}
|
||||
|
||||
const endAt = Date.now()
|
||||
const startAt = endAt - 30 * 24 * 60 * 60 * 1000 // 30 days
|
||||
|
||||
const [stats, pageviews] = await Promise.all([
|
||||
$fetch<Record<string, unknown>>(`${umamiUrl}/api/websites/${umamiWebsiteId}/stats`, {
|
||||
headers: { 'x-umami-api-key': umamiApiKey },
|
||||
query: { startAt, endAt },
|
||||
}).catch(() => null),
|
||||
$fetch<Record<string, unknown>>(`${umamiUrl}/api/websites/${umamiWebsiteId}/pageviews`, {
|
||||
headers: { 'x-umami-api-key': umamiApiKey },
|
||||
query: { startAt, endAt, unit: 'day', timezone: 'Europe/Paris' },
|
||||
}).catch(() => null),
|
||||
])
|
||||
|
||||
return {
|
||||
tenant: tenantId,
|
||||
configured: true,
|
||||
period: '30d',
|
||||
stats,
|
||||
pageviews,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user