Skip to content
Merged
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
67 changes: 64 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,70 @@
# Server
# =============================================================================
# Fluxora Backend - Environment Configuration
# =============================================================================

# Server Configuration
PORT=3000
NODE_ENV=production

# =============================================================================
# PostgreSQL Database (docker-compose)
# =============================================================================
POSTGRES_USER=fluxora
POSTGRES_PASSWORD=fluxora_secure_password
POSTGRES_DB=fluxora
POSTGRES_PORT=5432

# Database Connection Pool Settings
DB_POOL_MIN=2
DB_POOL_MAX=10
DB_CONNECTION_TIMEOUT=5000
DB_IDLE_TIMEOUT=30000

# Full DATABASE_URL (auto-generated in docker-compose, override if needed)
# DATABASE_URL=postgresql://fluxora:fluxora_secure_password@localhost:5432/fluxora

# =============================================================================
# Redis Cache (optional - enable with docker-compose --profile redis)
# =============================================================================
REDIS_ENABLED=true
REDIS_URL=redis://localhost:6379
REDIS_PORT=6379

# Stellar testnet
# =============================================================================
# Stellar Network Configuration
# =============================================================================
STELLAR_NETWORK=testnet
HORIZON_URL=https://horizon-testnet.stellar.org
NETWORK_PASSPHRASE=Test SDF Network ; September 2015
HORIZON_NETWORK_PASSPHRASE=Test SDF Network ; September 2015

# Contract Addresses (required for production)
CONTRACT_ADDRESS_STREAMING=

# =============================================================================
# Security & Authentication
# =============================================================================
JWT_SECRET=your-jwt-secret-min-32-chars-long
JWT_EXPIRES_IN=24h
API_KEYS=api-key-1,api-key-2

# =============================================================================
# Request Handling
# =============================================================================
MAX_REQUEST_SIZE=1mb
MAX_JSON_DEPTH=20
REQUEST_TIMEOUT_MS=30000

# =============================================================================
# Observability
# =============================================================================
LOG_LEVEL=info
METRICS_ENABLED=true

# =============================================================================
# Webhooks (optional)
# =============================================================================
WEBHOOK_URL=
WEBHOOK_SECRET=

# Database configuration
DATABASE_URL=postgresql://user:password@localhost:5432/fluxora
Expand Down
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,83 @@

Express + TypeScript API for the Fluxora treasury streaming protocol. Today this repository exposes a minimal HTTP surface for stream CRUD and health checks. It now documents both the decimal-string serialization policy for chain/API amounts and the consumer-facing webhook signature verification contract the team intends to keep stable when delivery is enabled.

## Quick Start with Docker Compose

The fastest way to run Fluxora Backend with all dependencies:

```bash
# 1. Clone and navigate to the repository
git clone <repository-url>
cd Fluxora-Backend

# 2. Copy and configure environment variables
cp .env.example .env
# Edit .env with your secrets (JWT_SECRET, API_KEYS, etc.)

# 3. Start with PostgreSQL only
docker-compose up -d

# 4. Or start with PostgreSQL + Redis (full stack)
docker-compose --profile redis up -d

# 5. Check service health
curl http://localhost:3000/health

# 6. View logs
docker-compose logs -f app
```

### Docker Compose Services

| Service | Description | Default URL |
|---------|-------------|-------------|
| `app` | Fluxora Backend API | http://localhost:3000 |
| `postgres` | PostgreSQL 16 database | localhost:5432 |
| `redis` | Redis 7 cache (optional) | localhost:6379 |

### Configuration Profiles

- **Default** (`docker-compose up`): App + PostgreSQL
- **With Redis** (`--profile redis`): App + PostgreSQL + Redis
- **Full Stack** (`--profile full`): All services

### Health Checks

All services include health checks:
- **PostgreSQL**: `pg_isready` every 10s
- **Redis**: `redis-cli ping` every 10s
- **App**: HTTP health endpoint every 30s

The app waits for PostgreSQL to be healthy before starting.

### Database Initialization

PostgreSQL automatically initializes on first run using scripts in `init-db/`:
- `01-schema.sql`: Creates tables, indexes, and initial data
- Streams table for treasury protocol state
- Indexer state tracking
- Audit logs for chain-derived changes
- Webhook delivery tracking (future)

### Troubleshooting

```bash
# Reset everything (destroys data)
docker-compose down -v

# Rebuild after code changes
docker-compose up -d --build

# Check database logs
docker-compose logs postgres

# Connect to database
docker-compose exec postgres psql -U fluxora -d fluxora

# Scale app instances (with external load balancer)
docker-compose up -d --scale app=3
```

## Current status

- Implemented today:
Expand Down
108 changes: 108 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
version: '3.8'

services:
# PostgreSQL Database
postgres:
image: postgres:16-alpine
container_name: fluxora-postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-fluxora}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-fluxora_password}
POSTGRES_DB: ${POSTGRES_DB:-fluxora}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-db:/docker-entrypoint-initdb.d:ro
ports:
- "${POSTGRES_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-fluxora} -d ${POSTGRES_DB:-fluxora}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
- fluxora-network
restart: unless-stopped

# Optional Redis Cache
redis:
image: redis:7-alpine
container_name: fluxora-redis
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
ports:
- "${REDIS_PORT:-6379}:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
networks:
- fluxora-network
restart: unless-stopped
profiles:
- redis
- full

