The 2026-07-14 backend refactor (PR #1) moved 13 features' route logic out of server.js into src/features/*/routes.ts as Fastify plugins, and left "// Migrated to src/features/X/" comments behind -- but never actually built or started anything that served those plugins. Found a complete, unused Fastify app bootstrap already sitting at src/api/server.ts (added in an earlier commit, 5554c1a, alongside the BGP hijack/PDF-export/ASPA-adoption work) that imports and registers all 16 route modules, including 3 (pdf-export, aspa-adoption, hijack-alerts) with proper Postgres-backed implementations, tests, and PDF templates -- more capable than the puppeteer-based inline version kept from the production snapshot. Also confirmed there is no code anywhere that calls initializeDatabase() or startApiServer(): this Fastify server had never actually been run, not even once. What this commit does: - Fixes 6 real TypeScript compile errors blocking `tsc` from building the affected modules cleanly (all pre-existing, none introduced by this branch): - src/routes/hijack-alerts.ts: route generic was missing `Body` in its type param, so it didn't match the handler's own declared type - src/features/aspa-adoption/scheduler.ts: node-cron v4's ScheduledTask dropped `.destroy()` (relevant now that this branch already bumped node-cron 3->4 for the uuid CVE fix) -- `.stop()` alone is sufficient - src/features/{bgp-communities,rpki-history}/routes.ts: `response.json()` typed as `{}` under this @types/node version, cast to `any` to match this file's existing loose-typing style - src/features/pdf-export/cache-manager.ts: `NodeJS.Timer` -> `NodeJS.Timeout` (what setInterval/clearInterval actually use) - src/features/pdf-export/renderer.ts: this uses Playwright, not Puppeteer -- `timeout` isn't a page.pdf() option in Playwright, moved to page.setDefaultTimeout() before the call - Adds src/api/index.ts as the actual entry point (there wasn't one). Calls initializeDatabase() before dynamically importing ./server.js, because src/routes/hijack-alerts.ts calls getDatabase() at module load time -- a static top-level import of ./server would have crashed on startup before initializeDatabase() ever ran. Verified this boots cleanly and serves real responses (changelog-data, ix-matrix, rib/prefix graceful-503, bgp-communities with a live RIPE Stat call) in local testing. - Wires server.js to reverse-proxy the 11 genuinely-orphaned paths (submarine-cables, global-infra, communities, irr-audit, asset-expand, rpki-history, aspath, looking-glass, ix-matrix, changelog-data, rib/*, prefix-changes) to this new internal server via a plain http.request() proxy -- chosen over exposing a second Cloudflare Tunnel ingress rule so this stays a same-origin, internal-only change with no DNS/tunnel config to touch. Verified the proxy mechanism itself against a live instance of the new server. Deliberately did NOT proxy /api/webhooks, /api/hijacks, or /api/hijack-subscribe -- server.js already serves those paths inline (file-backed, live, working), and src/routes/hijack-alerts.ts registers the same path names against an empty Postgres table. Proxying would have silently shadowed working data with nothing. - Default port 3102, not 3100: checked Erik and found 3100 already bound by an unrelated docker-proxy container. 3102 is free and reads as "the second peercortex port" next to the existing 3101. - Adds a `peercortex-api` PM2 app to ecosystem.config.js on Erik (backed up first via rollback-standard.sh) -- config only, NOT started. Nothing is deployed to Erik as part of this commit: no dist/ upload, no `npm install` for fastify/pg on Erik, no `pm2 start`. server.js and public/index.html on Erik are untouched.
105 lines
2.2 KiB
TypeScript
105 lines
2.2 KiB
TypeScript
import crypto from 'crypto'
|
|
import type { PDFReport } from './types'
|
|
|
|
interface CacheEntry {
|
|
pdfBuffer: Buffer
|
|
hash: string
|
|
expiresAt: number
|
|
createdAt: number
|
|
hits: number
|
|
}
|
|
|
|
export class PDFCacheManager {
|
|
private cache: Map<string, CacheEntry> = new Map()
|
|
private readonly TTL_MS = 5 * 60 * 1000
|
|
private cleanupInterval: NodeJS.Timeout | null = null
|
|
|
|
constructor() {
|
|
this.startCleanup()
|
|
}
|
|
|
|
private startCleanup(): void {
|
|
this.cleanupInterval = setInterval(() => {
|
|
const now = Date.now()
|
|
let removed = 0
|
|
|
|
for (const [key, entry] of this.cache.entries()) {
|
|
if (now > entry.expiresAt) {
|
|
this.cache.delete(key)
|
|
removed++
|
|
}
|
|
}
|
|
|
|
if (removed > 0) {
|
|
console.log(`[PDFCache] Removed ${removed} expired entries`)
|
|
}
|
|
}, 60000)
|
|
|
|
if (this.cleanupInterval.unref) {
|
|
this.cleanupInterval.unref()
|
|
}
|
|
}
|
|
|
|
get(cacheKey: string): CacheEntry | null {
|
|
const entry = this.cache.get(cacheKey)
|
|
|
|
if (!entry) return null
|
|
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.cache.delete(cacheKey)
|
|
return null
|
|
}
|
|
|
|
entry.hits++
|
|
return entry
|
|
}
|
|
|
|
set(cacheKey: string, pdfBuffer: Buffer): void {
|
|
const hash = crypto.createHash('sha256').update(pdfBuffer).digest('hex')
|
|
const now = Date.now()
|
|
|
|
this.cache.set(cacheKey, {
|
|
pdfBuffer,
|
|
hash,
|
|
expiresAt: now + this.TTL_MS,
|
|
createdAt: now,
|
|
hits: 0,
|
|
})
|
|
|
|
console.log(`[PDFCache] Cached PDF: ${cacheKey} (${pdfBuffer.length} bytes)`)
|
|
}
|
|
|
|
generateKey(asn: number, format: string, date: string): string {
|
|
return crypto
|
|
.createHash('md5')
|
|
.update(`${asn}:${format}:${date}`)
|
|
.digest('hex')
|
|
}
|
|
|
|
getStats(): { size: number; entries: number; totalMemory: number } {
|
|
let totalMemory = 0
|
|
|
|
for (const entry of this.cache.values()) {
|
|
totalMemory += entry.pdfBuffer.length
|
|
}
|
|
|
|
return {
|
|
size: this.cache.size,
|
|
entries: this.cache.size,
|
|
totalMemory,
|
|
}
|
|
}
|
|
|
|
clear(): void {
|
|
this.cache.clear()
|
|
console.log('[PDFCache] Cache cleared')
|
|
}
|
|
|
|
destroy(): void {
|
|
if (this.cleanupInterval) {
|
|
clearInterval(this.cleanupInterval)
|
|
}
|
|
this.clear()
|
|
}
|
|
}
|