# 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=