import { usePrisma } from "../utils/prisma"; /** * Device-Binding pro User. Free=1, Pro=1, Legend=3 (siehe plan-features.maxDevices). * deviceId kommt vom Frontend via Capacitor Device.getId() (persistent UUID). */ export interface DeviceRecord { id: string; deviceId: string; platform: string; model: string | null; name: string | null; lastSeenAt: Date; createdAt: Date; } /** Liste aller Devices eines Users, aktuellstes zuerst. */ export async function listUserDevices(userId: string): Promise { const db = usePrisma(); return db.userDevice.findMany({ where: { userId }, orderBy: { lastSeenAt: "desc" }, select: { id: true, deviceId: true, platform: true, model: true, name: true, lastSeenAt: true, createdAt: true, }, }); } /** Gibt das Device zurück wenn registriert; sonst null. */ export async function findUserDevice( userId: string, deviceId: string, ): Promise { const db = usePrisma(); return db.userDevice.findUnique({ where: { userId_deviceId: { userId, deviceId } }, select: { id: true, deviceId: true, platform: true, model: true, name: true, lastSeenAt: true, createdAt: true, }, }); } /** * Idempotente Registrierung. Wenn Device bereits existiert: Touch lastSeenAt. * Wenn nicht existiert UND Limit erreicht: throw mit Liste der existierenden Devices. */ export async function registerDevice(opts: { userId: string; deviceId: string; platform: string; model?: string | null; name?: string | null; maxDevices: number; }): Promise<{ device: DeviceRecord; created: boolean; }> { const db = usePrisma(); // Idempotent: existiert das Device schon? const existing = await findUserDevice(opts.userId, opts.deviceId); if (existing) { // model/name beim Re-Register aktualisieren — User-Agent oder OS-Version // kann sich geändert haben (App-Update, OS-Upgrade, iPad-Detection-Fix). const updated = await db.userDevice.update({ where: { id: existing.id }, data: { lastSeenAt: new Date(), ...(opts.model !== undefined && { model: opts.model }), ...(opts.name !== undefined && { name: opts.name }), }, select: { id: true, deviceId: true, platform: true, model: true, name: true, lastSeenAt: true, createdAt: true, }, }); return { device: updated, created: false }; } // Neues Device — Limit prüfen const count = await db.userDevice.count({ where: { userId: opts.userId } }); if (count >= opts.maxDevices) { throw Object.assign(new Error("device_limit_reached"), { code: "DEVICE_LIMIT_REACHED", currentCount: count, max: opts.maxDevices, }); } const created = await db.userDevice.create({ data: { userId: opts.userId, deviceId: opts.deviceId, platform: opts.platform, model: opts.model ?? null, name: opts.name ?? null, }, select: { id: true, deviceId: true, platform: true, model: true, name: true, lastSeenAt: true, createdAt: true, }, }); return { device: created, created: true }; } /** Touch lastSeenAt — wird in der Auth-Middleware bei jedem Request aufgerufen. */ export async function touchDevice(userId: string, deviceId: string): Promise { const db = usePrisma(); await db.userDevice .updateMany({ where: { userId, deviceId }, data: { lastSeenAt: new Date() }, }) .catch(() => { /* race-safe: wenn Device gerade gelöscht wurde */ }); } /** User entfernt ein eigenes Device — gibt Slot frei. */ export async function deleteUserDevice(userId: string, id: string): Promise { const db = usePrisma(); await db.userDevice.deleteMany({ where: { id, userId } }); }