# Fluxora Backend Application
app:
build:
context: .
dockerfile: Dockerfile
container_name: fluxora-app
environment:
NODE_ENV: ${NODE_ENV:-production}
PORT: ${PORT:-3000}
DATABASE_URL: postgresql://${POSTGRES_USER:-fluxora}:${POSTGRES_PASSWORD:-fluxora_password}@postgres:5432/${POSTGRES_DB:-fluxora}
DB_POOL_MIN: ${DB_POOL_MIN:-2}
DB_POOL_MAX: ${DB_POOL_MAX:-10}
DB_CONNECTION_TIMEOUT: ${DB_CONNECTION_TIMEOUT:-5000}
DB_IDLE_TIMEOUT: ${DB_IDLE_TIMEOUT:-30000}
REDIS_URL: redis://redis:6379
REDIS_ENABLED: ${REDIS_ENABLED:-true}
STELLAR_NETWORK: ${STELLAR_NETWORK:-testnet}
HORIZON_URL: ${HORIZON_URL:-https://horizon-testnet.stellar.org}
HORIZON_NETWORK_PASSPHRASE: ${HORIZON_NETWORK_PASSPHRASE:-Test SDF Network ; September 2015}
CONTRACT_ADDRESS_STREAMING: ${CONTRACT_ADDRESS_STREAMING:-}
JWT_SECRET: ${JWT_SECRET}
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-24h}
API_KEYS: ${API_KEYS}
LOG_LEVEL: ${LOG_LEVEL:-info}
METRICS_ENABLED: ${METRICS_ENABLED:-true}
WEBHOOK_URL: ${WEBHOOK_URL}
WEBHOOK_SECRET: ${WEBHOOK_SECRET}
ENABLE_STREAM_VALIDATION: ${ENABLE_STREAM_VALIDATION:-true}
ENABLE_RATE_LIMIT: ${ENABLE_RATE_LIMIT:-false}
MAX_REQUEST_SIZE: ${MAX_REQUEST_SIZE:-1mb}
MAX_JSON_DEPTH: ${MAX_JSON_DEPTH:-20}
REQUEST_TIMEOUT_MS: ${REQUEST_TIMEOUT_MS:-30000}
ports:
- "${PORT:-3000}:3000"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
networks:
- fluxora-network
restart: unless-stopped
# Uncomment to use Redis profile
# profiles:
# - full

volumes:
postgres_data:
name: fluxora-postgres-data
redis_data:
name: fluxora-redis-data

networks:
fluxora-network:
name: fluxora-network
driver: bridge
89 changes: 89 additions & 0 deletions init-db/01-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
-- Initialize Fluxora database schema
-- This script runs automatically when PostgreSQL container starts for the first time

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Streams table for treasury streaming protocol
CREATE TABLE IF NOT EXISTS streams (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stream_id VARCHAR(255) UNIQUE NOT NULL,
sender VARCHAR(56) NOT NULL,
recipient VARCHAR(56) NOT NULL,
deposit_amount VARCHAR(64) NOT NULL,
rate_per_second VARCHAR(64) NOT NULL,
start_time BIGINT NOT NULL,
end_time BIGINT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active',
contract_address VARCHAR(56),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
metadata JSONB DEFAULT '{}'
);

-- Index for stream lookups
CREATE INDEX IF NOT EXISTS idx_streams_sender ON streams(sender);
CREATE INDEX IF NOT EXISTS idx_streams_recipient ON streams(recipient);
CREATE INDEX IF NOT EXISTS idx_streams_status ON streams(status);
CREATE INDEX IF NOT EXISTS idx_streams_created_at ON streams(created_at);

-- Indexer state tracking
CREATE TABLE IF NOT EXISTS indexer_state (
id SERIAL PRIMARY KEY,
cursor VARCHAR(255),
last_ledger BIGINT,
last_indexed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
status VARCHAR(20) NOT NULL DEFAULT 'healthy',
error_count INTEGER DEFAULT 0,
last_error TEXT
);

-- Audit log for chain-derived state changes
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
event_type VARCHAR(50) NOT NULL,
stream_id VARCHAR(255) REFERENCES streams(stream_id),
transaction_hash VARCHAR(64),
ledger_sequence BIGINT,
old_values JSONB,
new_values JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_audit_logs_stream_id ON audit_logs(stream_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_event_type ON audit_logs(event_type);
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);

-- Webhook delivery tracking (for future implementation)
CREATE TABLE IF NOT EXISTS webhook_deliveries (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
delivery_id VARCHAR(255) UNIQUE NOT NULL,
event_type VARCHAR(50) NOT NULL,
payload JSONB NOT NULL,
endpoint_url TEXT NOT NULL,
status VARCHAR(20) NOT NULL,
attempts INTEGER DEFAULT 0,
last_attempt_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
error_message TEXT
);

CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_delivery_id ON webhook_deliveries(delivery_id);
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_status ON webhook_deliveries(status);
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_created_at ON webhook_deliveries(created_at);

-- API keys for authentication
CREATE TABLE IF NOT EXISTS api_keys (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
key_hash VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
permissions JSONB DEFAULT '[]',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_used_at TIMESTAMP WITH TIME ZONE
);

-- Insert initial indexer state
INSERT INTO indexer_state (cursor, last_ledger, status)
VALUES (NULL, 0, 'not_configured')
ON CONFLICT DO NOTHING;
Loading