- Deterministic Classification: MOAS/HIJACK/LEAK type detection - Severity scoring: CRITICAL/HIGH/MEDIUM/LOW based on prefix length - Optional Ollama enrichment (qwen2.5:3b) for CRITICAL only (5s timeout) - PostgreSQL backend: hijack_events, webhook_subscriptions, webhook_deliveries - HMAC-SHA256 webhook signing with exponential backoff retry - Retry scheduler: node-cron job every 5 minutes - 6 API endpoints: POST/GET/DELETE webhooks, test delivery, list/resolve hijacks - 22 comprehensive tests (80%+ coverage) - Zero external API costs (deterministic + local Ollama only)
91 lines
2.2 KiB
TypeScript
91 lines
2.2 KiB
TypeScript
import type { Pool, QueryResult } from 'pg'
|
|
import type { PDFReport } from './types'
|
|
|
|
export class PDFExportDatabaseClient {
|
|
constructor(private pool: Pool) {}
|
|
|
|
async recordPDFGeneration(
|
|
resource_value: string,
|
|
format: string,
|
|
file_hash: string,
|
|
file_size_bytes: number,
|
|
metadata: Record<string, unknown> = {}
|
|
): Promise<PDFReport> {
|
|
const query = `
|
|
INSERT INTO pdf_reports (
|
|
resource_type,
|
|
resource_value,
|
|
format,
|
|
generated_at,
|
|
expires_at,
|
|
file_hash,
|
|
file_size_bytes,
|
|
metadata
|
|
) VALUES ($1, $2, $3, NOW(), NOW() + INTERVAL '5 minutes', $4, $5, $6)
|
|
ON CONFLICT (resource_value, format, DATE(generated_at))
|
|
DO UPDATE SET
|
|
file_hash = EXCLUDED.file_hash,
|
|
file_size_bytes = EXCLUDED.file_size_bytes,
|
|
expires_at = NOW() + INTERVAL '5 minutes'
|
|
RETURNING *
|
|
`
|
|
|
|
const result: QueryResult<PDFReport> = await this.pool.query(query, [
|
|
'asn',
|
|
resource_value,
|
|
format,
|
|
file_hash,
|
|
file_size_bytes,
|
|
JSON.stringify(metadata),
|
|
])
|
|
|
|
return result.rows[0]
|
|
}
|
|
|
|
async getPDFByHash(file_hash: string): Promise<PDFReport | null> {
|
|
const query = `
|
|
SELECT * FROM pdf_reports
|
|
WHERE file_hash = $1 AND expires_at > NOW()
|
|
LIMIT 1
|
|
`
|
|
|
|
const result: QueryResult<PDFReport> = await this.pool.query(query, [
|
|
file_hash,
|
|
])
|
|
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
async cleanupExpiredPDFs(): Promise<number> {
|
|
const query = `
|
|
DELETE FROM pdf_reports
|
|
WHERE expires_at <= NOW()
|
|
`
|
|
|
|
const result: QueryResult = await this.pool.query(query)
|
|
return result.rowCount || 0
|
|
}
|
|
|
|
async getPDFStats(): Promise<{
|
|
total_records: number
|
|
total_size_bytes: number
|
|
expired_records: number
|
|
}> {
|
|
const query = `
|
|
SELECT
|
|
COUNT(*) as total_records,
|
|
COALESCE(SUM(file_size_bytes), 0) as total_size_bytes,
|
|
COUNT(CASE WHEN expires_at <= NOW() THEN 1 END) as expired_records
|
|
FROM pdf_reports
|
|
`
|
|
|
|
const result: QueryResult<{
|
|
total_records: number
|
|
total_size_bytes: number
|
|
expired_records: number
|
|
}> = await this.pool.query(query)
|
|
|
|
return result.rows[0]
|
|
}
|
|
}
|