From a713070d2591ce104f0e3a76366b0529b0286857 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Thu, 21 May 2026 22:44:19 +0200 Subject: [PATCH] feat(protection): VIP umfasst auch approved Custom-Domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getWebCustomDomains schliesst nur noch 'rejected' aus — 'approved' Domains bleiben in der Layer-2-VIP (Zweitschutz, falls Layer 1 aus ist). Reihenfolge: pending zuerst (keine Layer-1-Deckung → duerfen nie aus dem 50er-Cap fallen), dann approved neueste-zuerst (Ueberlauf = aelteste approved, via Layer 1 gedeckt). Co-Authored-By: Claude Opus 4.7 --- backend/server/db/domains.ts | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/backend/server/db/domains.ts b/backend/server/db/domains.ts index 33b6aef..646d343 100644 --- a/backend/server/db/domains.ts +++ b/backend/server/db/domains.ts @@ -22,24 +22,35 @@ export const CUSTOM_DOMAIN_TYPES: CustomDomainType[] = [ // ─── Custom Domains ─────────────────────────────────────────────────────────── /** - * Gibt die aktiven Web-Custom-Domains eines Users zurück (nur type='web'). - * Status 'approved' und 'rejected' werden ausgeschlossen: - * - approved → Domain ist in der globalen Blocklist, kein Slot mehr nötig - * - rejected → Domain wurde abgelehnt und Slot wurde freigegeben - * Wird von GET /api/protection/webcontent-domains für die VIP-Komposition genutzt. + * Web-Custom-Domains eines Users für die Layer-2-VIP-Komposition (type='web'). + * Nur 'rejected' wird ausgeschlossen — 'approved' Domains BLEIBEN in der VIP: + * Layer 2 ist der Zweitschutz für den Fall, dass Layer 1 (VPN/URL-Filter) aus + * ist. Eine approved Domain ist zwar in der globalen Layer-1-Blocklist, muss + * aber auch in Layer 2 gedeckt sein. + * + * Reihenfolge = Priorität für den 50er-Cap im Endpoint: + * 1. pending zuerst — KEINE Layer-1-Deckung, die VIP ist ihre einzige + * Absicherung → dürfen nie aus dem Cap fallen (≤ Slot-Limit, passen immer). + * 2. approved danach, neueste zuerst — bei Überlauf fallen die ältesten + * approved weg (via Layer 1 weiter gedeckt, daher vertretbar). + * + * Wird von GET /api/protection/webcontent-domains genutzt. */ export async function getWebCustomDomains(userId: string): Promise { const db = usePrisma(); - const rows = await db.userCustomDomain.findMany({ - where: { - userId, - type: "web", - status: { notIn: ["approved", "rejected"] }, - }, + // pending = alles außer approved/rejected — älteste zuerst (passen alle rein) + const pending = await db.userCustomDomain.findMany({ + where: { userId, type: "web", status: { notIn: ["approved", "rejected"] } }, orderBy: { addedAt: "asc" }, select: { domain: true }, }); - return rows.map((r) => r.domain); + // approved — neueste zuerst, damit bei Cap-Überlauf die ältesten wegfallen + const approved = await db.userCustomDomain.findMany({ + where: { userId, type: "web", status: "approved" }, + orderBy: { addedAt: "desc" }, + select: { domain: true }, + }); + return [...pending.map((r) => r.domain), ...approved.map((r) => r.domain)]; } export async function getUserCustomDomains(userId: string) {