feat(protection): VIP umfasst auch approved Custom-Domains

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 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-05-21 22:44:19 +02:00
parent ced749018b
commit a713070d25

View File

@ -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<string[]> {
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) {