177 lines
4.7 KiB
JavaScript
177 lines
4.7 KiB
JavaScript
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 || [])
|
|
}
|