124 lines
3.5 KiB
TypeScript
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" }],
|
|
});
|
|
}
|