# For mobile/LAN testing set PUBLIC_BASE_URL to your machine IP, not localhost.
# Example: PUBLIC_BASE_URL=http://192.168.1.12:4000
NODE_ENV=development
PORT=4000
HOST=0.0.0.0
PUBLIC_BASE_URL=http://localhost:4000
PUBLIC_APP_URL=https://oudelaa.com
RESPONSE_ENVELOPE_ENABLED=false
SWAGGER_ENABLED=true
GLOBAL_PREFIX=api/v1
# Add every frontend origin used by web/mobile debug tools.
CORS_ORIGINS=http://localhost:3000,http://192.168.1.14:3000,http://192.168.1.14:5173
MONGODB_URI=mongodb://127.0.0.1:27017/oudelaa
MONGODB_AUTO_INDEX=true
MONGODB_MIN_POOL_SIZE=5
MONGODB_MAX_POOL_SIZE=100
MONGODB_MAX_IDLE_TIME_MS=60000
MONGODB_SERVER_SELECTION_TIMEOUT_MS=10000
MONGODB_SOCKET_TIMEOUT_MS=45000
# Search keeps the same API contract. "auto" uses Atlas Search when available
# and falls back to MongoDB regex search on local/community deployments.
SEARCH_ENGINE=auto
SEARCH_ATLAS_USER_INDEX=users_search
SEARCH_ATLAS_POST_INDEX=posts_search
SEARCH_FALLBACK_ENABLED=true
SEARCH_ATLAS_RETRY_SECONDS=300
JWT_ACCESS_SECRET=change_me_access_secret
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_SECRET=change_me_refresh_secret
JWT_REFRESH_EXPIRES_IN=30d
BCRYPT_SALT_ROUNDS=12
REFRESH_TOKEN_HASH_SECRET=
HTTP_BODY_LIMIT=1mb
# Controller execution deadline. Override individual long-running routes with @RequestTimeout().
HTTP_REQUEST_TIMEOUT_MS=30000
# Low-level slow-client protection and connection reuse limits.
HTTP_SERVER_REQUEST_TIMEOUT_MS=120000
HTTP_HEADERS_TIMEOUT_MS=15000
HTTP_KEEP_ALIVE_TIMEOUT_MS=5000
HTTP_MAX_REQUESTS_PER_SOCKET=1000
# Time allowed for in-flight controller requests after SIGTERM/SIGINT.
SHUTDOWN_GRACE_PERIOD_MS=30000
METRICS_EVENT_LOOP_RESOLUTION_MS=20
METRICS_EVENT_LOOP_LAG_WARN_MS=100
METRICS_MAX_ROUTES=500
MODERATION_BLOCKED_TERMS=
PASSWORD_RESET_CODE_EXPIRES_MINUTES=10
PASSWORD_RESET_MAX_ATTEMPTS=5
PASSWORD_RESET_TOKEN_SECRET=
PASSWORD_RESET_TOKEN_EXPIRES_IN=15m
EMAIL_VERIFICATION_CODE_EXPIRES_MINUTES=10
EMAIL_VERIFICATION_MAX_ATTEMPTS=5
SWAGGER_TITLE=Oudelaa API
SWAGGER_DESCRIPTION=Social media backend API documentation
SWAGGER_VERSION=1.0.0
SWAGGER_PATH=docs
LOG_LEVEL=log
REQUEST_LOGGING_ENABLED=true
FEED_CACHE_ENABLED=true
FEED_CACHE_USER_TTL_SECONDS=15
FEED_CACHE_TRENDING_TTL_SECONDS=30
FEED_CACHE_RANKING_PROFILE_TTL_SECONDS=30
FEED_TIMING_LOGS_ENABLED=false
HTTP_COMPRESSION_ENABLED=true
HTTP_COMPRESSION_THRESHOLD_BYTES=1024

REDIS_ENABLED=false
REDIS_URL=
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_USERNAME=
REDIS_PASSWORD=
REDIS_DB=0
REDIS_KEY_PREFIX=oudelaa
REDIS_SOCKET_ADAPTER_ENABLED=false

QUEUE_ENABLED=false
QUEUE_NAME=app-jobs
QUEUE_DEFAULT_ATTEMPTS=3
QUEUE_DEFAULT_BACKOFF_MS=1000
QUEUE_REMOVE_ON_COMPLETE=true
QUEUE_WORKER_CONCURRENCY=5

