postgres makes me want to kill myself actually, sqlite all the way :333

This commit is contained in:
2026-05-14 21:23:40 +01:00
parent e58adcc716
commit 98f374a300
6 changed files with 514 additions and 204 deletions
+68 -56
View File
@@ -1,8 +1,9 @@
const fs = require('node:fs')
const http = require('node:http')
const https = require('node:https')
const os = require('node:os')
const path = require('node:path')
const { Pool } = require('pg')
const Database = require('better-sqlite3')
function loadEnvFile() {
const envPath = path.join(__dirname, '.env')
@@ -37,7 +38,8 @@ loadEnvFile()
const PORT = Number(process.env.PORT || 3001)
const API_UPSTREAM = process.env.API_UPSTREAM || 'http://127.0.0.1:6000'
const PUBLIC_ORIGIN = process.env.PUBLIC_ORIGIN || ''
const UPTIME_DATABASE_URL = process.env.UPTIME_DATABASE_URL || process.env.DATABASE_URL || ''
const UPTIME_STORAGE_DIR = process.env.UPTIME_STORAGE_DIR || '~/tsswebstorage'
const UPTIME_DATABASE_FILE = process.env.UPTIME_DATABASE_FILE || 'uptime.sqlite'
const UPTIME_SAMPLE_INTERVAL_MS = Number(process.env.UPTIME_SAMPLE_INTERVAL_MS || 30 * 60 * 1000)
const UPTIME_HISTORY_LIMIT = Number(process.env.UPTIME_HISTORY_LIMIT || 336)
const API_CACHE_TTL_MS = Number(process.env.API_CACHE_TTL_MS || 15000)
@@ -73,13 +75,7 @@ const jsonHeaders = {
const apiCache = new Map()
const rateLimits = new Map()
const uptimePool = UPTIME_DATABASE_URL
? new Pool({
connectionString: UPTIME_DATABASE_URL,
ssl: process.env.PGSSLMODE === 'require' ? { rejectUnauthorized: false } : undefined,
})
: null
let uptimeReady = false
let uptimeDb = null
let latestUptimeSnapshot = null
function sendJson(res, status, body, headers = {}) {
@@ -131,28 +127,42 @@ function requestJson(url, timeoutMs = 10000) {
})
}
async function ensureUptimeSchema() {
if (!uptimePool || uptimeReady) return Boolean(uptimePool)
function expandHome(filePath) {
if (filePath === '~') return os.homedir()
if (filePath.startsWith(`~${path.sep}`)) return path.join(os.homedir(), filePath.slice(2))
if (filePath.startsWith('~/')) return path.join(os.homedir(), filePath.slice(2))
return filePath
}
await uptimePool.query(`
function uptimeStoragePath() {
return path.resolve(expandHome(UPTIME_STORAGE_DIR))
}
function ensureUptimeDb() {
if (uptimeDb) return uptimeDb
const storageDir = uptimeStoragePath()
fs.mkdirSync(storageDir, { recursive: true })
uptimeDb = new Database(path.join(storageDir, UPTIME_DATABASE_FILE))
uptimeDb.pragma('journal_mode = WAL')
uptimeDb.exec(`
create table if not exists uptime_snapshots (
id bigserial primary key,
checked_at timestamptz not null default now(),
website_ok boolean not null,
health_ok boolean not null,
tss_ok boolean not null,
ok boolean not null,
id integer primary key autoincrement,
checked_at text not null default (datetime('now')),
website_ok integer not null,
health_ok integer not null,
tss_ok integer not null,
ok integer not null,
latency_ms integer not null,
details jsonb not null default '{}'::jsonb
)
`)
await uptimePool.query(`
details text not null default '{}'
);
create index if not exists uptime_snapshots_checked_at_idx
on uptime_snapshots (checked_at desc)
on uptime_snapshots (checked_at desc);
`)
uptimeReady = true
return true
return uptimeDb
}
async function takeUptimeSnapshot() {
@@ -194,37 +204,42 @@ async function takeUptimeSnapshot() {
latestUptimeSnapshot = snapshot
if (uptimePool) {
await ensureUptimeSchema()
await uptimePool.query(
`insert into uptime_snapshots
(website_ok, health_ok, tss_ok, ok, latency_ms, details)
values ($1, $2, $3, $4, $5, $6)`,
[snapshot.website_ok, snapshot.health_ok, snapshot.tss_ok, snapshot.ok, snapshot.latency_ms, snapshot.details],
)
}
const db = ensureUptimeDb()
db.prepare(`
insert into uptime_snapshots
(checked_at, website_ok, health_ok, tss_ok, ok, latency_ms, details)
values
(@checked_at, @website_ok, @health_ok, @tss_ok, @ok, @latency_ms, @details)
`).run({
checked_at: snapshot.checked_at,
website_ok: snapshot.website_ok ? 1 : 0,
health_ok: snapshot.health_ok ? 1 : 0,
tss_ok: snapshot.tss_ok ? 1 : 0,
ok: snapshot.ok ? 1 : 0,
latency_ms: snapshot.latency_ms,
details: JSON.stringify(snapshot.details),
})
return snapshot
}
async function uptimeHistory() {
if (!uptimePool) {
return {
configured: false,
latest: latestUptimeSnapshot,
history: latestUptimeSnapshot ? [latestUptimeSnapshot] : [],
}
}
await ensureUptimeSchema()
const result = await uptimePool.query(
`select checked_at, website_ok, health_ok, tss_ok, ok, latency_ms, details
from uptime_snapshots
order by checked_at desc
limit $1`,
[UPTIME_HISTORY_LIMIT],
)
const history = result.rows.reverse()
const db = ensureUptimeDb()
const rows = db.prepare(`
select checked_at, website_ok, health_ok, tss_ok, ok, latency_ms, details
from uptime_snapshots
order by checked_at desc
limit ?
`).all(UPTIME_HISTORY_LIMIT)
const history = rows.reverse().map((row) => ({
checked_at: row.checked_at,
website_ok: Boolean(row.website_ok),
health_ok: Boolean(row.health_ok),
tss_ok: Boolean(row.tss_ok),
ok: Boolean(row.ok),
latency_ms: row.latency_ms,
details: JSON.parse(row.details || '{}'),
}))
return {
configured: true,
@@ -490,10 +505,7 @@ http
.listen(PORT, '0.0.0.0', () => {
console.log(`tssbot-web serving http://localhost:${PORT}`)
console.log(`proxying API requests to ${API_UPSTREAM}`)
console.log(
uptimePool
? `sampling uptime every ${Math.round(UPTIME_SAMPLE_INTERVAL_MS / 60000)} minutes`
: 'uptime database disabled; set DATABASE_URL or UPTIME_DATABASE_URL to persist snapshots',
)
console.log(`sampling uptime every ${Math.round(UPTIME_SAMPLE_INTERVAL_MS / 60000)} minutes`)
console.log(`storing uptime snapshots in ${path.join(uptimeStoragePath(), UPTIME_DATABASE_FILE)}`)
startUptimeSampler()
})