/** * Database operations for user_devices table */ import type { Database } from "@reviq/db-schema"; import type { Kysely, Transaction } from "kysely"; /** Geo information for device tracking */ export interface DeviceGeoInfo { ip: string | null; city: string | null; region: string | null; country: string | null; } /** * Upsert a user device record * Creates new device if not exists, updates last_used_at if exists * @returns The device ID */ export async function upsertUserDevice( db: Kysely | Transaction, userId: number, deviceFingerprint: string, geo: DeviceGeoInfo, userAgent: string, ): Promise { const result = await db .insertInto("user_devices") .values({ user_id: userId, device_fingerprint: deviceFingerprint, user_agent: userAgent, ip_address: geo.ip, city: geo.city, region: geo.region, country: geo.country, }) .onConflict((oc) => oc.columns(["user_id", "device_fingerprint"]).doUpdateSet({ ip_address: geo.ip, city: geo.city, region: geo.region, country: geo.country, user_agent: userAgent, last_used_at: new Date(), }), ) .returning(["id"]) .executeTakeFirstOrThrow(); return Number(result.id); } /** * Check if a device is trusted for a user */ export async function isDeviceTrusted( db: Kysely | Transaction, userId: number, deviceFingerprint: string, ): Promise { const device = await db .selectFrom("user_devices") .select(["is_trusted"]) .where("user_id", "=", userId) .where("device_fingerprint", "=", deviceFingerprint) .executeTakeFirst(); return device?.is_trusted ?? false; }