/** * SQLite store for native FCM device tokens (staff chat push to the Android app). * * Frontend-owned operational data — same posture as chatDb.ts: lazy-opens inside try/catch on first * use and is only imported by the native-push code. If better-sqlite3 or the file fails, every export * logs and no-ops — native push degrades, the server never crashes at boot, and removing the feature * leaves nothing dangling. Lives at data/fcm-tokens.db. */ import Database from 'better-sqlite3' import path from 'path' import fs from 'fs' let db: any = null let initFailed = false const getDb = () => { if (db) return db if (initFailed) return null try { const dbPath = path.join(process.cwd(), 'data', 'fcm-tokens.db') const dataDir = path.dirname(dbPath) if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true }) const _db = new Database(dbPath) _db.pragma('journal_mode = WAL') _db.exec(` CREATE TABLE IF NOT EXISTS fcm_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, role_id INTEGER, token TEXT UNIQUE NOT NULL, platform TEXT, created_at INTEGER NOT NULL, last_used INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_fcm_user ON fcm_tokens(user_id); `) db = _db return db } catch (e: any) { initFailed = true console.warn('[fcmDb] open failed (native push disabled):', e?.message) return null } } export const saveFcmToken = (userId: number, roleId: number, token: string, platform = 'android') => { const d = getDb() if (!d || !token) return try { const now = Date.now() d.prepare(` INSERT INTO fcm_tokens (user_id, role_id, token, platform, created_at, last_used) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(token) DO UPDATE SET user_id = excluded.user_id, role_id = excluded.role_id, platform = excluded.platform, last_used = excluded.last_used `).run(userId, roleId, token, platform, now, now) } catch (e: any) { console.warn('[fcmDb] save failed:', e?.message) } } export const getFcmTokensForUser = (userId: number): string[] => { const d = getDb() if (!d) return [] try { return d.prepare('SELECT token FROM fcm_tokens WHERE user_id = ?').all(userId).map((r: any) => r.token) } catch { return [] } } export const deleteFcmToken = (token: string) => { const d = getDb() if (!d || !token) return try { d.prepare('DELETE FROM fcm_tokens WHERE token = ?').run(token) } catch { /* ignore */ } }