Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ REQUEST_TIMEOUT=30000
# Enable request validation
ENABLE_REQUEST_VALIDATION=true

# ============================================
# ADMIN CONFIGURATION
# ============================================

# Comma-separated Stellar public keys authorized to perform admin actions.
# Required to use: /api/auto-rebalancer/start|stop|force-check|history
# and /api/rebalance/history/sync-onchain
# Example: ADMIN_PUBLIC_KEYS=GADMIN1PUBLICKEY56CHARS...,GADMIN2PUBLICKEY56CHARS...
# Without this set, all admin-protected routes will return HTTP 503
ADMIN_PUBLIC_KEYS=

# ============================================
# DEVELOPMENT/DEBUG SETTINGS
# ============================================
Expand Down
83 changes: 46 additions & 37 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import { RebalancingService } from './monitoring/rebalancer.js'
import { AutoRebalancerService } from './services/autoRebalancer.js'
import { logger } from './utils/logger.js'
import { databaseService } from './services/databaseService.js'
import { validateStartupConfigOrThrow, buildStartupSummary, type StartupConfig } from './config/startupConfig.js'
import { getFeatureFlags, getPublicFeatureFlags } from './config/featureFlags.js'
import { isRedisAvailable, logQueueStartup } from './queue/connection.js'
import { closeAllQueues } from './queue/queues.js'
import { startQueueScheduler } from './queue/scheduler.js'
import { startPortfolioCheckWorker, stopPortfolioCheckWorker } from './queue/workers/portfolioCheckWorker.js'
import { startRebalanceWorker, stopRebalanceWorker } from './queue/workers/rebalanceWorker.js'
import { startAnalyticsSnapshotWorker, stopAnalyticsSnapshotWorker } from './queue/workers/analyticsSnapshotWorker.js'
import { contractEventIndexerService } from './services/contractEventIndexer.js'
import { validateStartupConfigOrThrow, buildStartupSummary, type StartupConfig } from './config/startupConfig.js'
import { getFeatureFlags, getPublicFeatureFlags } from './config/featureFlags.js'
import { isRedisAvailable, logQueueStartup } from './queue/connection.js'
import { closeAllQueues } from './queue/queues.js'
import { startQueueScheduler } from './queue/scheduler.js'
import { startPortfolioCheckWorker, stopPortfolioCheckWorker } from './queue/workers/portfolioCheckWorker.js'
import { startRebalanceWorker, stopRebalanceWorker } from './queue/workers/rebalanceWorker.js'
import { startAnalyticsSnapshotWorker, stopAnalyticsSnapshotWorker } from './queue/workers/analyticsSnapshotWorker.js'
import { contractEventIndexerService } from './services/contractEventIndexer.js'

let startupConfig: StartupConfig
try {
Expand Down Expand Up @@ -235,6 +235,15 @@ server.listen(port, async () => {
console.log(`🚀 Server running on port ${port}`)
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`)
console.log(`CoinGecko API Key: ${!!process.env.COINGECKO_API_KEY ? 'SET' : 'NOT SET'}`)

// Warn if ADMIN_PUBLIC_KEYS is not set
if (!process.env.ADMIN_PUBLIC_KEYS) {
logger.warn(
'ADMIN_PUBLIC_KEYS is not set — admin routes (/api/auto-rebalancer/*, ' +
'/api/rebalance/history/sync-onchain) will return 503. ' +
'Set ADMIN_PUBLIC_KEYS in .env to enable admin functionality.'
)
}

// ── BullMQ / Redis setup ────────────────────────────────────────────────
const redisAvailable = await isRedisAvailable()
Expand All @@ -260,7 +269,7 @@ server.listen(port, async () => {
process.env.NODE_ENV === 'production' ||
process.env.ENABLE_AUTO_REBALANCER === 'true'

if (shouldStartAutoRebalancer) {
if (shouldStartAutoRebalancer) {
try {
console.log('[AUTO-REBALANCER] Starting automatic rebalancing service...')
await autoRebalancer.start()
Expand All @@ -279,17 +288,17 @@ server.listen(port, async () => {
} catch (error) {
console.error('[AUTO-REBALANCER] ❌ Failed to start automatic rebalancing service:', error)
}
} else {
console.log('[AUTO-REBALANCER] Automatic rebalancing disabled in development mode')
console.log('[AUTO-REBALANCER] Set ENABLE_AUTO_REBALANCER=true to enable in development')
}

// Contract event indexer (on-chain source-of-truth history)
try {
await contractEventIndexerService.start()
} catch (error) {
console.error('[CHAIN-INDEXER] Failed to start:', error)
}
} else {
console.log('[AUTO-REBALANCER] Automatic rebalancing disabled in development mode')
console.log('[AUTO-REBALANCER] Set ENABLE_AUTO_REBALANCER=true to enable in development')
}
// Contract event indexer (on-chain source-of-truth history)
try {
await contractEventIndexerService.start()
} catch (error) {
console.error('[CHAIN-INDEXER] Failed to start:', error)
}

console.log('Available endpoints:')
console.log(` Health: http://localhost:${port}/health`)
Expand All @@ -303,10 +312,10 @@ server.listen(port, async () => {
const gracefulShutdown = async (signal: string) => {
console.log(`\n[SHUTDOWN] ${signal} received, shutting down gracefully...`)

// Stop auto-rebalancer
try {
autoRebalancer.stop()
console.log('[SHUTDOWN] Auto-rebalancer stopped')
// Stop auto-rebalancer
try {
autoRebalancer.stop()
console.log('[SHUTDOWN] Auto-rebalancer stopped')
} catch (error) {
console.error('[SHUTDOWN] Error stopping auto-rebalancer:', error)
}
Expand All @@ -331,18 +340,18 @@ const gracefulShutdown = async (signal: string) => {
console.error('[SHUTDOWN] Error closing queues:', error)
}

// Close database connection
try {
await contractEventIndexerService.stop()
console.log('[SHUTDOWN] Contract event indexer stopped')
} catch (error) {
console.error('[SHUTDOWN] Error stopping contract event indexer:', error)
}

try {
databaseService.close()
console.log('[SHUTDOWN] Database connection closed')
} catch (error) {
// Close database connection
try {
await contractEventIndexerService.stop()
console.log('[SHUTDOWN] Contract event indexer stopped')
} catch (error) {
console.error('[SHUTDOWN] Error stopping contract event indexer:', error)
}
try {
databaseService.close()
console.log('[SHUTDOWN] Database connection closed')
} catch (error) {
console.error('[SHUTDOWN] Error closing database:', error)
}

Expand Down
Loading