Files
anouma/collections/AnalyticsDailyStats.ts
T
maro 65b6eeac51 Add privacy-friendly analytics and cookie consent, integrated into the CMS
Adds a first-party analytics system with no third-party trackers and no
persistent per-visitor identifiers: events are grouped into sessions via a
daily-rotating pseudonymous hash (IP+UA+host, HMAC-salted, never stored raw).

- New "Analytics" section in the Payload admin, gated by a canViewAnalytics
  permission on Users, following the existing custom-admin-view pattern
  used by the calendar feature
- Sessions, page views, unique visitors, top/entry/exit pages, events,
  device/browser breakdown, date range filter with period comparison
- Raw events and daily aggregate rollups as their own collections, with
  configurable retention and a cron endpoint that rolls up before purging
- Cookie consent banner (necessary/preferences/statistics) with categories
  and copy editable from a new CMS global; a track() helper that is a
  complete no-op without statistics consent, so nothing is ever sent
  before a visitor opts in
- track() calls wired into the booking flow, contact form, and navigation
2026-09-06 21:59:59 +02:00

53 lines
1.8 KiB
TypeScript

import type { CollectionConfig } from "payload";
import { canViewAnalytics } from "@/access";
/**
* One row per calendar day, upserted by the cleanup cron
* (lib/analytics/cleanup.ts) from raw analytics-events BEFORE those raw rows
* age out. This is what lets aggregated history survive well beyond the raw
* retention window (default 90 days raw vs. 12 months aggregated — see
* globals/AnalyticsSettings.ts) without keeping individual events around.
*
* Not currently read by the main Analytics dashboard filters (those all
* stay within raw retention by design — see the implementation plan); this
* exists so long-term aggregated history isn't silently lost once raw
* events are purged, and is available for a future longer-range view.
*/
export const AnalyticsDailyStats: CollectionConfig = {
slug: "analytics-daily-stats",
labels: {
singular: "Analytics-Tagesstatistik",
plural: "Analytics-Tagesstatistiken",
},
admin: {
hidden: true,
},
access: {
create: () => false,
read: canViewAnalytics,
update: () => false,
delete: () => false,
},
timestamps: true,
fields: [
{
name: "date",
type: "date",
required: true,
unique: true,
index: true,
admin: { date: { pickerAppearance: "dayOnly" } },
},
{ name: "sessions", type: "number", required: true, defaultValue: 0 },
{ name: "pageViews", type: "number", required: true, defaultValue: 0 },
{ name: "uniqueVisitors", type: "number", required: true, defaultValue: 0 },
// Each a { [key: string]: number } map, e.g. eventCounts.booking_started.
{ name: "eventCounts", type: "json" },
{ name: "deviceCounts", type: "json" },
{ name: "browserCounts", type: "json" },
{ name: "topPaths", type: "json" },
{ name: "entryPaths", type: "json" },
{ name: "exitPaths", type: "json" },
],
};