import { v4 as uuidv4 } from 'uuid' import { getDb, saveDbToStore } from '../lib/sqlite.js' export const NOTIFICATION_DEBUG_STATUS = { review: 'review', unmatched: 'unmatched', matched: 'matched', ignored: 'ignored', error: 'error', } const mapRow = (row) => ({ id: row.id, sourceId: row.source_id || '', channel: row.channel || '', title: row.title || '', text: row.text || '', postedAt: row.posted_at || '', status: row.status || NOTIFICATION_DEBUG_STATUS.unmatched, ruleId: row.rule_id || '', transactionId: row.transaction_id || '', errorMessage: row.error_message || '', }) const dedupeRecords = (rows = []) => { const bucket = new Map() rows.forEach((row) => { const item = mapRow(row) const key = item.sourceId || item.id if (!bucket.has(key)) { bucket.set(key, item) return } const existing = bucket.get(key) const existingScore = Number(Boolean(existing.transactionId)) + Number(existing.status === NOTIFICATION_DEBUG_STATUS.matched) + Number(Boolean(existing.ruleId)) const nextScore = Number(Boolean(item.transactionId)) + Number(item.status === NOTIFICATION_DEBUG_STATUS.matched) + Number(Boolean(item.ruleId)) if (nextScore > existingScore) { bucket.set(key, item) } }) return Array.from(bucket.values()) } const getLatestBySourceId = async (sourceId) => { if (!sourceId) return null const db = await getDb() const result = await db.query( ` SELECT * FROM notifications_raw WHERE source_id = ? ORDER BY posted_at DESC LIMIT 1 `, [sourceId], ) return result?.values?.[0] ? mapRow(result.values[0]) : null } export const ensureNotificationRaw = async (notification, options = {}) => { const sourceId = notification?.id || notification?.sourceId || '' const existing = await getLatestBySourceId(sourceId) if (existing) return existing const db = await getDb() const id = notification?.rawId || uuidv4() await db.run( ` INSERT INTO notifications_raw ( id, source_id, channel, title, text, posted_at, status, rule_id, transaction_id, error_message ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ id, sourceId || null, notification?.channel || null, notification?.title || null, notification?.text || '', notification?.createdAt || notification?.postedAt || new Date().toISOString(), options.status || NOTIFICATION_DEBUG_STATUS.unmatched, options.ruleId || null, options.transactionId || null, options.errorMessage || null, ], ) await saveDbToStore() return { id, sourceId, channel: notification?.channel || '', title: notification?.title || '', text: notification?.text || '', postedAt: notification?.createdAt || notification?.postedAt || '', status: options.status || NOTIFICATION_DEBUG_STATUS.unmatched, ruleId: options.ruleId || '', transactionId: options.transactionId || '', errorMessage: options.errorMessage || '', } } export const updateNotificationRawStatus = async (sourceId, next = {}) => { if (!sourceId) return null const existing = await getLatestBySourceId(sourceId) if (!existing) return null const db = await getDb() await db.run( ` UPDATE notifications_raw SET status = ?, rule_id = ?, transaction_id = ?, error_message = ? WHERE id = ? `, [ next.status || existing.status, next.ruleId ?? existing.ruleId ?? null, next.transactionId ?? existing.transactionId ?? null, next.errorMessage ?? existing.errorMessage ?? null, existing.id, ], ) await saveDbToStore() return { ...existing, status: next.status || existing.status, ruleId: next.ruleId ?? existing.ruleId, transactionId: next.transactionId ?? existing.transactionId, errorMessage: next.errorMessage ?? existing.errorMessage, } } export const fetchNotificationDebugRecords = async (options = {}) => { const db = await getDb() const params = [] const where = [] if (options.status && options.status !== 'all') { where.push('status = ?') params.push(options.status) } if (options.keyword) { where.push('(channel LIKE ? OR title LIKE ? OR text LIKE ?)') const keyword = `%${String(options.keyword).trim()}%` params.push(keyword, keyword, keyword) } const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : '' const limit = Math.max(1, Math.min(Number(options.limit) || 80, 200)) params.push(limit) const result = await db.query( ` SELECT * FROM notifications_raw ${whereClause} ORDER BY posted_at DESC LIMIT ? `, params, ) return dedupeRecords(result?.values || []) }