# Phase 1 microservice routing (false keeps the current in-process path)
NOTIFICATION_SERVICE_ENABLED=false
# The polling dispatcher is independently disabled for the current production monolith.
OUTBOX_DISPATCHER_ENABLED=false
OUTBOX_POLL_INTERVAL_MS=1000
OUTBOX_BATCH_SIZE=25
OUTBOX_MAX_ATTEMPTS=8
OUTBOX_LEASE_TIMEOUT_MS=60000
OUTBOX_RETRY_BASE_MS=1000
OUTBOX_RETRY_MAX_MS=60000
# Local development only. Production must inject non-guest credentials and TLS/amqps.
RABBITMQ_URL=amqp://guest:guest@127.0.0.1:5672
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PORT=5672
RABBITMQ_USERNAME=
RABBITMQ_PASSWORD=
RABBITMQ_VHOST=/
RABBITMQ_TLS_ENABLED=false
RABBITMQ_TLS_REQUIRED=true
RABBITMQ_CA_CERT_BASE64=
RABBITMQ_CA_CERT_PATH=
RABBITMQ_CLIENT_CERT_BASE64=
RABBITMQ_CLIENT_CERT_PATH=
RABBITMQ_CLIENT_KEY_BASE64=
RABBITMQ_CLIENT_KEY_PATH=
RABBITMQ_TLS_REJECT_UNAUTHORIZED=true
RABBITMQ_SERVERNAME=
RABBITMQ_HEARTBEAT_SECONDS=10
RABBITMQ_RECONNECT_SECONDS=5
RABBITMQ_CONNECTION_TIMEOUT_MS=10000
NOTIFICATION_RPC_TIMEOUT_MS=3000
NOTIFICATION_RPC_RETRIES=1
NOTIFICATION_CIRCUIT_FAILURE_THRESHOLD=3
NOTIFICATION_CIRCUIT_RESET_MS=15000
NOTIFICATION_SERVICE_PORT=4001
NOTIFICATION_RMQ_PREFETCH=10

STORAGE_PROVIDER=local
MEDIA_ACCESS_MODE=direct
STORAGE_BASE_PATH=uploads
# In Docker/production with local storage, mount a persistent volume to /app/uploads
# or to the runtime path resolved from STORAGE_BASE_PATH.
# Leave empty for local storage unless you want a dedicated CDN/base URL.
STORAGE_PUBLIC_BASE_URL=
S3_BUCKET=
S3_REGION=auto
S3_ENDPOINT=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_FORCE_PATH_STYLE=false
# Optional: writes then deletes a tiny health object when GET /media/health runs.
# Keep false unless you explicitly want to verify S3 write permissions.
S3_HEALTH_WRITE_TEST_ENABLED=false
IMAGE_PROCESSING_ENABLED=true
IMAGE_PROCESSING_FFMPEG_PATH=ffmpeg
IMAGE_PROCESSING_LOW_WIDTH=360
IMAGE_PROCESSING_MEDIUM_WIDTH=720
IMAGE_PROCESSING_HIGH_WIDTH=1280
IMAGE_PROCESSING_QUALITY=78
VIDEO_PROCESSING_ENABLED=false
VIDEO_PROCESSING_FFMPEG_PATH=ffmpeg
VIDEO_PROCESSING_FFPROBE_PATH=ffprobe
VIDEO_PROCESSING_MAX_WIDTH=1280
VIDEO_PROCESSING_MAX_FPS=30
VIDEO_PROCESSING_CRF=28
VIDEO_PROCESSING_PRESET=veryfast
VIDEO_PROCESSING_AUDIO_BITRATE_KBPS=128
VIDEO_PROCESSING_GENERATE_THUMBNAILS=true
VIDEO_PROCESSING_GENERATE_HLS=true
VIDEO_PROCESSING_HLS_SEGMENT_DURATION_SECONDS=4
VIDEO_PROCESSING_THUMBNAIL_WIDTH=720
AUDIO_PROCESSING_ENABLED=false
AUDIO_WAVEFORM_PEAKS=100

GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
# Match this to the same reachable host/IP used in PUBLIC_BASE_URL.
GOOGLE_CALLBACK_URL=http://192.168.1.14:4000/api/v1/auth/google/callback
EMAIL_ENABLED=false
EMAIL_SMTP_HOST=smtp.gmail.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_SECURE=false
EMAIL_SMTP_USER=
EMAIL_SMTP_PASS=
EMAIL_FROM_NAME=Oudelaa
EMAIL_FROM_EMAIL=
# Enables POST /api/v1/media/ai/text-to-music.
# Uses Google Vertex AI publisher model endpoint, not GEMINI_API_KEY directly.
# Auth priority:
# 1. AI_MUSIC_API_KEY when set.
# 2. GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64 when set.
# 3. GOOGLE_APPLICATION_CREDENTIALS file path handled by google-auth-library.
AI_MUSIC_ENABLED=false
AI_MUSIC_API_KEY=
GOOGLE_APPLICATION_CREDENTIALS_JSON_BASE64=
AI_MUSIC_PROJECT_ID=accordev
AI_MUSIC_LOCATION=us-central1
AI_MUSIC_MODEL=lyria-002


SUPERADMIN_EMAIL=admin@oudelaa.com
SUPERADMIN_PASSWORD=SuperAdminStrongPass123!
# Required in production. Generate with bcrypt and leave SUPERADMIN_PASSWORD empty there.
SUPERADMIN_PASSWORD_HASH=
SUPERADMIN_ACCESS_SECRET=change_me_superadmin_access_secret
SUPERADMIN_ACCESS_EXPIRES_IN=15m
SUPERADMIN_REFRESH_SECRET=change_me_superadmin_refresh_secret
SUPERADMIN_REFRESH_EXPIRES_IN=30d
SUPERADMIN_TOTP_SECRET=
