import gamblingDomains from "../../data/gambling-domains.json"; import { getWebCustomDomains } from "../../db/domains"; const COUNTRY_KEYS = ["DE", "GB", "FR", "TN"] as const; type CountryKey = (typeof COUNTRY_KEYS)[number]; const GLOBAL_LISTS = gamblingDomains as unknown as Record; const MAX_PER_COUNTRY = 50; // Hybrid-Reservierung: die Top-N kuratierten Gambling-Domains pro Land sind // FEST garantiert — ein User kann sie nicht mit eigenen Custom-Domains aus // seinem Layer-2-Zweitschutz verdrängen. Custom-Domains werden daher hart auf // (50 − RESERVED_CURATED) gekappt. Voraussetzung: gambling-domains.json ist // nach Relevanz sortiert (die ersten RESERVED_CURATED = die wichtigsten). const RESERVED_CURATED = 20; const MAX_CUSTOM = MAX_PER_COUNTRY - RESERVED_CURATED; // 30 /** * GET /api/protection/webcontent-domains * * Liefert die VIP-Domain-Liste für den WebKit-webContent-Filter (Layer 2). * Pro User personalisiert, Hybrid-Komposition pro Land: * 1. Custom-Web-Domains (pending zuerst, dann approved) — gekappt auf 30 * 2. kuratierte Gambling-Liste — füllt den Rest bis 50 auf * → dedupliziert → hart auf 50 gekappt (Apple-Limit). * * Damit sind immer ≥ 20 kuratierte Top-Domains im Zweitschutz garantiert, * egal wie viele Custom-Domains der User angesammelt hat. * Response-Shape ist identisch mit der statischen Version — iOS parst das unverändert. * * Lade-Mechanismus: direkter JSON-Import (build-time gebundelt via Nitro-Bundler). * Kein serverAssets/useStorage — kein extra Laufzeit-I/O, kein globales * backend/data/-Verzeichnis nötig. * * Pflege: backend/server/data/gambling-domains.json editieren, * _meta.version hochzählen, _meta.updatedAt setzen, dann neu deployen. */ export default defineEventHandler(async (event) => { const user = await requireUser(event); // Custom Web-Domains des Users laden — parallel zu allen Country-Listen const userWebDomains = await getWebCustomDomains(user.id); // Custom-Domains hart auf 30 kappen — die ersten 30 sind die höchst- // priorisierten (getWebCustomDomains liefert pending zuerst, dann approved // neueste-zuerst). Die restlichen 20 Slots bleiben für die kuratierte Liste. const cappedCustom = userWebDomains.slice(0, MAX_CUSTOM); // Dedup-Set NUR über die gekappten Customs — eine kuratierte Domain, die // einer aus dem 30-Cap GEFLOGENEN Custom-Domain entspricht, soll über die // kuratierte Auffüllung wieder reinkommen (sie ist ja eine Top-Domain). const cappedCustomSet = new Set(cappedCustom); // Pro Country: Custom-Domains vorne, dann globale Auffüllung, dedup, cap 50 const composed: Record = {} as Record< CountryKey, string[] >; for (const country of COUNTRY_KEYS) { const globalList: string[] = GLOBAL_LISTS[country] ?? []; // Gekappte Custom-Domains zuerst (bereits dedupliziert da aus DB) const merged: string[] = [...cappedCustom]; // Kuratierte Domains auffüllen — nur wenn noch nicht durch Custom drin for (const domain of globalList) { if (!cappedCustomSet.has(domain)) { merged.push(domain); } } composed[country] = merged.slice(0, MAX_PER_COUNTRY); } return { _meta: gamblingDomains._meta, ...composed, }; });