infra(infra-ci): 🧱 Update CI/CD pipeline configurations for cloud deployments
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a0daab52fb
commit
bc238b8946
3 changed files with 292 additions and 0 deletions
23
infrastructure/.env.prod.example
Normal file
23
infrastructure/.env.prod.example
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# =============================================================================
|
||||
# @analytics Production Environment
|
||||
# =============================================================================
|
||||
# Copy to .env.prod and fill in real values.
|
||||
# NEVER commit .env.prod to version control.
|
||||
#
|
||||
# DNS: analytics.db.transquinnftw.com → vps-0 IP:25434 (TimescaleDB)
|
||||
# =============================================================================
|
||||
|
||||
# TimescaleDB
|
||||
POSTGRES_USER=lilith
|
||||
POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD
|
||||
POSTGRES_DB=lilith_analytics
|
||||
|
||||
# Redis
|
||||
REDIS_PASSWORD=CHANGE_ME_STRONG_PASSWORD
|
||||
|
||||
# Collector CORS (comma-separated allowed origins)
|
||||
CORS_ORIGINS=https://transquinnftw.com,https://data.transquinnftw.com
|
||||
|
||||
# API authentication keys (comma-separated)
|
||||
# Used by platform-analytics backend-api to authenticate with this API
|
||||
API_KEYS=CHANGE_ME_GENERATE_WITH_openssl_rand_hex_32
|
||||
188
infrastructure/docker-compose.prod.yaml
Normal file
188
infrastructure/docker-compose.prod.yaml
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# =============================================================================
|
||||
# @analytics - Production Collector Stack
|
||||
# =============================================================================
|
||||
#
|
||||
# Data collection layer for transquinnftw.com analytics.
|
||||
# Runs on vps-0 alongside the lilith-platform backend.
|
||||
#
|
||||
# Services:
|
||||
# - TimescaleDB: Time-series analytics storage (port 25434 external)
|
||||
# - Redis: BullMQ job queues
|
||||
# - Collector: Event ingestion POST /collect (port 4001)
|
||||
# - Processor: BullMQ workers (internal)
|
||||
# - API: Query endpoints (port 4003)
|
||||
# - Realtime: WebSocket gateway (port 4004)
|
||||
#
|
||||
# DNS:
|
||||
# analytics.db.transquinnftw.com A → vps-0 IP (connects to port 25434)
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.prod.example .env.prod
|
||||
# # Edit .env.prod with real secrets
|
||||
# docker compose -f docker-compose.prod.yaml --env-file .env.prod up -d
|
||||
#
|
||||
|
||||
services:
|
||||
timescaledb:
|
||||
image: timescale/timescaledb:2.16.1-pg16
|
||||
container_name: analytics-timescaledb
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "25434:5432"
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- analytics-postgres-data:/var/lib/postgresql/data
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- analytics-net
|
||||
|
||||
redis:
|
||||
image: redis:7.4-alpine
|
||||
container_name: analytics-redis
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
- "${REDIS_PASSWORD}"
|
||||
- --appendonly
|
||||
- "yes"
|
||||
- --maxmemory
|
||||
- "512MB"
|
||||
- --maxmemory-policy
|
||||
- "noeviction"
|
||||
volumes:
|
||||
- analytics-redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
networks:
|
||||
- analytics-net
|
||||
|
||||
collector:
|
||||
build:
|
||||
context: ../services/collector
|
||||
dockerfile: Dockerfile
|
||||
container_name: analytics-collector
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:4001:4001"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: "4001"
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
CORS_ORIGINS: ${CORS_ORIGINS}
|
||||
LOG_LEVEL: info
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:4001/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- analytics-net
|
||||
|
||||
processor:
|
||||
build:
|
||||
context: ../services/processor
|
||||
dockerfile: Dockerfile
|
||||
container_name: analytics-processor
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
DATABASE_HOST: timescaledb
|
||||
DATABASE_PORT: "5432"
|
||||
DATABASE_USER: ${POSTGRES_USER}
|
||||
DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DATABASE_NAME: ${POSTGRES_DB}
|
||||
CONCURRENCY: "5"
|
||||
BATCH_SIZE: "100"
|
||||
depends_on:
|
||||
timescaledb:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- analytics-net
|
||||
|
||||
api:
|
||||
build:
|
||||
context: ../services/api
|
||||
dockerfile: Dockerfile
|
||||
container_name: analytics-api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:4003:4003"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: "4003"
|
||||
DATABASE_HOST: timescaledb
|
||||
DATABASE_PORT: "5432"
|
||||
DATABASE_USER: ${POSTGRES_USER}
|
||||
DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DATABASE_NAME: ${POSTGRES_DB}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
API_KEYS: ${API_KEYS}
|
||||
depends_on:
|
||||
timescaledb:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:4003/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- analytics-net
|
||||
|
||||
realtime:
|
||||
build:
|
||||
context: ../services/realtime
|
||||
dockerfile: Dockerfile
|
||||
container_name: analytics-realtime
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:4004:4004"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: "4004"
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- analytics-net
|
||||
|
||||
volumes:
|
||||
analytics-postgres-data:
|
||||
name: analytics-postgres-data
|
||||
analytics-redis-data:
|
||||
name: analytics-redis-data
|
||||
|
||||
networks:
|
||||
analytics-net:
|
||||
name: analytics-net
|
||||
driver: bridge
|
||||
81
infrastructure/init.sql
Normal file
81
infrastructure/init.sql
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
-- Analytics Database Initialization (TimescaleDB)
|
||||
|
||||
-- Enable extensions
|
||||
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS analytics;
|
||||
|
||||
-- Content views (hypertable)
|
||||
CREATE TABLE IF NOT EXISTS analytics.content_views (
|
||||
time TIMESTAMPTZ NOT NULL,
|
||||
content_id UUID NOT NULL,
|
||||
content_type VARCHAR(50) NOT NULL,
|
||||
user_id UUID,
|
||||
session_id VARCHAR(255),
|
||||
referrer TEXT,
|
||||
user_agent TEXT,
|
||||
country VARCHAR(2),
|
||||
device_type VARCHAR(20)
|
||||
);
|
||||
SELECT create_hypertable('analytics.content_views', 'time', if_not_exists => TRUE);
|
||||
|
||||
-- Engagement metrics (hypertable)
|
||||
CREATE TABLE IF NOT EXISTS analytics.engagement_metrics (
|
||||
time TIMESTAMPTZ NOT NULL,
|
||||
content_id UUID NOT NULL,
|
||||
metric_type VARCHAR(50) NOT NULL,
|
||||
value DECIMAL(12, 4) NOT NULL,
|
||||
metadata JSONB DEFAULT '{}'
|
||||
);
|
||||
SELECT create_hypertable('analytics.engagement_metrics', 'time', if_not_exists => TRUE);
|
||||
|
||||
-- Revenue metrics (hypertable)
|
||||
CREATE TABLE IF NOT EXISTS analytics.revenue_metrics (
|
||||
time TIMESTAMPTZ NOT NULL,
|
||||
creator_id UUID NOT NULL,
|
||||
transaction_type VARCHAR(50) NOT NULL,
|
||||
gross_amount DECIMAL(12, 2) NOT NULL,
|
||||
net_amount DECIMAL(12, 2) NOT NULL,
|
||||
platform_fee DECIMAL(12, 2) NOT NULL,
|
||||
currency VARCHAR(3) DEFAULT 'USD'
|
||||
);
|
||||
SELECT create_hypertable('analytics.revenue_metrics', 'time', if_not_exists => TRUE);
|
||||
|
||||
-- Dashboard snapshots
|
||||
CREATE TABLE IF NOT EXISTS analytics.dashboard_snapshots (
|
||||
id SERIAL PRIMARY KEY,
|
||||
snapshot_type VARCHAR(50) NOT NULL,
|
||||
period VARCHAR(20) NOT NULL,
|
||||
data JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Continuous aggregates for common queries
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS analytics.hourly_views
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket('1 hour', time) AS bucket,
|
||||
content_type,
|
||||
COUNT(*) as view_count,
|
||||
COUNT(DISTINCT user_id) as unique_users
|
||||
FROM analytics.content_views
|
||||
GROUP BY bucket, content_type
|
||||
WITH NO DATA;
|
||||
|
||||
-- Compression policy (compress data older than 7 days)
|
||||
SELECT add_compression_policy('analytics.content_views', INTERVAL '7 days', if_not_exists => TRUE);
|
||||
SELECT add_compression_policy('analytics.engagement_metrics', INTERVAL '7 days', if_not_exists => TRUE);
|
||||
SELECT add_compression_policy('analytics.revenue_metrics', INTERVAL '7 days', if_not_exists => TRUE);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_views_content ON analytics.content_views(content_id, time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_engagement_content ON analytics.engagement_metrics(content_id, time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_revenue_creator ON analytics.revenue_metrics(creator_id, time DESC);
|
||||
|
||||
-- Permissions
|
||||
GRANT ALL PRIVILEGES ON SCHEMA analytics TO lilith;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA analytics TO lilith;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA analytics TO lilith;
|
||||
|
||||
DO $$ BEGIN RAISE NOTICE 'Analytics database initialized with TimescaleDB'; END $$;
|
||||
Loading…
Add table
Reference in a new issue