94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
import type { CollectionConfig } from "payload";
|
|
import { canViewAnalytics } from "@/access";
|
|
|
|
/**
|
|
* One row per tracked event (page_view, booking_started, ...). Deliberately
|
|
* append-only and never writable via the public/admin API - the only writer
|
|
* is app/(payload)/api/analytics/track/route.ts, which uses the trusted
|
|
* Local API (overrideAccess: true by default) to insert rows despite
|
|
* `create` being locked to `() => false` here. The only deleter is the
|
|
* cleanup cron (lib/analytics/cleanup.ts), same mechanism.
|
|
*
|
|
* No name/email/IP/UA is ever stored - see lib/analytics/salt.ts for how
|
|
* `visitorHash` is derived without persisting anything identifying.
|
|
*/
|
|
export const AnalyticsEvents: CollectionConfig = {
|
|
slug: "analytics-events",
|
|
labels: {
|
|
singular: "Messereignis",
|
|
plural: "Messereignisse",
|
|
},
|
|
admin: {
|
|
group: "Auswertung",
|
|
hidden: true,
|
|
// High-volume, low-signal raw data - not meant for manual browsing.
|
|
// The Analytics dashboard (a custom admin view) reads it programmatically.
|
|
},
|
|
access: {
|
|
create: () => false,
|
|
read: canViewAnalytics,
|
|
update: () => false,
|
|
delete: () => false,
|
|
},
|
|
timestamps: true,
|
|
indexes: [{ fields: ["visitorHash", "createdAt"] }, { fields: ["createdAt"] }],
|
|
fields: [
|
|
{
|
|
name: "sessionId",
|
|
type: "text",
|
|
required: true,
|
|
index: true,
|
|
},
|
|
{
|
|
name: "visitorHash",
|
|
type: "text",
|
|
required: true,
|
|
// Daily-rotating pseudonymous hash (see lib/analytics/salt.ts) - never
|
|
// a raw IP/UA, never stable across days, never exposed to the client.
|
|
},
|
|
{
|
|
name: "path",
|
|
type: "text",
|
|
required: true,
|
|
index: true,
|
|
},
|
|
{
|
|
name: "eventName",
|
|
type: "text",
|
|
required: true,
|
|
index: true,
|
|
// Deliberately free-text, not a select/enum: new event names (see
|
|
// lib/analytics/events.ts) never require a schema migration.
|
|
defaultValue: "page_view",
|
|
},
|
|
{
|
|
name: "referrerHost",
|
|
type: "text",
|
|
// Hostname only (never a full URL/query string). document.referrer is
|
|
// fixed for a tab's whole lifetime (it doesn't change on client-side
|
|
// route transitions), so this naturally reflects how the visitor
|
|
// originally arrived, sent unchanged with every event in the session.
|
|
},
|
|
{
|
|
name: "device",
|
|
type: "select",
|
|
options: [
|
|
{ label: "Mobile", value: "mobile" },
|
|
{ label: "Desktop", value: "desktop" },
|
|
{ label: "Tablet", value: "tablet" },
|
|
],
|
|
},
|
|
{
|
|
name: "browser",
|
|
type: "select",
|
|
options: [
|
|
{ label: "Chrome", value: "chrome" },
|
|
{ label: "Firefox", value: "firefox" },
|
|
{ label: "Safari", value: "safari" },
|
|
{ label: "Edge", value: "edge" },
|
|
{ label: "Sonstige", value: "other" },
|
|
],
|
|
},
|
|
],
|
|
};
|