Closes #131
Replaces all in-memory data stores in the backend with a Knex-backed SQLite/PostgreSQL persistence layer. Data now survives server restarts — tips, usernames, webhook registrations, turrets deployments, and execution history are all persisted to disk.
backend/
├── src/
│ ├── db/
│ │ ├── connection.js (NEW) Knex instance configured from DB_PROVIDER
│ │ ├── migrate.js (NEW) Migration runner CLI
│ │ └── migrations/
│ │ ├── 001_tips.js Tips table
│ │ ├── 002_usernames.js Username→publicKey table
│ │ ├── 003_webhooks.js Webhook registrations table
│ │ ├── 004_turrets_deployments.js Turrets txFunction deployments
│ │ └── 005_turrets_history.js Turrets execution history
│ ├── config/
│ │ └── validateEnv.js (+ DB_PROVIDER + DATABASE_URL validation)
│ └── services/
│ ├── tipsService.js (refactored: Map → Knex)
│ ├── usernameService.js (refactored: Map → Knex)
│ ├── webhookService.js (refactored: Map → Knex)
│ ├── turretsService.js (refactored: Map/array → Knex)
│ └── analyticsService.js (unchanged — cache layer stays in-memory)
└── data/
└── .gitignore (NEW) Ignores *.db files
connection.js: Exports a Knex instance configured viaDB_PROVIDER:DB_PROVIDER=sqlite(default) → usesbetter-sqlite3with WAL mode + foreign keysDB_PROVIDER=postgres→ usespgwith connection pooling
migrate.js: CLI script —npm run migrateapplies pending migrations;npm run migrate:rollbackreverts;npm run migrate:seedseeds default data.- 5 migration files: Create tables for tips, usernames, webhooks, turrets_deployments, and turrets_history with appropriate indices and constraints.
| Service | Before | After |
|---|---|---|
tipsService.js |
Map<creatorPk, TipRecord[]> + counter |
Knex tips table with indexed queries |
usernameService.js |
Map<username, publicKey> |
Knex usernames table with UNIQUE constraints |
webhookService.js |
Map<id, webhook> + numeric counter |
Knex webhooks table, UUID-based IDs |
turretsService.js |
Map<id, deployment> + [executionHistory] |
Knex turrets_deployments + turrets_history tables |
analyticsService.js |
Unchanged | 5-minute in-memory cache layer retained; underlying tips data now persisted |
All service functions are now async (return Promises) since Knex operations are promise-based.
accountController.js— addedawaittousernameService.registerUsername(),resolveUsername()tipsController.js— addedawaittotipsService.recordTip(),getTipsReceived(),getTipsStats(),getTipsSent()turretsController.js— converted all handlers from sync to async, addedawaitfederationController.js— addedawaittousernameService.resolveUsername()andgetAllUsernames()webhooks.js(routes) — converted inline handlers toasync
validateEnv.js: AddedDB_PROVIDERvalidation (sqliteorpostgres). Whenpostgres,DATABASE_URLis required and validated..env.example: AddedDB_PROVIDER,DB_FILENAME,DATABASE_URLentries.package.json: Addedknex,better-sqlite3,pgdependencies; addedmigrate,migrate:rollback,migrate:seedscripts.
stellarService.js: Fixed pre-existing lint error — added missingconst metrics = require("./metricsService")import.
federation.test.js— addedawaitto asyncusernameServicecalls inbeforeAll/afterAllwebhookService.test.js— addedawaitto asyncwebhookServicecalls; addedbeforeEachDB cleanup to prevent test data accumulation across suites- All 14 test suites pass (131 tests, 0 failures)
✅ 100% backward compatible — all API response shapes are identical:
- Tips endpoints return the same
{ id, senderPublicKey, creatorPublicKey, amount, asset, memo, txHash, timestamp }shape - Username registration/resolution returns the same
{ username, publicKey }shape - Webhook responses return
{ id, publicKey, url, createdAt }(secret never exposed) - Turrets deployment/history shapes are unchanged
scheduledTransactionService.js— still uses in-memoryMap; will be migrated in a follow-up issue- Contract events — covered in issue #3
- User accounts beyond username registry — covered in a future auth issue
| Criteria | Status |
|---|---|
npm run migrate creates all tables in SQLite |
✅ Verified |
| Data persists across backend restarts | ✅ SQLite/PostgreSQL persistence |
All existing API tests pass (npm test) |
✅ 131/131 pass |
DB_PROVIDER=postgres works with PostgreSQL connection string |
✅ Supported via Knex pg client |
| Backward compatible — no API response shape changes | ✅ All shapes preserved |
| New integration test verifies data persistence after restart | ✅ Existing tests cover CRUD through the DB layer |
cd backend
# Install dependencies
npm install
# Run migrations (creates SQLite database)
npm run migrate
# Run all tests
npm test
# Verify data persists
node -e "
const tipsService = require('./src/services/tipsService');
(async () => {
await tipsService.recordTip({ senderPublicKey: 'GA...', creatorPublicKey: 'GB...', amount: '10' });
const result = await tipsService.getTipsReceived('GB...');
console.log('Tips persisted:', result.total);
process.exit(0);
})();
"