From dce763503b63de082b277260486b1d1dbd66aad2 Mon Sep 17 00:00:00 2001 From: tunmisehassan Date: Mon, 1 Jun 2026 03:50:14 +0100 Subject: [PATCH] feat: ETag caching, Prisma pool config, CHANGELOG, husky lint-staged - #356: Add ETag headers to GET /api/stellar/account/:publicKey with If-None-Match / 304 Not Modified support - #354: Document connection_limit and pool_timeout params in .env.example, fix duplicate content in the file - #350: Add CHANGELOG.md following Keep a Changelog format (v1.0.0) - #346: Add husky pre-commit hook with lint-staged, ESLint config, and eslint devDependency to root and backend package.json --- .husky/pre-commit | 1 + CHANGELOG.md | 90 ++++++++++++++ backend/.env.example | 156 ++----------------------- backend/package.json | 2 + backend/src/routes/stellar/accounts.js | 11 ++ eslint.config.js | 29 +++++ package.json | 14 +++ 7 files changed, 159 insertions(+), 144 deletions(-) create mode 100644 .husky/pre-commit create mode 100644 CHANGELOG.md create mode 100644 eslint.config.js diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c6e0ea2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,90 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.0] - 2026-05-28 + +### Added + +#### Core Platform +- Cross-border remittance platform built on the Stellar blockchain +- Stellar account creation, funding (testnet), and import via secret key +- XLM and non-XLM asset balance retrieval +- Trustline management (add/remove/list) +- Payment sending with optional fee-bump sponsorship for low-balance accounts +- Path payment support for cross-asset conversions +- Exchange rate service with multi-currency conversion +- AMM (Automated Market Maker) integration + +#### Authentication & Security +- JWT-based authentication with refresh token rotation +- Password hashing with bcrypt +- TOTP-based multi-factor authentication (MFA) with AES-256-GCM encrypted secrets +- Google OAuth2 login +- CSRF protection middleware +- Input sanitization and validation via express-validator and Zod +- Security headers middleware (Helmet-style) +- Rate limiting per endpoint with configurable windows and whitelists + +#### Database +- PostgreSQL via Prisma ORM with `@prisma/adapter-pg` driver adapter +- PgBouncer transaction-pooling support +- Database sharding across multiple PostgreSQL instances +- Soft-delete middleware for User, Transaction, and related models +- Configurable connection pool (`DB_POOL_MAX`) and query timeout (`DB_QUERY_TIMEOUT_MS`) +- Automated migrations on startup + +#### Payment Streaming +- Real-time payment streams with per-stream sender secret encryption (AES-256-GCM) +- WebSocket push for live balance and stream updates + +#### Notifications +- Web Push notifications (VAPID) +- Email notifications via SMTP (stubbed when unconfigured) +- SMS notifications via Twilio (stubbed when unconfigured) +- Per-user notification preferences + +#### Compliance & KYC +- KYC record collection and status tracking +- AML monitoring and alert generation +- Sanctions screening +- Risk scoring +- Compliance audit logging and reporting +- Identity verification workflow + +#### Analytics & Monitoring +- Request-level performance middleware with configurable alert thresholds +- OpenTelemetry distributed tracing (OTLP HTTP export) +- Winston structured logging with daily log rotation +- Prometheus-compatible metrics endpoint +- User behaviour analytics and fraud detection +- Event sourcing with projection manager, archiver, and replayer + +#### Caching +- Multi-level cache: in-memory L1 + optional Redis L2 +- Per-route cache middleware with configurable TTLs +- Cache invalidation on balance-changing operations +- Cache analytics, monitoring, and warming utilities + +#### Infrastructure +- Docker development environment (`Dockerfile.dev`) +- CDN middleware with multi-region edge support +- Microservices gateway, service mesh, and discovery utilities +- Chaos engineering toolkit (failure injection, network partition simulation, blast-radius limiter) +- Load testing framework with k6 scenarios and bottleneck analysis +- Backup and recovery manager with optional AES encryption and configurable retention +- Scheduled jobs via internal scheduler + +#### Developer Experience +- OpenAPI / Swagger documentation (`/api-docs`) +- Vitest test suite (unit, integration, performance, property-based, contract) +- Stryker mutation testing +- Prettier code formatting + +[Unreleased]: https://github.com/Ethereal-Future/FuTuRe/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/Ethereal-Future/FuTuRe/releases/tag/v1.0.0 diff --git a/backend/.env.example b/backend/.env.example index a7ff927..9d4d0ea 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -57,15 +57,19 @@ FRONTEND_BASE_URL=http://localhost:3000 # CONFIG_ENCRYPTION_KEY=your-strong-key PLATFORM_SECRET_KEY= -JWT_SECRET= -ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173 LOG_LEVEL=info -DATABASE_URL=postgresql://user:password@localhost:5432/future_remittance + +# Database +# connection_limit: max connections Prisma holds open +# Recommended: 10 (dev), 25-50 (prod). Rule of thumb: (num_cores * 2) + 1 +# pool_timeout: seconds to wait for a free connection before throwing +# Recommended: 10 (dev/prod) +DATABASE_URL=postgresql://user:password@localhost:5432/future_remittance?connection_limit=10&pool_timeout=10 # Optional: PgBouncer pooler URL (transaction pooling mode). # When set, Prisma connects through the pooler and prepared statements are # disabled automatically (?pgbouncer=true is appended if not already present). -# DATABASE_POOL_URL=postgresql://user:password@pgbouncer:6432/future_remittance +# DATABASE_POOL_URL=postgresql://user:password@pgbouncer:6432/future_remittance?connection_limit=25&pool_timeout=10 # Set to true to enable Prisma query logging outside of development mode. # In APP_ENV=development, query logging is always on. @@ -75,8 +79,8 @@ DATABASE_URL=postgresql://user:password@localhost:5432/future_remittance # Number of shards (default: 1 = no sharding, uses DATABASE_URL) DB_SHARD_COUNT=1 # Per-shard connection URLs (DB_SHARD_0_URL falls back to DATABASE_URL) -# DB_SHARD_0_URL=postgresql://user:password@shard0:5432/future_remittance -# DB_SHARD_1_URL=postgresql://user:password@shard1:5432/future_remittance +# DB_SHARD_0_URL=postgresql://user:password@shard0:5432/future_remittance?connection_limit=25&pool_timeout=10 +# DB_SHARD_1_URL=postgresql://user:password@shard1:5432/future_remittance?connection_limit=25&pool_timeout=10 # Max connections per shard pool DB_POOL_MAX=10 @@ -100,7 +104,6 @@ PERF_ALERT_ERROR_RATE=0.1 # NEW_RELIC_LICENSE_KEY= # Optional: DataDog API key (set to enable DataDog APM) # DD_API_KEY= -LOG_LEVEL=info # Email notifications (optional — stub used if not set) # EMAIL_HOST=smtp.example.com @@ -113,12 +116,14 @@ LOG_LEVEL=info # TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # TWILIO_AUTH_TOKEN=your-auth-token # TWILIO_FROM_NUMBER=+10000000000 + # Backup & Recovery BACKUP_DIR=./backups # 64-char hex key (openssl rand -hex 32) — required for encrypted backups # BACKUP_ENC_KEY= BACKUP_RETENTION_DAYS=7 BACKUP_INTERVAL_HOURS=24 + # Redis (optional — falls back to in-memory L1 cache if not set) # REDIS_URL=redis://localhost:6379 @@ -145,140 +150,3 @@ CACHE_TTL_FEE_S=120 # ALERT_EMAIL=ops@example.com # Slack webhook URL for notifications # SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL -# Backend environment configuration (example) -# -# Copy to `.env` and adjust as needed: -# cp .env.example .env - -# App environment: development | test | production -APP_ENV=development -CONFIG_VERSION=1 - -# Enable hot-reloading when `.env*` files change (ignored in APP_ENV=test) -CONFIG_WATCH=false - -# Server -PORT=3001 -ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173 - -# Stellar -STELLAR_NETWORK=testnet -HORIZON_URL=https://horizon-testnet.stellar.org - -# Required when sending non-XLM assets -ASSET_ISSUER= - -# Fee Bump Transactions -# Platform account secret key used to sponsor transaction fees for buyers with low XLM balance -# When set, payments from accounts below FEE_BUMP_THRESHOLD_XLM will be wrapped in a fee bump -PLATFORM_FEE_ACCOUNT_SECRET= -# XLM balance threshold below which fee bumping is applied (default: 2) -FEE_BUMP_THRESHOLD_XLM=2 - -# Payment Streaming -# AES-256-GCM key used to encrypt per-stream sender secrets at rest (required) -# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -STREAM_SECRET_ENCRYPTION_KEY= - -# Security -JWT_SECRET=change-me - -# MFA (TOTP) Encryption Key -# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -MFA_ENCRYPTION_KEY= - -# OAuth2 Configuration -# Google OAuth2 credentials (get from https://console.cloud.google.com) -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= - -# Server URLs for OAuth callbacks -SERVER_BASE_URL=http://localhost:3001 -FRONTEND_BASE_URL=http://localhost:3000 - -# WebSocket -# HMAC secret for signing outbound WebSocket message envelopes -# WS_MSG_SECRET=your-strong-random-secret - -# Optional: decrypt ENC(...) values -# CONFIG_ENCRYPTION_KEY=your-strong-key - -PLATFORM_SECRET_KEY= -JWT_SECRET= -ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173 -LOG_LEVEL=info -DATABASE_URL=postgresql://user:password@localhost:5432/future_remittance - -# Optional: PgBouncer pooler URL (transaction pooling mode). -# When set, Prisma connects through the pooler and prepared statements are -# disabled automatically (?pgbouncer=true is appended if not already present). -# DATABASE_POOL_URL=postgresql://user:password@pgbouncer:6432/future_remittance - -# Set to true to enable Prisma query logging outside of development mode. -# In APP_ENV=development, query logging is always on. -# PRISMA_QUERY_LOG=true - -# Database Sharding -# Number of shards (default: 1 = no sharding, uses DATABASE_URL) -DB_SHARD_COUNT=1 -# Per-shard connection URLs (DB_SHARD_0_URL falls back to DATABASE_URL) -# DB_SHARD_0_URL=postgresql://user:password@shard0:5432/future_remittance -# DB_SHARD_1_URL=postgresql://user:password@shard1:5432/future_remittance -# Max connections per shard pool -DB_POOL_MAX=10 - -# Database query timeout (milliseconds) -# Applied as PostgreSQL statement_timeout on each connection AND as a Node.js -# Promise.race guard on every Prisma operation. Prevents slow/hung queries from -# holding pool connections indefinitely. Default: 5000 (5 s). -DB_QUERY_TIMEOUT_MS=5000 - -RATE_LIMIT_WINDOW_MS=60000 -RATE_LIMIT_MAX=100 -RATE_LIMIT_MESSAGE="Too many requests, please try again later" -RATE_LIMIT_WHITELIST=192.168.1.1,10.0.0.0/8 - -# Performance Monitoring -# Alert when any API response exceeds this threshold (ms) -PERF_ALERT_RESPONSE_MS=2000 -# Alert when error rate exceeds this fraction (0.1 = 10%) -PERF_ALERT_ERROR_RATE=0.1 -# Optional: New Relic license key (set to enable New Relic APM) -# NEW_RELIC_LICENSE_KEY= -# Optional: DataDog API key (set to enable DataDog APM) -# DD_API_KEY= -LOG_LEVEL=info - -# Email notifications (optional — stub used if not set) -# EMAIL_HOST=smtp.example.com -# EMAIL_PORT=587 -# EMAIL_USER=notifications@example.com -# EMAIL_PASS=your-smtp-password -# EMAIL_FROM=noreply@futureremit.app - -# SMS notifications via Twilio (optional — stub used if not set) -# TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -# TWILIO_AUTH_TOKEN=your-auth-token -# TWILIO_FROM_NUMBER=+10000000000 -# Backup & Recovery -BACKUP_DIR=./backups -# 64-char hex key (openssl rand -hex 32) — required for encrypted backups -# BACKUP_ENC_KEY= -BACKUP_RETENTION_DAYS=7 -BACKUP_INTERVAL_HOURS=24 -# Redis (optional — falls back to in-memory L1 cache if not set) -# REDIS_URL=redis://localhost:6379 - -# CDN Configuration -CDN_ENABLED=false -# CDN_URL=https://cdn.example.com -# CDN_SECONDARY_URL=https://cdn2.example.com -# Comma-separated list of CDN edge regions -CDN_REGIONS=us-east-1,eu-west-1,ap-southeast-1 -# Max-age for immutable static assets (seconds, default 86400 = 1 day) -CDN_CACHE_MAX_AGE_S=86400 - -# Cache TTLs (seconds) -CACHE_TTL_BALANCE_S=30 -RATE_CACHE_TTL_S=60 -CACHE_TTL_FEE_S=120 diff --git a/backend/package.json b/backend/package.json index 9058c30..c514a6e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,6 +7,7 @@ "start": "node src/server.js", "test": "vitest run", "format": "prettier --write \"src/**/*.js\"", + "lint": "eslint src/**/*.js", "test:performance": "vitest run tests/performance.benchmark.test.js", "load-test:regression": "k6 run load-tests/k6/scenarios/performance-regression.js", "load-test:endpoints": "k6 run load-tests/k6/scenarios/api-endpoints.js", @@ -47,6 +48,7 @@ "zod": "^3.24.4" }, "devDependencies": { + "eslint": "^9.28.0", "prettier": "^3.3.3" } } diff --git a/backend/src/routes/stellar/accounts.js b/backend/src/routes/stellar/accounts.js index 48faeec..323db30 100644 --- a/backend/src/routes/stellar/accounts.js +++ b/backend/src/routes/stellar/accounts.js @@ -1,3 +1,4 @@ +import crypto from 'crypto'; import express from 'express'; import { body } from 'express-validator'; import * as StellarSDK from '@stellar/stellar-sdk'; @@ -64,6 +65,16 @@ router.post('/import', rules.importAccount, validate, async (req, res) => { router.get('/:publicKey', rules.publicKeyParam, validate, async (req, res) => { try { const balance = await StellarService.getBalance(req.params.publicKey); + + // ETag based on a hash of the balance payload (issue #356) + const etag = `"${crypto.createHash('sha256').update(JSON.stringify(balance)).digest('hex').slice(0, 16)}"`; + res.setHeader('ETag', etag); + res.setHeader('Cache-Control', 'no-cache'); + + if (req.headers['if-none-match'] === etag) { + return res.status(304).end(); + } + res.json(balance); } catch (error) { logError(req, error, { publicKey: req.params.publicKey }); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..40455f0 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js'; + +export default [ + js.configs.recommended, + { + files: ['backend/src/**/*.js'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { + process: 'readonly', + console: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + setInterval: 'readonly', + clearInterval: 'readonly', + Buffer: 'readonly', + URL: 'readonly', + }, + }, + rules: { + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + 'no-console': 'off', + }, + }, + { + ignores: ['**/node_modules/**', '**/dist/**', '**/build/**'], + }, +]; diff --git a/package.json b/package.json index fbeaa09..3688326 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "frontend" ], "scripts": { + "prepare": "husky", "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"", "dev:backend": "npm run dev --workspace=backend", "dev:frontend": "npm run dev --workspace=frontend", @@ -37,7 +38,20 @@ "test:contracts:registry": "vitest run --config vitest.contracts.config.js contracts/registry.test.js", "test:contracts": "npm run test:contracts:consumer && npm run test:contracts:provider && npm run test:contracts:registry" }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "**/*.{json,md,yml,yaml}": [ + "prettier --write" + ] + }, "devDependencies": { + "@eslint/js": "^9.28.0", + "eslint": "^9.28.0", + "husky": "^9.1.7", + "lint-staged": "^15.5.2", "@pact-foundation/pact": "^16.3.0", "@stryker-mutator/core": "^9.6.0", "@stryker-mutator/vitest-runner": "^9.6.0",