Expand ecash explorer capabilities
Add Fedimint discovery, dual SQLite/Postgres storage, richer review handling, and generated social imagery.
This commit is contained in:
+40
-16
@@ -17,17 +17,30 @@ function track<T>(work: Promise<T>): Promise<T> {
|
||||
return work;
|
||||
}
|
||||
|
||||
/*
|
||||
* One cycle of each kind at a time. A cycle that outlives its interval (a relay socket
|
||||
* that never closes, a wedged fetch) must not have the next timer tick start a second
|
||||
* copy beside it — that doubles the relay load and interleaves state writes for no
|
||||
* new information. The late cycle finishes; the ticks it swallowed are simply skipped.
|
||||
*/
|
||||
let probeRunning = false;
|
||||
let discoveryRunning = false;
|
||||
|
||||
async function probeCycle(): Promise<void> {
|
||||
if (shuttingDown) return;
|
||||
if (shuttingDown || probeRunning) return;
|
||||
probeRunning = true;
|
||||
try {
|
||||
await track(probeAll());
|
||||
} catch (err) {
|
||||
log.error('probe cycle failed', { reason: err instanceof Error ? err.message : String(err) });
|
||||
} finally {
|
||||
probeRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function discoveryCycle(backfill: boolean): Promise<void> {
|
||||
if (shuttingDown) return;
|
||||
if (shuttingDown || discoveryRunning) return;
|
||||
discoveryRunning = true;
|
||||
try {
|
||||
const result = await track(runDiscovery(backfill));
|
||||
// New mints are probed straight away, not on the next cycle, so their page has
|
||||
@@ -37,16 +50,20 @@ async function discoveryCycle(backfill: boolean): Promise<void> {
|
||||
log.error('discovery cycle failed', {
|
||||
reason: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
} finally {
|
||||
discoveryRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
getDb();
|
||||
async function main(): Promise<void> {
|
||||
// Fail loudly here rather than on the first request: a bad DATABASE_URL, an
|
||||
// unreachable Postgres or an unwritable SQLite directory should stop the boot.
|
||||
await getDb();
|
||||
|
||||
const server = serve({ fetch: createApp().fetch, port: config.port }, (info) => {
|
||||
log.info('api listening', {
|
||||
port: info.port,
|
||||
db: config.dbPath,
|
||||
db: config.db.label,
|
||||
relays: config.relays.length,
|
||||
});
|
||||
});
|
||||
@@ -57,8 +74,13 @@ function main(): void {
|
||||
config.discoveryIntervalMin * 60 * 1000,
|
||||
);
|
||||
const pruneTimer = setInterval(() => {
|
||||
const removed = pruneProbes();
|
||||
if (removed > 0) log.info('pruned probes', { rows: removed });
|
||||
void pruneProbes()
|
||||
.then((removed) => {
|
||||
if (removed > 0) log.info('pruned probes', { rows: removed });
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
log.error('prune failed', { reason: err instanceof Error ? err.message : String(err) });
|
||||
});
|
||||
}, DAY_MS);
|
||||
|
||||
// Startup: probe what we already know, then a full discovery backfill so a fresh
|
||||
@@ -74,17 +96,16 @@ function main(): void {
|
||||
clearInterval(discoveryTimer);
|
||||
clearInterval(pruneTimer);
|
||||
|
||||
const stop = (): void => {
|
||||
// closeDb drains the Postgres pool, so it is awaited before the process leaves.
|
||||
void closeDb().finally(() => process.exit(0));
|
||||
};
|
||||
|
||||
void inFlight.finally(() => {
|
||||
closePool();
|
||||
server.close(() => {
|
||||
closeDb();
|
||||
process.exit(0);
|
||||
});
|
||||
server.close(stop);
|
||||
// Do not hang forever on a relay socket that refuses to close.
|
||||
setTimeout(() => {
|
||||
closeDb();
|
||||
process.exit(0);
|
||||
}, 8000).unref();
|
||||
setTimeout(stop, 8000).unref();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,4 +113,7 @@ function main(): void {
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
}
|
||||
|
||||
main();
|
||||
void main().catch((err: unknown) => {
|
||||
log.error('startup failed', { reason: err instanceof Error ? err.message : String(err) });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user