rebreak-monorepo/backend/server/api/protection/webcontent-domains.get.ts
chahinebrini 8f2ef2cc98 feat(mdm,vip): MDM-VPN-Pivot + Layer-2-Country-Curated + Custom-Domain-Refactor
MDM-VPN-Pivot (Phase F.2 done):
- ops/mdm/profiles/rebreak-iphone-protection.mobileconfig auf v5 mit
  com.apple.vpn.managed Payload + OnDemandUserOverrideDisabled. iPhone-User
  kann ReBreak-VPN-Profile nicht entfernen und "Bedarf verbinden"-Toggle
  ist disabled. allowEnablingRestrictions empirisch widerlegt für FC-Toggle-
  Lock — out.
- DEV-removable Variante als Test-Profile dazu.
- Bootstrap-Tool (rebreak-supervise.sh) + Supervision-Identity-Setup-Doc.
- PHASES.md updated mit empirischen Befunden.

App-side MDM-Detect (Pfad-a Banner-Logic):
- modules/rebreak-protection: getDeviceState() returnt mdmManaged via
  Heuristik NETunnelProviderManager.count > 1 (App selbst kann nur einen
  eigenen erstellen, MDM-Push fügt einen zweiten hinzu).
- DeviceLayers.mdmManaged?: boolean Type.
- blocker.tsx: lockedIn-Bedingung erweitert um mdmManaged. Bei MDM-managed
  iPhones wird der App-Lock-Card (FC-Authorization-Toggle UI) ausgeblendet
  weil der per-App FC-Toggle nicht lockbar ist und durch den MDM-VPN-Layer
  redundant.

Layer-2-Country-Curated-Pivot:
- backend: vip-swap.post.ts raus, suggest.post.ts rein. Curated-domains
  durch admin (separate Tabelle/Pfad), getrennt von User-Custom-Domains.
- Admin-APIs für curated-domain Pflege (index.get + [id].patch).
- seed-country-blocklists Script für initiale Curated-Domain-Liste.
- protection/webcontent-domains.get refactored für Country-Curated-Pfad.
- Migration drop_vip_swap_fields.sql + schema.prisma adjusted.
- docs/concepts/layer2-country-pivot.md mit Architektur + Decision-Trail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 07:11:47 +02:00

66 lines
2.1 KiB
TypeScript

import gamblingDomains from "../../data/gambling-domains.json";
import { usePrisma } from "../../utils/prisma";
const COUNTRY_KEYS = ["DE", "GB", "FR", "TN"] as const;
type CountryKey = (typeof COUNTRY_KEYS)[number];
const GLOBAL_LISTS = gamblingDomains as unknown as Record<string, string[]>;
const MAX_PER_COUNTRY = 50;
/**
* GET /api/protection/webcontent-domains
*
* Liefert die Country-Curated-Domain-Liste für den WebKit-webContent-Filter
* (Layer 2). Nach Layer-2-Country-Pivot (2026-05-25) ist Layer 2 vollständig
* entkoppelt von User-Custom-Domains:
*
* Layer 1 (VPN/blocklist.bin) = User-Custom-Domains + globale Blocklist
* Layer 2 (iOS NEFilter) = ausschliesslich Country-Curated (Admin-managed)
*
* Zusammensetzung pro Land:
* 1. Statische gambling-domains.json (build-time gebundelt)
* 2. DB-approved CuratedDomain-Rows (Admin-kuratiert + User-Vorschläge mit status="approved")
* → dedupliziert → hart auf 50 gekappt (Apple-Limit)
*
* Optional: Query-Param ?travel=FR für Travel-Detection (Server-side Merge).
* iOS sendet origin (OS-Region) + travel (Cellular-MCC-Land) wenn verfügbar.
* Ohne Params: alle COUNTRY_KEYS werden zurückgegeben — iOS filtert selbst.
*
* Response-Shape unverändert: { _meta, DE: [], GB: [], FR: [], TN: [] }
*/
export default defineEventHandler(async (event) => {
await requireUser(event); // Auth bleibt — kein User-Lookup, nur Authentifizierung
const db = usePrisma();
const approvedCurated = await db.curatedDomain.findMany({
where: { status: "approved" },
select: { domain: true, country: true },
});
const curatedByCountry: Record<string, string[]> = {};
for (const c of approvedCurated) {
(curatedByCountry[c.country] ??= []).push(c.domain);
}
const composed: Record<CountryKey, string[]> = {} as Record<
CountryKey,
string[]
>;
for (const country of COUNTRY_KEYS) {
const merged = [
...new Set([
...(GLOBAL_LISTS[country] ?? []),
...(curatedByCountry[country] ?? []),
]),
];
composed[country] = merged.slice(0, MAX_PER_COUNTRY);
}
return {
_meta: gamblingDomains._meta,
...composed,
};
});