Files
pwap/backend/src/index.js
T
Satoshi Qazi Muhammed 80d273ff11 ci/cd(backend): offline test suite + Dockerize + audit-grade deploy (b + c)
Closes the "backend has zero CI + manual SSH deploy" audit finding for the indexer.

Part B — CI-runnable tests (the integration-tests/*.live.test.js need a live chain and
are excluded, not stubbed): extract injectable cores (indexer.js: DB+HTTP+decode, no
@pezkuwi import; council.js: createApp factory for the council/KYC routes). New node:test
suite (19/19, fully offline) — in-memory sqlite, dependency-injected chain stub, an
in-memory fake-supabase, and REAL @pezkuwi keyring signatures proving 401 bad-sig /
400 msg-mismatch / 200 valid / 409 dup / 403 non-member / threshold auto-execute. Backend
job now runs npm ci + npm test and is in ci-gate (failing tests block merge). Fixed a
latent bug: block indexing now awaits each insert (was fire-and-forget forEach). Added
runtime deps server.js imported but were missing from package.json (@supabase/supabase-js,
pino, pino-http) — server.js could not have `npm ci`-run before.

Part C — Dockerfile (non-root, HEALTHCHECK /health, sqlite state on a /data VOLUME kept
out of the image) + backend deploy pipeline mirroring the web one: build+push to GHCR,
cosign keyless sign + verify, ssh deploy with the DB volume preserved across deploys,
/health poll, auto-rollback to previous SHA, same Telegram CEO approval gate, main/tags
only, never fork PRs. New secrets documented in backend/DEPLOY.md (BACKEND_VPS_HOST/USER/
SSH_KEY). DB_PATH env (default ./transactions.db) lets prod point at the volume.

Note for owner: if server.js (council/KYC bootstrap) is legacy/unused, its newly-added
deps can be dropped instead.
2026-07-25 11:01:47 -07:00

43 lines
1.5 KiB
JavaScript

// Indexer service bootstrap.
//
// Thin wiring layer: connects to the live chain and feeds decoded blocks into
// the injectable core in ./indexer.js (which holds all the DB + HTTP logic and
// is unit-tested offline). Keeping the @pezkuwi/api connection isolated here
// means the testable core never touches the network.
import { ApiPromise, WsProvider } from '@pezkuwi/api'
import dotenv from 'dotenv'
import { initDb, indexBlock, createApp } from './indexer.js'
dotenv.config()
const port = process.env.PORT || 3001
const WS_ENDPOINT = process.env.WS_ENDPOINT || 'wss://rpc.pezkuwichain.io'
// Start Indexing
async function startIndexer (db) {
console.log(`Connecting to Pezkuwi Node: ${WS_ENDPOINT}`)
const provider = new WsProvider(WS_ENDPOINT)
const api = await ApiPromise.create({ provider })
console.log('Connected! Listening for new blocks...')
api.rpc.chain.subscribeNewHeads(async (header) => {
const blockNumber = header.number.toNumber()
const blockHash = await api.rpc.chain.getBlockHash(blockNumber)
const signedBlock = await api.rpc.chain.getBlock(blockHash)
await indexBlock(db, signedBlock, blockNumber)
})
}
// Launch
// DB_PATH lets the deploy point the stateful sqlite file at a mounted volume
// (e.g. /data/transactions.db); defaults to the historical ./transactions.db.
const db = await initDb(process.env.DB_PATH || './transactions.db')
startIndexer(db)
const app = createApp(db)
app.listen(port, () => {
console.log(`Indexer API running at http://localhost:${port}`)
})