Files
adam-acs/app/services/exchange_index_checker.ts
T
Kevin L'Hereec 0c83a4ccb2 feat: TR-069 Exchange Viewer - structured MongoDB storage with SSE streaming and sequence diagram UI
- MongoDB storage: cwmp_exchanges collection with indexes (deviceId+timestamp, deviceId+sequenceNumber unique, TTL 24h)
- ExchangeStoreService: CWMP method extraction, per-device queue for ordering, retry logic, message cap (5000/device)
- Exchange API: GET/DELETE/SSE stream endpoints with Last-Event-ID reconnection
- CwmpLoggerMiddleware: dual-write (MongoDB + legacy files) with fire-and-forget
- Frontend: SequenceViewer (CPE↔ACS lifelines, arrows, auto-scroll, virtual scroll) + InspectorPanel (formatted XML, copy, syntax highlighting)
- Tr069Trace page: integrated new viewer with col-6/col-6 layout
- DeviceDetail page: collapsible exchange section when device is traced
- 14 property-based tests, 40+ unit tests, 5 integration tests
- Health check includes index verification
- Config: feature flags, SSE timeouts, retry policy
2026-06-03 15:52:44 +02:00

88 lines
2.4 KiB
TypeScript

import { Db } from 'mongodb'
import { getMongoDb } from '#providers/mongodb_provider'
/**
* Expected indexes on the `cwmp_exchanges` collection.
* Each entry describes the index name and key pattern for verification.
*/
const EXPECTED_INDEXES = [
{
name: 'idx_deviceId_timestamp',
key: { deviceId: 1, timestamp: 1 },
description: 'Compound index (deviceId, timestamp)',
},
{
name: 'idx_deviceId_sequenceNumber',
key: { deviceId: 1, sequenceNumber: 1 },
description: 'Unique index (deviceId, sequenceNumber)',
},
{
name: 'idx_createdAt_ttl',
key: { createdAt: 1 },
description: 'TTL index (createdAt, 86400s)',
},
]
export interface IndexCheckResult {
name: string
description: string
exists: boolean
}
export interface IndexVerificationResult {
collection: string
allPresent: boolean
indexes: IndexCheckResult[]
}
/**
* Verifies that all expected indexes exist on the cwmp_exchanges collection.
* Returns a structured result with per-index status.
*/
export async function verifyCwmpExchangesIndexes(db?: Db): Promise<IndexVerificationResult> {
const database = db ?? getMongoDb()
const collection = database.collection('cwmp_exchanges')
let existingIndexes: { name?: string }[]
try {
existingIndexes = await collection.indexes()
} catch {
// Collection may not exist yet
existingIndexes = []
}
const existingNames = new Set(existingIndexes.map((idx) => idx.name))
const indexes: IndexCheckResult[] = EXPECTED_INDEXES.map((expected) => ({
name: expected.name,
description: expected.description,
exists: existingNames.has(expected.name),
}))
const allPresent = indexes.every((idx) => idx.exists)
return {
collection: 'cwmp_exchanges',
allPresent,
indexes,
}
}
/**
* Runs index verification at startup and logs warnings for missing indexes.
* Intended to be called after MongoDB provider has booted.
*/
export async function verifyIndexesAtStartup(db: Db, log: { warn: (msg: string) => void; info: (msg: string) => void }): Promise<void> {
const result = await verifyCwmpExchangesIndexes(db)
if (result.allPresent) {
log.info('[ExchangeIndexCheck] All cwmp_exchanges indexes verified OK')
return
}
const missing = result.indexes.filter((idx) => !idx.exists)
for (const idx of missing) {
log.warn(`[ExchangeIndexCheck] Missing index on cwmp_exchanges: ${idx.name} (${idx.description})`)
}
}