rebreak-monorepo/backend/server/db/device-protection.ts

124 lines
3.5 KiB
TypeScript

import { usePrisma } from "../utils/prisma";
// ─── Types ────────────────────────────────────────────────────────────────────
export type ProtectionType = "nefilter" | "vpn" | "dns";
export const PROTECTION_TYPES: ProtectionType[] = [
"nefilter",
"vpn",
"dns",
];
export interface DeviceProtectionStateRecord {
id: string;
userId: string;
deviceId: string;
platform: string;
protectionType: string;
active: boolean;
lastSeenAt: Date | null;
changedAt: Date;
reason: string | null;
}
// ─── Write ─────────────────────────────────────────────────────────────────────
/**
* Upserts the per-device/per-protection-type state.
*
* If `active` changed compared to the existing row (or there was no row), an
* entry is appended to `DeviceProtectionStateLog` with `occurredAt = now()`.
*/
export async function upsertDeviceProtectionState(
userId: string,
deviceId: string,
platform: string,
protectionType: ProtectionType,
active: boolean,
lastSeenAt?: Date | null,
reason?: string | null,
source?: string | null,
): Promise<DeviceProtectionStateRecord> {
const db = usePrisma();
const now = new Date();
const existing = await db.deviceProtectionState.findUnique({
where: {
userId_deviceId_protectionType: { userId, deviceId, protectionType },
},
});
const changed = !existing || existing.active !== active;
const row = await db.deviceProtectionState.upsert({
where: {
userId_deviceId_protectionType: { userId, deviceId, protectionType },
},
create: {
userId,
deviceId,
platform,
protectionType,
active,
lastSeenAt: lastSeenAt ?? null,
changedAt: now,
reason: reason ?? null,
},
update: {
platform,
active,
lastSeenAt: lastSeenAt === undefined ? undefined : lastSeenAt,
changedAt: now,
reason: reason === undefined ? undefined : reason,
},
});
if (changed) {
await db.deviceProtectionStateLog.create({
data: {
userId,
deviceId,
protectionType,
active,
occurredAt: now,
reason: reason ?? null,
source: source ?? "app",
},
});
}
return row;
}
// ─── Read ──────────────────────────────────────────────────────────────────────
/** Returns the current state for one protection type on a device. */
export async function getDeviceProtectionState(
userId: string,
deviceId: string,
protectionType: ProtectionType,
): Promise<DeviceProtectionStateRecord | null> {
const db = usePrisma();
return db.deviceProtectionState.findUnique({
where: {
userId_deviceId_protectionType: { userId, deviceId, protectionType },
},
});
}
/** Lists all protection states for a user, optionally filtered to one device. */
export async function listDeviceProtectionStates(
userId: string,
deviceId?: string,
): Promise<DeviceProtectionStateRecord[]> {
const db = usePrisma();
return db.deviceProtectionState.findMany({
where: {
userId,
...(deviceId ? { deviceId } : {}),
},
orderBy: [{ deviceId: "asc" }, { protectionType: "asc" }],
});
}