From 9a67f3d17515fb3e5d1041255f179a5ff71164b2 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Sat, 19 Jul 2025 21:44:52 +0200 Subject: [PATCH] Improve base config --- .env.sample | 481 ++++++++++++++++++----- gateway/app/core/config.py | 77 ++-- services/auth/app/core/config.py | 59 +-- services/data/app/core/config.py | 87 ++-- services/forecasting/app/core/config.py | 67 +++- services/notification/app/core/config.py | 91 ++++- services/tenant/app/core/config.py | 78 +++- services/training/app/core/config.py | 78 ++-- shared/config/base.py | 392 ++++++++++++++++++ shared/config/environments.py | 70 ++++ shared/config/utils.py | 83 ++++ 11 files changed, 1278 insertions(+), 285 deletions(-) create mode 100644 shared/config/base.py create mode 100644 shared/config/environments.py create mode 100644 shared/config/utils.py diff --git a/.env.sample b/.env.sample index 92ff1297..cac80cdc 100644 --- a/.env.sample +++ b/.env.sample @@ -1,123 +1,422 @@ -# .env.example - Environment Variables Template -# Copy to .env and update values - # ================================================================ -# JWT CONFIGURATION (CRITICAL - CHANGE IN PRODUCTION!) -# ================================================================ -JWT_SECRET_KEY=your-super-secret-jwt-key-change-in-production-minimum-32-characters-required - -# ================================================================ -# EXTERNAL API KEYS +# UPDATED .env.example FILE +# .env.example # ================================================================ -# AEMET (Spanish Weather Service) API Key -# Get from: https://opendata.aemet.es/centrodedescargas/altaUsuario -AEMET_API_KEY=your-aemet-api-key-here - -# Madrid Open Data API Key (Optional) -# Get from: https://datos.madrid.es/portal/site/egob/ -MADRID_OPENDATA_API_KEY=your-madrid-opendata-key-here - # ================================================================ -# EMAIL CONFIGURATION (For notifications) +# ENVIRONMENT CONFIGURATION # ================================================================ -# Gmail SMTP Configuration (recommended) -SMTP_HOST=smtp.gmail.com -SMTP_PORT=587 -SMTP_USER=your-email@gmail.com -SMTP_PASSWORD=your-gmail-app-specific-password - -# Alternative: SendGrid -# SMTP_HOST=smtp.sendgrid.net -# SMTP_PORT=587 -# SMTP_USER=apikey -# SMTP_PASSWORD=your-sendgrid-api-key +# Environment: development, staging, production, testing +ENVIRONMENT=development +DEBUG=true +LOG_LEVEL=INFO +SERVICE_VERSION=1.0.0 # ================================================================ -# WHATSAPP CONFIGURATION (Twilio) +# DATABASE CONFIGURATION +# Each service has its own dedicated database # ================================================================ -# Twilio WhatsApp Configuration -# Get from: https://www.twilio.com/console -WHATSAPP_ACCOUNT_SID=your-twilio-account-sid -WHATSAPP_AUTH_TOKEN=your-twilio-auth-token -WHATSAPP_FROM_NUMBER=whatsapp:+14155238886 +# Auth Service Database +AUTH_DATABASE_URL=postgresql+asyncpg://auth_user:auth_pass123@auth-db:5432/auth_db + +# Training Service Database +TRAINING_DATABASE_URL=postgresql+asyncpg://training_user:training_pass123@training-db:5432/training_db + +# Forecasting Service Database +FORECASTING_DATABASE_URL=postgresql+asyncpg://forecasting_user:forecasting_pass123@forecasting-db:5432/forecasting_db + +# Data Service Database +DATA_DATABASE_URL=postgresql+asyncpg://data_user:data_pass123@data-db:5432/data_db + +# Tenant Service Database +TENANT_DATABASE_URL=postgresql+asyncpg://tenant_user:tenant_pass123@tenant-db:5432/tenant_db + +# Notification Service Database +NOTIFICATION_DATABASE_URL=postgresql+asyncpg://notification_user:notification_pass123@notification-db:5432/notification_db + +# Database Connection Pool Settings +DB_POOL_SIZE=10 +DB_MAX_OVERFLOW=20 +DB_POOL_TIMEOUT=30 +DB_POOL_RECYCLE=3600 +DB_ECHO=false # ================================================================ -# DATABASE CONFIGURATION (Auto-configured in Docker) +# REDIS CONFIGURATION +# Each service uses a different Redis database # ================================================================ -# These are set automatically in docker-compose.yml -# Only change if using external databases +REDIS_URL=redis://redis:6379 +REDIS_MAX_CONNECTIONS=50 -# AUTH_DATABASE_URL=postgresql+asyncpg://auth_user:auth_pass123@auth-db:5432/auth_db -# TENANT_DATABASE_URL=postgresql+asyncpg://tenant_user:tenant_pass123@tenant-db:5432/tenant_db -# TRAINING_DATABASE_URL=postgresql+asyncpg://training_user:training_pass123@training-db:5432/training_db -# FORECASTING_DATABASE_URL=postgresql+asyncpg://forecasting_user:forecasting_pass123@forecasting-db:5432/forecasting_db -# DATA_DATABASE_URL=postgresql+asyncpg://data_user:data_pass123@data-db:5432/data_db -# NOTIFICATION_DATABASE_URL=postgresql+asyncpg://notification_user:notification_pass123@notification-db:5432/notification_db +# Redis Database Assignments: +# 0 - Auth Service +# 1 - Training Service +# 2 - Forecasting Service +# 3 - Data Service +# 4 - Tenant Service +# 5 - Notification Service +# 6 - Gateway Service # ================================================================ -# REDIS CONFIGURATION (Auto-configured in Docker) +# RABBITMQ CONFIGURATION # ================================================================ -# REDIS_URL=redis://:redis_pass123@redis:6379 +RABBITMQ_URL=amqp://bakery:forecast123@rabbitmq:5672/ +RABBITMQ_EXCHANGE=bakery_events +RABBITMQ_QUEUE_PREFIX=bakery +RABBITMQ_RETRY_ATTEMPTS=3 +RABBITMQ_RETRY_DELAY=5 # ================================================================ -# RABBITMQ CONFIGURATION (Auto-configured in Docker) +# AUTHENTICATION & SECURITY # ================================================================ -# RABBITMQ_URL=amqp://bakery:forecast123@rabbitmq:5672/ - -# ================================================================ -# CORS CONFIGURATION -# ================================================================ - -# Allowed origins for CORS (comma-separated) -CORS_ORIGINS=http://localhost:3000,http://localhost:3001,https://yourdomain.com - -# ================================================================ -# ML/AI CONFIGURATION -# ================================================================ - -# Model storage configuration -MODEL_STORAGE_PATH=/app/models -MAX_TRAINING_TIME_MINUTES=30 -MIN_TRAINING_DATA_DAYS=30 -PROPHET_SEASONALITY_MODE=additive - -# Prediction caching -PREDICTION_CACHE_TTL_HOURS=6 - -# ================================================================ -# SECURITY CONFIGURATION -# ================================================================ - -# Password requirements -PASSWORD_MIN_LENGTH=8 -MAX_LOGIN_ATTEMPTS=5 -LOCKOUT_DURATION_MINUTES=30 - -# Rate limiting -RATE_LIMIT_CALLS_PER_MINUTE=60 -RATE_LIMIT_BURST=10 - -# Session configuration +# JWT Configuration (CHANGE IN PRODUCTION!) +JWT_SECRET_KEY=your-super-secret-jwt-key-change-in-production-very-long-and-secure +JWT_ALGORITHM=HS256 JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30 JWT_REFRESH_TOKEN_EXPIRE_DAYS=7 +# Service-to-Service Authentication +SERVICE_API_KEY=service-api-key-change-in-production +ENABLE_SERVICE_AUTH=false + +# Password Requirements +PASSWORD_MIN_LENGTH=8 +PASSWORD_REQUIRE_UPPERCASE=true +PASSWORD_REQUIRE_LOWERCASE=true +PASSWORD_REQUIRE_NUMBERS=true +PASSWORD_REQUIRE_SYMBOLS=false + +# Security Settings +BCRYPT_ROUNDS=12 +MAX_LOGIN_ATTEMPTS=5 +LOCKOUT_DURATION_MINUTES=30 + # ================================================================ -# MONITORING CONFIGURATION +# CORS & API CONFIGURATION # ================================================================ -# Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL -LOG_LEVEL=INFO +CORS_ORIGINS=http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000 +CORS_ALLOW_CREDENTIALS=true -# Service versions -SERVICE_VERSION=1.0.0 +# Rate Limiting +RATE_LIMIT_ENABLED=true +RATE_LIMIT_REQUESTS=100 +RATE_LIMIT_WINDOW=60 +RATE_LIMIT_BURST=10 -# Data retention -DATA_RETENTION_DAYS=365 +# API Documentation +API_DOCS_ENABLED=true + +# ================================================================ +# SERVICE URLS +# ================================================================ + +GATEWAY_URL=http://gateway:8000 +AUTH_SERVICE_URL=http://auth-service:8000 +TRAINING_SERVICE_URL=http://training-service:8000 +FORECASTING_SERVICE_URL=http://forecasting-service:8000 +DATA_SERVICE_URL=http://data-service:8000 +TENANT_SERVICE_URL=http://tenant-service:8000 +NOTIFICATION_SERVICE_URL=http://notification-service:8000 + +# HTTP Client Settings +HTTP_TIMEOUT=30 +HTTP_RETRIES=3 +HTTP_RETRY_DELAY=1.0 + +# ================================================================ +# EXTERNAL APIS & INTEGRATIONS +# ================================================================ + +# Spanish Weather Service (AEMET) +AEMET_API_KEY=your-aemet-api-key-here +AEMET_TIMEOUT=30 +AEMET_RETRY_ATTEMPTS=3 + +# Madrid Open Data Platform +MADRID_OPENDATA_API_KEY=your-madrid-opendata-key-here +MADRID_OPENDATA_TIMEOUT=30 + +# Email Configuration (Gmail example) +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=your-email@gmail.com +SMTP_PASSWORD=your-email-app-password +SMTP_TLS=true +SMTP_SSL=false +DEFAULT_FROM_EMAIL=noreply@bakeryforecast.es +DEFAULT_FROM_NAME=Bakery Forecast + +# WhatsApp API (Twilio example) +WHATSAPP_API_KEY=your-whatsapp-api-key-here +WHATSAPP_BASE_URL=https://api.twilio.com +WHATSAPP_FROM_NUMBER=whatsapp:+14155238886 + +# ================================================================ +# ML & AI CONFIGURATION +# ================================================================ + +# Model Storage +MODEL_STORAGE_PATH=/app/models +MODEL_STORAGE_BACKEND=local +MODEL_BACKUP_ENABLED=true +MODEL_VERSIONING_ENABLED=true + +# Training Configuration +MAX_TRAINING_TIME_MINUTES=30 +MAX_CONCURRENT_TRAINING_JOBS=3 +MIN_TRAINING_DATA_DAYS=30 +TRAINING_BATCH_SIZE=1000 + +# Prophet Configuration +PROPHET_SEASONALITY_MODE=additive +PROPHET_CHANGEPOINT_PRIOR_SCALE=0.05 +PROPHET_SEASONALITY_PRIOR_SCALE=10.0 +PROPHET_HOLIDAYS_PRIOR_SCALE=10.0 + +# Prediction Caching +PREDICTION_CACHE_TTL_HOURS=6 WEATHER_CACHE_TTL_HOURS=1 -TRAFFIC_CACHE_TTL_HOURS=1 \ No newline at end of file +TRAFFIC_CACHE_TTL_HOURS=1 + +# ================================================================ +# BUSINESS CONFIGURATION +# ================================================================ + +# Forecasting Limits +MAX_FORECAST_DAYS=30 +MIN_HISTORICAL_DAYS=60 +PREDICTION_CONFIDENCE_THRESHOLD=0.8 + +# Spanish Business Context +TIMEZONE=Europe/Madrid +LOCALE=es_ES.UTF-8 +CURRENCY=EUR +BUSINESS_HOUR_START=7 +BUSINESS_HOUR_END=20 + +# Spanish Holidays & Seasonal Adjustments +ENABLE_SPANISH_HOLIDAYS=true +ENABLE_MADRID_HOLIDAYS=true +SCHOOL_CALENDAR_ENABLED=true + +# Weather Impact Modeling +WEATHER_IMPACT_ENABLED=true +TEMPERATURE_THRESHOLD_COLD=10.0 +TEMPERATURE_THRESHOLD_HOT=30.0 +RAIN_IMPACT_FACTOR=0.7 + +# Business Adjustments +WEEKEND_ADJUSTMENT_FACTOR=0.8 +HOLIDAY_ADJUSTMENT_FACTOR=0.5 + +# ================================================================ +# TENANT & SUBSCRIPTION CONFIGURATION +# ================================================================ + +# Default Settings +DEFAULT_PLAN=basic +TRIAL_PERIOD_DAYS=14 + +# Plan Limits +BASIC_PLAN_LOCATIONS=1 +BASIC_PLAN_PREDICTIONS_PER_DAY=100 +BASIC_PLAN_DATA_RETENTION_DAYS=90 + +PREMIUM_PLAN_LOCATIONS=5 +PREMIUM_PLAN_PREDICTIONS_PER_DAY=1000 +PREMIUM_PLAN_DATA_RETENTION_DAYS=365 + +ENTERPRISE_PLAN_LOCATIONS=50 +ENTERPRISE_PLAN_PREDICTIONS_PER_DAY=10000 +ENTERPRISE_PLAN_DATA_RETENTION_DAYS=1095 + +# Billing (disabled by default) +BILLING_ENABLED=false +BILLING_CURRENCY=EUR +BILLING_CYCLE_DAYS=30 +SPANISH_TAX_RATE=0.21 + +# Resource Limits +MAX_API_CALLS_PER_MINUTE=100 +MAX_STORAGE_MB=1024 +MAX_CONCURRENT_REQUESTS=10 + +# ================================================================ +# MONITORING & OBSERVABILITY +# ================================================================ + +# Logging Configuration +LOG_FORMAT=json +LOG_FILE_ENABLED=false +LOG_FILE_PATH=/app/logs +LOG_ROTATION_SIZE=100MB +LOG_RETENTION_DAYS=30 + +# Metrics & Monitoring +PROMETHEUS_ENABLED=true +PROMETHEUS_PORT=9090 + +# Tracing (disabled by default) +JAEGER_ENABLED=false +JAEGER_AGENT_HOST=localhost +JAEGER_AGENT_PORT=6831 + +# Health Checks +HEALTH_CHECK_TIMEOUT=30 +HEALTH_CHECK_INTERVAL=30 + +# ================================================================ +# DATA RETENTION & CLEANUP +# ================================================================ + +DATA_RETENTION_DAYS=365 +LOG_RETENTION_DAYS=90 +METRIC_RETENTION_DAYS=90 +TEMP_FILE_CLEANUP_HOURS=24 + +# Service-specific Data Retention +AUTH_DATA_RETENTION_DAYS=365 +RAW_DATA_RETENTION_DAYS=90 +PROCESSED_DATA_RETENTION_DAYS=365 + +# ================================================================ +# DEVELOPMENT & TESTING +# ================================================================ + +# Development Features +AUTO_RELOAD=false +PROFILING_ENABLED=false +MOCK_EXTERNAL_APIS=false + +# Testing Configuration +TESTING=false +TEST_DATABASE_URL=postgresql+asyncpg://test_user:test_pass@test-db:5432/test_db + +# Data Collection Intervals +WEATHER_COLLECTION_INTERVAL_HOURS=1 +TRAFFIC_COLLECTION_INTERVAL_HOURS=1 +EVENTS_COLLECTION_INTERVAL_HOURS=6 + +# Data Quality +DATA_VALIDATION_ENABLED=true +OUTLIER_DETECTION_ENABLED=true +DATA_COMPLETENESS_THRESHOLD=0.8 + +# Geolocation (Madrid, Spain) +DEFAULT_LATITUDE=40.4168 +DEFAULT_LONGITUDE=-3.7038 +LOCATION_RADIUS_KM=50.0 + +# ================================================================ +# NOTIFICATION CONFIGURATION +# ================================================================ + +# Notification Types +ENABLE_EMAIL_NOTIFICATIONS=true +ENABLE_WHATSAPP_NOTIFICATIONS=true +ENABLE_PUSH_NOTIFICATIONS=false + +# Notification Queuing +MAX_RETRY_ATTEMPTS=3 +RETRY_DELAY_SECONDS=60 +NOTIFICATION_BATCH_SIZE=100 + +# Rate Limiting +EMAIL_RATE_LIMIT_PER_HOUR=1000 +WHATSAPP_RATE_LIMIT_PER_HOUR=100 + +# Localization +DEFAULT_LANGUAGE=es +DATE_FORMAT=%d/%m/%Y +TIME_FORMAT=%H:%M + +# Templates +EMAIL_TEMPLATES_PATH=/app/templates/email +WHATSAPP_TEMPLATES_PATH=/app/templates/whatsapp + +# Delivery & Analytics +IMMEDIATE_DELIVERY=true +SCHEDULED_DELIVERY_ENABLED=true +DELIVERY_TRACKING_ENABLED=true +OPEN_TRACKING_ENABLED=true +CLICK_TRACKING_ENABLED=true + +# ================================================================ +# COMPLIANCE & GDPR +# ================================================================ + +# GDPR Compliance +GDPR_COMPLIANCE_ENABLED=true +CONSENT_REQUIRED=true +DATA_EXPORT_ENABLED=true +DATA_DELETION_ENABLED=true +PRIVACY_POLICY_URL=/privacy + +# Email Verification +EMAIL_VERIFICATION_REQUIRED=true +EMAIL_VERIFICATION_EXPIRE_HOURS=24 + +# Account Security +ACCOUNT_LOCKOUT_ENABLED=true +PASSWORD_HISTORY_COUNT=5 +SESSION_TIMEOUT_MINUTES=60 +CONCURRENT_SESSIONS_LIMIT=3 + +# ================================================================ +# PERFORMANCE & OPTIMIZATION +# ================================================================ + +# Caching +REALTIME_FORECASTING_ENABLED=true +FORECAST_UPDATE_INTERVAL_HOURS=6 + +# Batch Processing +BATCH_PROCESSING_ENABLED=true +BATCH_SIZE=1000 +PARALLEL_PROCESSING_WORKERS=4 +FORECAST_BATCH_SIZE=100 + +# Circuit Breaker (Gateway) +CIRCUIT_BREAKER_ENABLED=true +CIRCUIT_BREAKER_FAILURE_THRESHOLD=5 +CIRCUIT_BREAKER_RECOVERY_TIMEOUT=60 + +# Load Balancing (Gateway) +ENABLE_LOAD_BALANCING=true +LOAD_BALANCER_ALGORITHM=round_robin + +# Request Limits +MAX_REQUEST_SIZE=10485760 +REQUEST_TIMEOUT=30 + +# ================================================================ +# BUSINESS INTELLIGENCE & ALERTS +# ================================================================ + +# Alert Thresholds +HIGH_DEMAND_THRESHOLD=1.5 +LOW_DEMAND_THRESHOLD=0.5 +STOCKOUT_RISK_THRESHOLD=0.9 + +# Model Validation +CROSS_VALIDATION_ENABLED=true +VALIDATION_SPLIT_RATIO=0.2 +MIN_MODEL_ACCURACY=0.7 + +# Data Processing +DATA_PREPROCESSING_ENABLED=true +SEASONAL_DECOMPOSITION_ENABLED=true + +# Distributed Training (Future scaling) +DISTRIBUTED_TRAINING_ENABLED=false +TRAINING_WORKER_COUNT=1 + +# Support & Contact +SUPPORT_EMAIL=soporte@bakeryforecast.es +INVOICE_LANGUAGE=es \ No newline at end of file diff --git a/gateway/app/core/config.py b/gateway/app/core/config.py index eef93ca1..b33abe09 100644 --- a/gateway/app/core/config.py +++ b/gateway/app/core/config.py @@ -1,65 +1,46 @@ +# ================================================================ +# GATEWAY SERVICE CONFIGURATION +# gateway/app/core/config.py +# ================================================================ + """ -Gateway configuration +Gateway service configuration +Central API Gateway for all microservices """ +from shared.config.base import BaseServiceSettings import os -from typing import List, Dict -from pydantic_settings import BaseSettings +from typing import Dict, List -class Settings(BaseSettings): - """Application settings""" +class GatewaySettings(BaseServiceSettings): + """Gateway-specific settings""" - # Basic settings + # Service Identity APP_NAME: str = "Bakery Forecasting Gateway" - VERSION: str = "1.0.0" - DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" - LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") + SERVICE_NAME: str = "gateway" + DESCRIPTION: str = "API Gateway for Bakery Forecasting Platform" - # Service URLs - AUTH_SERVICE_URL: str = "http://auth-service:8000" - TRAINING_SERVICE_URL: str = "http://training-service:8000" - FORECASTING_SERVICE_URL: str = "http://forecasting-service:8000" - DATA_SERVICE_URL: str = "http://data-service:8000" - TENANT_SERVICE_URL: str = "http://tenant-service:8000" - NOTIFICATION_SERVICE_URL: str = "http://notification-service:8000" + # Gateway-specific Redis database + REDIS_DB: int = 6 # Service Discovery CONSUL_URL: str = os.getenv("CONSUL_URL", "http://consul:8500") ENABLE_SERVICE_DISCOVERY: bool = os.getenv("ENABLE_SERVICE_DISCOVERY", "false").lower() == "true" - - # CORS - CORS_ORIGINS: str = os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:3001") - - @property - def CORS_ORIGINS_LIST(self) -> List[str]: - """Get CORS origins as list""" - return [origin.strip() for origin in self.CORS_ORIGINS.split(",")] - - # Redis settings - REDIS_URL: str = "redis://redis:6379/6" + # Load Balancing + ENABLE_LOAD_BALANCING: bool = os.getenv("ENABLE_LOAD_BALANCING", "true").lower() == "true" + LOAD_BALANCER_ALGORITHM: str = os.getenv("LOAD_BALANCER_ALGORITHM", "round_robin") - # Rate limiting - RATE_LIMIT_REQUESTS: int = 100 - RATE_LIMIT_WINDOW: int = 60 + # Circuit Breaker + CIRCUIT_BREAKER_ENABLED: bool = os.getenv("CIRCUIT_BREAKER_ENABLED", "true").lower() == "true" + CIRCUIT_BREAKER_FAILURE_THRESHOLD: int = int(os.getenv("CIRCUIT_BREAKER_FAILURE_THRESHOLD", "5")) + CIRCUIT_BREAKER_RECOVERY_TIMEOUT: int = int(os.getenv("CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "60")) - # JWT settings - JWT_SECRET_KEY: str = "your-super-secret-jwt-key-change-in-production" - JWT_ALGORITHM: str = "HS256" + # Request/Response Settings + MAX_REQUEST_SIZE: int = int(os.getenv("MAX_REQUEST_SIZE", "10485760")) # 10MB + REQUEST_TIMEOUT: int = int(os.getenv("REQUEST_TIMEOUT", "30")) - @property - def SERVICES(self) -> Dict[str, str]: - """Service registry""" - return { - "auth": self.AUTH_SERVICE_URL, - "training": self.TRAINING_SERVICE_URL, - "forecasting": self.FORECASTING_SERVICE_URL, - "data": self.DATA_SERVICE_URL, - "tenant": self.TENANT_SERVICE_URL, - "notification": self.NOTIFICATION_SERVICE_URL - } + # Gateway doesn't need a database + DATABASE_URL: str = "" - class Config: - env_file = ".env" - -settings = Settings() \ No newline at end of file +settings = GatewaySettings() diff --git a/services/auth/app/core/config.py b/services/auth/app/core/config.py index c6b7da5a..1abc862b 100644 --- a/services/auth/app/core/config.py +++ b/services/auth/app/core/config.py @@ -1,47 +1,56 @@ +# ================================================================ +# AUTH SERVICE CONFIGURATION +# services/auth/app/core/config.py +# ================================================================ + """ Authentication service configuration +User management and JWT token handling """ +from shared.config.base import BaseServiceSettings import os -from pydantic_settings import BaseSettings -class Settings(BaseSettings): - """Application settings""" +class AuthSettings(BaseServiceSettings): + """Auth service specific settings""" - # Basic settings + # Service Identity APP_NAME: str = "Authentication Service" - VERSION: str = "1.0.0" - DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" - LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") + SERVICE_NAME: str = "auth-service" + DESCRIPTION: str = "User authentication and authorization service" - # Database settings - DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql+asyncpg://auth_user:auth_pass123@auth-db:5432/auth_db") + # Database Configuration + DATABASE_URL: str = os.getenv("AUTH_DATABASE_URL", + "postgresql+asyncpg://auth_user:auth_pass123@auth-db:5432/auth_db") - # Redis settings - REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/0") + # Redis Database (dedicated for auth) + REDIS_DB: int = 0 - # JWT settings - JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "your-super-secret-jwt-key-change-in-production") - JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256") - JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("JWT_ACCESS_TOKEN_EXPIRE_MINUTES", "30")) - JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = int(os.getenv("JWT_REFRESH_TOKEN_EXPIRE_DAYS", "7")) - - # Password settings + # Enhanced Password Requirements for Spain PASSWORD_MIN_LENGTH: int = 8 PASSWORD_REQUIRE_UPPERCASE: bool = True PASSWORD_REQUIRE_LOWERCASE: bool = True PASSWORD_REQUIRE_NUMBERS: bool = True PASSWORD_REQUIRE_SYMBOLS: bool = False - # Security settings - BCRYPT_ROUNDS: int = 12 + # Spanish GDPR Compliance + GDPR_COMPLIANCE_ENABLED: bool = True + DATA_RETENTION_DAYS: int = int(os.getenv("AUTH_DATA_RETENTION_DAYS", "365")) + CONSENT_REQUIRED: bool = True + PRIVACY_POLICY_URL: str = os.getenv("PRIVACY_POLICY_URL", "/privacy") + + # Account Security + ACCOUNT_LOCKOUT_ENABLED: bool = True MAX_LOGIN_ATTEMPTS: int = 5 LOCKOUT_DURATION_MINUTES: int = 30 + PASSWORD_HISTORY_COUNT: int = 5 - # RabbitMQ settings - RABBITMQ_URL: str = os.getenv("RABBITMQ_URL", "amqp://bakery:forecast123@rabbitmq:5672/") + # Session Management + SESSION_TIMEOUT_MINUTES: int = int(os.getenv("SESSION_TIMEOUT_MINUTES", "60")) + CONCURRENT_SESSIONS_LIMIT: int = int(os.getenv("CONCURRENT_SESSIONS_LIMIT", "3")) - class Config: - env_file = ".env" + # Email Verification + EMAIL_VERIFICATION_REQUIRED: bool = os.getenv("EMAIL_VERIFICATION_REQUIRED", "true").lower() == "true" + EMAIL_VERIFICATION_EXPIRE_HOURS: int = int(os.getenv("EMAIL_VERIFICATION_EXPIRE_HOURS", "24")) -settings = Settings() \ No newline at end of file +settings = AuthSettings() diff --git a/services/data/app/core/config.py b/services/data/app/core/config.py index b36ce3df..a6a04404 100644 --- a/services/data/app/core/config.py +++ b/services/data/app/core/config.py @@ -1,37 +1,68 @@ -"""Data service configuration""" +# ================================================================ +# DATA SERVICE CONFIGURATION +# services/data/app/core/config.py +# ================================================================ -from pydantic_settings import BaseSettings -from typing import List +""" +Data service configuration +External data integration and management +""" -class Settings(BaseSettings): - # Database - DATABASE_URL: str = "postgresql+asyncpg://data_user:data_pass123@data-db:5432/data_db" +from shared.config.base import BaseServiceSettings +import os + +class DataSettings(BaseServiceSettings): + """Data service specific settings""" - # Redis - REDIS_URL: str = "redis://redis:6379/3" - - # RabbitMQ - RABBITMQ_URL: str = "amqp://bakery:forecast123@rabbitmq:5672/" - - # External APIs - AEMET_API_KEY: str = "your-aemet-api-key-here" - MADRID_OPENDATA_API_KEY: str = "your-madrid-opendata-key-here" - - # Service settings + # Service Identity + APP_NAME: str = "Data Service" SERVICE_NAME: str = "data-service" - SERVICE_VERSION: str = "1.0.0" + DESCRIPTION: str = "External data integration and management service" - # Auth - AUTH_SERVICE_URL: str = "http://auth-service:8000" + # Database Configuration + DATABASE_URL: str = os.getenv("DATA_DATABASE_URL", + "postgresql+asyncpg://data_user:data_pass123@data-db:5432/data_db") - # CORS - CORS_ORIGINS: List[str] = ["http://localhost:3000", "http://localhost:3001"] + # Redis Database (dedicated for external data cache) + REDIS_DB: int = 3 - # Monitoring - LOG_LEVEL: str = "INFO" - ENABLE_METRICS: bool = True + # External API Configuration + AEMET_API_KEY: str = os.getenv("AEMET_API_KEY", "") + AEMET_BASE_URL: str = "https://opendata.aemet.es/opendata" + AEMET_TIMEOUT: int = int(os.getenv("AEMET_TIMEOUT", "30")) + AEMET_RETRY_ATTEMPTS: int = int(os.getenv("AEMET_RETRY_ATTEMPTS", "3")) - class Config: - env_file = ".env" + MADRID_OPENDATA_API_KEY: str = os.getenv("MADRID_OPENDATA_API_KEY", "") + MADRID_OPENDATA_BASE_URL: str = "https://datos.madrid.es" + MADRID_OPENDATA_TIMEOUT: int = int(os.getenv("MADRID_OPENDATA_TIMEOUT", "30")) + + # Data Collection Configuration + WEATHER_COLLECTION_INTERVAL_HOURS: int = int(os.getenv("WEATHER_COLLECTION_INTERVAL_HOURS", "1")) + TRAFFIC_COLLECTION_INTERVAL_HOURS: int = int(os.getenv("TRAFFIC_COLLECTION_INTERVAL_HOURS", "1")) + EVENTS_COLLECTION_INTERVAL_HOURS: int = int(os.getenv("EVENTS_COLLECTION_INTERVAL_HOURS", "6")) + + # Cache TTL Configuration + WEATHER_CACHE_TTL_HOURS: int = int(os.getenv("WEATHER_CACHE_TTL_HOURS", "1")) + TRAFFIC_CACHE_TTL_HOURS: int = int(os.getenv("TRAFFIC_CACHE_TTL_HOURS", "1")) + EVENTS_CACHE_TTL_HOURS: int = int(os.getenv("EVENTS_CACHE_TTL_HOURS", "6")) + + # Data Quality Configuration + DATA_VALIDATION_ENABLED: bool = os.getenv("DATA_VALIDATION_ENABLED", "true").lower() == "true" + OUTLIER_DETECTION_ENABLED: bool = os.getenv("OUTLIER_DETECTION_ENABLED", "true").lower() == "true" + DATA_COMPLETENESS_THRESHOLD: float = float(os.getenv("DATA_COMPLETENESS_THRESHOLD", "0.8")) + + # Geolocation Settings (Madrid focus) + DEFAULT_LATITUDE: float = float(os.getenv("DEFAULT_LATITUDE", "40.4168")) # Madrid + DEFAULT_LONGITUDE: float = float(os.getenv("DEFAULT_LONGITUDE", "-3.7038")) # Madrid + LOCATION_RADIUS_KM: float = float(os.getenv("LOCATION_RADIUS_KM", "50.0")) + + # Data Retention + RAW_DATA_RETENTION_DAYS: int = int(os.getenv("RAW_DATA_RETENTION_DAYS", "90")) + PROCESSED_DATA_RETENTION_DAYS: int = int(os.getenv("PROCESSED_DATA_RETENTION_DAYS", "365")) + + # Batch Processing + BATCH_PROCESSING_ENABLED: bool = os.getenv("BATCH_PROCESSING_ENABLED", "true").lower() == "true" + BATCH_SIZE: int = int(os.getenv("BATCH_SIZE", "1000")) + PARALLEL_PROCESSING_WORKERS: int = int(os.getenv("PARALLEL_PROCESSING_WORKERS", "4")) -settings = Settings() +settings = DataSettings() \ No newline at end of file diff --git a/services/forecasting/app/core/config.py b/services/forecasting/app/core/config.py index 23802037..d0ce523c 100644 --- a/services/forecasting/app/core/config.py +++ b/services/forecasting/app/core/config.py @@ -1,32 +1,59 @@ +# ================================================================ +# FORECASTING SERVICE CONFIGURATION +# services/forecasting/app/core/config.py +# ================================================================ + """ -uLuforecasting service configuration +Forecasting service configuration +Demand prediction and forecasting """ +from shared.config.base import BaseServiceSettings import os -from pydantic_settings import BaseSettings -class Settings(BaseSettings): - """Application settings""" +class ForecastingSettings(BaseServiceSettings): + """Forecasting service specific settings""" - # Basic settings - APP_NAME: str = "uLuforecasting Service" - VERSION: str = "1.0.0" - DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" - LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") + # Service Identity + APP_NAME: str = "Forecasting Service" + SERVICE_NAME: str = "forecasting-service" + DESCRIPTION: str = "Demand prediction and forecasting service" - # Database settings - DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql+asyncpg://forecasting_user:forecasting_pass123@forecasting-db:5432/forecasting_db") + # Database Configuration + DATABASE_URL: str = os.getenv("FORECASTING_DATABASE_URL", + "postgresql+asyncpg://forecasting_user:forecasting_pass123@forecasting-db:5432/forecasting_db") - # Redis settings - REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/0") + # Redis Database (dedicated for prediction cache) + REDIS_DB: int = 2 - # RabbitMQ settings - RABBITMQ_URL: str = os.getenv("RABBITMQ_URL", "amqp://bakery:forecast123@rabbitmq:5672/") + # Prediction Configuration + MAX_FORECAST_DAYS: int = int(os.getenv("MAX_FORECAST_DAYS", "30")) + MIN_HISTORICAL_DAYS: int = int(os.getenv("MIN_HISTORICAL_DAYS", "60")) + PREDICTION_CONFIDENCE_THRESHOLD: float = float(os.getenv("PREDICTION_CONFIDENCE_THRESHOLD", "0.8")) - # Service URLs - AUTH_SERVICE_URL: str = os.getenv("AUTH_SERVICE_URL", "http://auth-service:8000") + # Caching Configuration + PREDICTION_CACHE_TTL_HOURS: int = int(os.getenv("PREDICTION_CACHE_TTL_HOURS", "6")) + FORECAST_BATCH_SIZE: int = int(os.getenv("FORECAST_BATCH_SIZE", "100")) - class Config: - env_file = ".env" + # Real-time Forecasting + REALTIME_FORECASTING_ENABLED: bool = os.getenv("REALTIME_FORECASTING_ENABLED", "true").lower() == "true" + FORECAST_UPDATE_INTERVAL_HOURS: int = int(os.getenv("FORECAST_UPDATE_INTERVAL_HOURS", "6")) + + # Business Rules for Spanish Bakeries + BUSINESS_HOUR_START: int = 7 # 7 AM + BUSINESS_HOUR_END: int = 20 # 8 PM + WEEKEND_ADJUSTMENT_FACTOR: float = float(os.getenv("WEEKEND_ADJUSTMENT_FACTOR", "0.8")) + HOLIDAY_ADJUSTMENT_FACTOR: float = float(os.getenv("HOLIDAY_ADJUSTMENT_FACTOR", "0.5")) + + # Weather Impact Modeling + WEATHER_IMPACT_ENABLED: bool = os.getenv("WEATHER_IMPACT_ENABLED", "true").lower() == "true" + TEMPERATURE_THRESHOLD_COLD: float = float(os.getenv("TEMPERATURE_THRESHOLD_COLD", "10.0")) + TEMPERATURE_THRESHOLD_HOT: float = float(os.getenv("TEMPERATURE_THRESHOLD_HOT", "30.0")) + RAIN_IMPACT_FACTOR: float = float(os.getenv("RAIN_IMPACT_FACTOR", "0.7")) + + # Alert Thresholds + HIGH_DEMAND_THRESHOLD: float = float(os.getenv("HIGH_DEMAND_THRESHOLD", "1.5")) + LOW_DEMAND_THRESHOLD: float = float(os.getenv("LOW_DEMAND_THRESHOLD", "0.5")) + STOCKOUT_RISK_THRESHOLD: float = float(os.getenv("STOCKOUT_RISK_THRESHOLD", "0.9")) -settings = Settings() +settings = ForecastingSettings() diff --git a/services/notification/app/core/config.py b/services/notification/app/core/config.py index b62eb81f..09332be5 100644 --- a/services/notification/app/core/config.py +++ b/services/notification/app/core/config.py @@ -1,32 +1,83 @@ +# ================================================================ +# NOTIFICATION SERVICE CONFIGURATION +# services/notification/app/core/config.py +# ================================================================ + """ -uLunotification service configuration +Notification service configuration +Email and WhatsApp notification handling """ +from shared.config.base import BaseServiceSettings import os -from pydantic_settings import BaseSettings -class Settings(BaseSettings): - """Application settings""" +class NotificationSettings(BaseServiceSettings): + """Notification service specific settings""" - # Basic settings - APP_NAME: str = "uLunotification Service" - VERSION: str = "1.0.0" - DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" - LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") + # Service Identity + APP_NAME: str = "Notification Service" + SERVICE_NAME: str = "notification-service" + DESCRIPTION: str = "Email and WhatsApp notification service" - # Database settings - DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql+asyncpg://notification_user:notification_pass123@notification-db:5432/notification_db") + # Database Configuration + DATABASE_URL: str = os.getenv("NOTIFICATION_DATABASE_URL", + "postgresql+asyncpg://notification_user:notification_pass123@notification-db:5432/notification_db") - # Redis settings - REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/0") + # Redis Database (dedicated for notification queue) + REDIS_DB: int = 5 - # RabbitMQ settings - RABBITMQ_URL: str = os.getenv("RABBITMQ_URL", "amqp://bakery:forecast123@rabbitmq:5672/") + # Email Configuration + SMTP_HOST: str = os.getenv("SMTP_HOST", "smtp.gmail.com") + SMTP_PORT: int = int(os.getenv("SMTP_PORT", "587")) + SMTP_USER: str = os.getenv("SMTP_USER", "") + SMTP_PASSWORD: str = os.getenv("SMTP_PASSWORD", "") + SMTP_TLS: bool = os.getenv("SMTP_TLS", "true").lower() == "true" + SMTP_SSL: bool = os.getenv("SMTP_SSL", "false").lower() == "true" - # Service URLs - AUTH_SERVICE_URL: str = os.getenv("AUTH_SERVICE_URL", "http://auth-service:8000") + # Email Settings + DEFAULT_FROM_EMAIL: str = os.getenv("DEFAULT_FROM_EMAIL", "noreply@bakeryforecast.es") + DEFAULT_FROM_NAME: str = os.getenv("DEFAULT_FROM_NAME", "Bakery Forecast") + EMAIL_TEMPLATES_PATH: str = os.getenv("EMAIL_TEMPLATES_PATH", "/app/templates/email") - class Config: - env_file = ".env" + # WhatsApp Configuration + WHATSAPP_API_KEY: str = os.getenv("WHATSAPP_API_KEY", "") + WHATSAPP_BASE_URL: str = os.getenv("WHATSAPP_BASE_URL", "https://api.twilio.com") + WHATSAPP_FROM_NUMBER: str = os.getenv("WHATSAPP_FROM_NUMBER", "") + WHATSAPP_TEMPLATES_PATH: str = os.getenv("WHATSAPP_TEMPLATES_PATH", "/app/templates/whatsapp") + + # Notification Queuing + MAX_RETRY_ATTEMPTS: int = int(os.getenv("MAX_RETRY_ATTEMPTS", "3")) + RETRY_DELAY_SECONDS: int = int(os.getenv("RETRY_DELAY_SECONDS", "60")) + BATCH_SIZE: int = int(os.getenv("NOTIFICATION_BATCH_SIZE", "100")) + + # Rate Limiting + EMAIL_RATE_LIMIT_PER_HOUR: int = int(os.getenv("EMAIL_RATE_LIMIT_PER_HOUR", "1000")) + WHATSAPP_RATE_LIMIT_PER_HOUR: int = int(os.getenv("WHATSAPP_RATE_LIMIT_PER_HOUR", "100")) + + # Spanish Localization + DEFAULT_LANGUAGE: str = os.getenv("DEFAULT_LANGUAGE", "es") + TIMEZONE: str = "Europe/Madrid" + DATE_FORMAT: str = "%d/%m/%Y" + TIME_FORMAT: str = "%H:%M" + + # Notification Types + ENABLE_EMAIL_NOTIFICATIONS: bool = os.getenv("ENABLE_EMAIL_NOTIFICATIONS", "true").lower() == "true" + ENABLE_WHATSAPP_NOTIFICATIONS: bool = os.getenv("ENABLE_WHATSAPP_NOTIFICATIONS", "true").lower() == "true" + ENABLE_PUSH_NOTIFICATIONS: bool = os.getenv("ENABLE_PUSH_NOTIFICATIONS", "false").lower() == "true" + + # Template Categories + ALERT_TEMPLATES_ENABLED: bool = True + MARKETING_TEMPLATES_ENABLED: bool = os.getenv("MARKETING_TEMPLATES_ENABLED", "false").lower() == "true" + TRANSACTIONAL_TEMPLATES_ENABLED: bool = True + + # Delivery Configuration + IMMEDIATE_DELIVERY: bool = os.getenv("IMMEDIATE_DELIVERY", "true").lower() == "true" + SCHEDULED_DELIVERY_ENABLED: bool = os.getenv("SCHEDULED_DELIVERY_ENABLED", "true").lower() == "true" + BULK_DELIVERY_ENABLED: bool = os.getenv("BULK_DELIVERY_ENABLED", "true").lower() == "true" + + # Analytics + DELIVERY_TRACKING_ENABLED: bool = os.getenv("DELIVERY_TRACKING_ENABLED", "true").lower() == "true" + OPEN_TRACKING_ENABLED: bool = os.getenv("OPEN_TRACKING_ENABLED", "true").lower() == "true" + CLICK_TRACKING_ENABLED: bool = os.getenv("CLICK_TRACKING_ENABLED", "true").lower() == "true" -settings = Settings() +settings = NotificationSettings() \ No newline at end of file diff --git a/services/tenant/app/core/config.py b/services/tenant/app/core/config.py index 8b713829..730d0da8 100644 --- a/services/tenant/app/core/config.py +++ b/services/tenant/app/core/config.py @@ -1,32 +1,70 @@ +# ================================================================ +# TENANT SERVICE CONFIGURATION +# services/tenant/app/core/config.py +# ================================================================ + """ -uLutenant service configuration +Tenant service configuration +Multi-tenant management and subscription handling """ +from shared.config.base import BaseServiceSettings import os -from pydantic_settings import BaseSettings -class Settings(BaseSettings): - """Application settings""" +class TenantSettings(BaseServiceSettings): + """Tenant service specific settings""" - # Basic settings - APP_NAME: str = "uLutenant Service" - VERSION: str = "1.0.0" - DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" - LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") + # Service Identity + APP_NAME: str = "Tenant Service" + SERVICE_NAME: str = "tenant-service" + DESCRIPTION: str = "Multi-tenant management and subscription service" - # Database settings - DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql+asyncpg://tenant_user:tenant_pass123@tenant-db:5432/tenant_db") + # Database Configuration + DATABASE_URL: str = os.getenv("TENANT_DATABASE_URL", + "postgresql+asyncpg://tenant_user:tenant_pass123@tenant-db:5432/tenant_db") - # Redis settings - REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/0") + # Redis Database (dedicated for tenant data) + REDIS_DB: int = 4 - # RabbitMQ settings - RABBITMQ_URL: str = os.getenv("RABBITMQ_URL", "amqp://bakery:forecast123@rabbitmq:5672/") + # Subscription Plans + DEFAULT_PLAN: str = os.getenv("DEFAULT_PLAN", "basic") + TRIAL_PERIOD_DAYS: int = int(os.getenv("TRIAL_PERIOD_DAYS", "14")) - # Service URLs - AUTH_SERVICE_URL: str = os.getenv("AUTH_SERVICE_URL", "http://auth-service:8000") + # Plan Limits + BASIC_PLAN_LOCATIONS: int = int(os.getenv("BASIC_PLAN_LOCATIONS", "1")) + BASIC_PLAN_PREDICTIONS_PER_DAY: int = int(os.getenv("BASIC_PLAN_PREDICTIONS_PER_DAY", "100")) + BASIC_PLAN_DATA_RETENTION_DAYS: int = int(os.getenv("BASIC_PLAN_DATA_RETENTION_DAYS", "90")) - class Config: - env_file = ".env" + PREMIUM_PLAN_LOCATIONS: int = int(os.getenv("PREMIUM_PLAN_LOCATIONS", "5")) + PREMIUM_PLAN_PREDICTIONS_PER_DAY: int = int(os.getenv("PREMIUM_PLAN_PREDICTIONS_PER_DAY", "1000")) + PREMIUM_PLAN_DATA_RETENTION_DAYS: int = int(os.getenv("PREMIUM_PLAN_DATA_RETENTION_DAYS", "365")) + + ENTERPRISE_PLAN_LOCATIONS: int = int(os.getenv("ENTERPRISE_PLAN_LOCATIONS", "50")) + ENTERPRISE_PLAN_PREDICTIONS_PER_DAY: int = int(os.getenv("ENTERPRISE_PLAN_PREDICTIONS_PER_DAY", "10000")) + ENTERPRISE_PLAN_DATA_RETENTION_DAYS: int = int(os.getenv("ENTERPRISE_PLAN_DATA_RETENTION_DAYS", "1095")) + + # Billing Configuration + BILLING_ENABLED: bool = os.getenv("BILLING_ENABLED", "false").lower() == "true" + BILLING_CURRENCY: str = os.getenv("BILLING_CURRENCY", "EUR") + BILLING_CYCLE_DAYS: int = int(os.getenv("BILLING_CYCLE_DAYS", "30")) + + # Resource Limits + MAX_API_CALLS_PER_MINUTE: int = int(os.getenv("MAX_API_CALLS_PER_MINUTE", "100")) + MAX_STORAGE_MB: int = int(os.getenv("MAX_STORAGE_MB", "1024")) + MAX_CONCURRENT_REQUESTS: int = int(os.getenv("MAX_CONCURRENT_REQUESTS", "10")) + + # Spanish Business Configuration + SPANISH_TAX_RATE: float = float(os.getenv("SPANISH_TAX_RATE", "0.21")) # IVA 21% + INVOICE_LANGUAGE: str = os.getenv("INVOICE_LANGUAGE", "es") + SUPPORT_EMAIL: str = os.getenv("SUPPORT_EMAIL", "soporte@bakeryforecast.es") + + # Onboarding + ONBOARDING_ENABLED: bool = os.getenv("ONBOARDING_ENABLED", "true").lower() == "true" + DEMO_DATA_ENABLED: bool = os.getenv("DEMO_DATA_ENABLED", "true").lower() == "true" + + # Compliance + GDPR_COMPLIANCE_ENABLED: bool = True + DATA_EXPORT_ENABLED: bool = True + DATA_DELETION_ENABLED: bool = True -settings = Settings() +settings = TenantSettings() diff --git a/services/training/app/core/config.py b/services/training/app/core/config.py index 220cf165..04411e16 100644 --- a/services/training/app/core/config.py +++ b/services/training/app/core/config.py @@ -1,53 +1,65 @@ +# ================================================================ +# TRAINING SERVICE CONFIGURATION +# services/training/app/core/config.py +# ================================================================ + """ Training service configuration +ML model training and management """ +from shared.config.base import BaseServiceSettings import os -from pydantic_settings import BaseSettings -from typing import List -class Settings(BaseSettings): - """Application settings""" +class TrainingSettings(BaseServiceSettings): + """Training service specific settings""" - # Basic settings + # Service Identity APP_NAME: str = "Training Service" - VERSION: str = "1.0.0" - DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" - LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") + SERVICE_NAME: str = "training-service" + DESCRIPTION: str = "Machine learning model training service" - # Database settings - DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql+asyncpg://training_user:training_pass123@training-db:5432/training_db") + # Database Configuration + DATABASE_URL: str = os.getenv("TRAINING_DATABASE_URL", + "postgresql+asyncpg://training_user:training_pass123@training-db:5432/training_db") - # Redis settings - REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/1") + # Redis Database (dedicated for training cache) + REDIS_DB: int = 1 - # RabbitMQ settings - RABBITMQ_URL: str = os.getenv("RABBITMQ_URL", "amqp://bakery:forecast123@rabbitmq:5672/") - - # Service URLs - AUTH_SERVICE_URL: str = os.getenv("AUTH_SERVICE_URL", "http://auth-service:8000") - DATA_SERVICE_URL: str = os.getenv("DATA_SERVICE_URL", "http://data-service:8000") - - # ML Settings + # ML Model Storage MODEL_STORAGE_PATH: str = os.getenv("MODEL_STORAGE_PATH", "/app/models") + MODEL_BACKUP_ENABLED: bool = os.getenv("MODEL_BACKUP_ENABLED", "true").lower() == "true" + MODEL_VERSIONING_ENABLED: bool = os.getenv("MODEL_VERSIONING_ENABLED", "true").lower() == "true" + + # Training Configuration MAX_TRAINING_TIME_MINUTES: int = int(os.getenv("MAX_TRAINING_TIME_MINUTES", "30")) + MAX_CONCURRENT_TRAINING_JOBS: int = int(os.getenv("MAX_CONCURRENT_TRAINING_JOBS", "3")) MIN_TRAINING_DATA_DAYS: int = int(os.getenv("MIN_TRAINING_DATA_DAYS", "30")) + TRAINING_BATCH_SIZE: int = int(os.getenv("TRAINING_BATCH_SIZE", "1000")) - # Prophet Settings + # Prophet Specific Configuration PROPHET_SEASONALITY_MODE: str = os.getenv("PROPHET_SEASONALITY_MODE", "additive") - PROPHET_DAILY_SEASONALITY: bool = os.getenv("PROPHET_DAILY_SEASONALITY", "true").lower() == "true" - PROPHET_WEEKLY_SEASONALITY: bool = os.getenv("PROPHET_WEEKLY_SEASONALITY", "true").lower() == "true" - PROPHET_YEARLY_SEASONALITY: bool = os.getenv("PROPHET_YEARLY_SEASONALITY", "true").lower() == "true" + PROPHET_CHANGEPOINT_PRIOR_SCALE: float = float(os.getenv("PROPHET_CHANGEPOINT_PRIOR_SCALE", "0.05")) + PROPHET_SEASONALITY_PRIOR_SCALE: float = float(os.getenv("PROPHET_SEASONALITY_PRIOR_SCALE", "10.0")) + PROPHET_HOLIDAYS_PRIOR_SCALE: float = float(os.getenv("PROPHET_HOLIDAYS_PRIOR_SCALE", "10.0")) - # CORS - CORS_ORIGINS: str = os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:3001") + # Spanish Holiday Integration + ENABLE_SPANISH_HOLIDAYS: bool = True + ENABLE_MADRID_HOLIDAYS: bool = True + ENABLE_CUSTOM_HOLIDAYS: bool = os.getenv("ENABLE_CUSTOM_HOLIDAYS", "true").lower() == "true" - @property - def CORS_ORIGINS_LIST(self) -> List[str]: - """Get CORS origins as list""" - return [origin.strip() for origin in self.CORS_ORIGINS.split(",")] + # Data Processing + DATA_PREPROCESSING_ENABLED: bool = True + OUTLIER_DETECTION_ENABLED: bool = os.getenv("OUTLIER_DETECTION_ENABLED", "true").lower() == "true" + SEASONAL_DECOMPOSITION_ENABLED: bool = os.getenv("SEASONAL_DECOMPOSITION_ENABLED", "true").lower() == "true" - class Config: - env_file = ".env" + # Model Validation + CROSS_VALIDATION_ENABLED: bool = os.getenv("CROSS_VALIDATION_ENABLED", "true").lower() == "true" + VALIDATION_SPLIT_RATIO: float = float(os.getenv("VALIDATION_SPLIT_RATIO", "0.2")) + MIN_MODEL_ACCURACY: float = float(os.getenv("MIN_MODEL_ACCURACY", "0.7")) + + # Distributed Training (for future scaling) + DISTRIBUTED_TRAINING_ENABLED: bool = os.getenv("DISTRIBUTED_TRAINING_ENABLED", "false").lower() == "true" + TRAINING_WORKER_COUNT: int = int(os.getenv("TRAINING_WORKER_COUNT", "1")) -settings = Settings() \ No newline at end of file +settings = TrainingSettings() \ No newline at end of file diff --git a/shared/config/base.py b/shared/config/base.py new file mode 100644 index 00000000..f773a4e2 --- /dev/null +++ b/shared/config/base.py @@ -0,0 +1,392 @@ +# shared/config/base.py +""" +Base configuration for all microservices +Provides common settings and patterns +""" + +import os +from typing import List, Dict, Optional, Any +from pydantic_settings import BaseSettings +from pydantic import validator + + +class BaseServiceSettings(BaseSettings): + """ + Base configuration class for all microservices + Provides common settings and validation patterns + """ + + # ================================================================ + # CORE SERVICE SETTINGS + # ================================================================ + + # Application Identity + APP_NAME: str = "Bakery Service" + SERVICE_NAME: str = "base-service" + VERSION: str = "1.0.0" + DESCRIPTION: str = "Base microservice for bakery platform" + + # Environment & Debugging + ENVIRONMENT: str = os.getenv("ENVIRONMENT", "development") + DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true" + LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") + + # Service Discovery & Health + SERVICE_HOST: str = os.getenv("SERVICE_HOST", "0.0.0.0") + SERVICE_PORT: int = int(os.getenv("SERVICE_PORT", "8000")) + HEALTH_CHECK_ENABLED: bool = True + METRICS_ENABLED: bool = True + + # ================================================================ + # DATABASE CONFIGURATION + # ================================================================ + + # Primary database URL (should be overridden by each service) + DATABASE_URL: str = os.getenv("DATABASE_URL", "") + + # Database connection settings + DB_POOL_SIZE: int = int(os.getenv("DB_POOL_SIZE", "10")) + DB_MAX_OVERFLOW: int = int(os.getenv("DB_MAX_OVERFLOW", "20")) + DB_POOL_TIMEOUT: int = int(os.getenv("DB_POOL_TIMEOUT", "30")) + DB_POOL_RECYCLE: int = int(os.getenv("DB_POOL_RECYCLE", "3600")) + DB_ECHO: bool = os.getenv("DB_ECHO", "false").lower() == "true" + + # ================================================================ + # REDIS CONFIGURATION + # ================================================================ + + REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379") + REDIS_DB: int = int(os.getenv("REDIS_DB", "0")) + REDIS_MAX_CONNECTIONS: int = int(os.getenv("REDIS_MAX_CONNECTIONS", "50")) + REDIS_RETRY_ON_TIMEOUT: bool = True + REDIS_SOCKET_KEEPALIVE: bool = True + REDIS_SOCKET_KEEPALIVE_OPTIONS: Dict[str, int] = { + "TCP_KEEPIDLE": 1, + "TCP_KEEPINTVL": 3, + "TCP_KEEPCNT": 5, + } + + @property + def REDIS_URL_WITH_DB(self) -> str: + """Get Redis URL with database number""" + base_url = self.REDIS_URL.rstrip('/') + return f"{base_url}/{self.REDIS_DB}" + + # ================================================================ + # RABBITMQ CONFIGURATION + # ================================================================ + + RABBITMQ_URL: str = os.getenv("RABBITMQ_URL", "amqp://bakery:forecast123@rabbitmq:5672/") + RABBITMQ_EXCHANGE: str = os.getenv("RABBITMQ_EXCHANGE", "bakery_events") + RABBITMQ_QUEUE_PREFIX: str = os.getenv("RABBITMQ_QUEUE_PREFIX", "bakery") + RABBITMQ_RETRY_ATTEMPTS: int = int(os.getenv("RABBITMQ_RETRY_ATTEMPTS", "3")) + RABBITMQ_RETRY_DELAY: int = int(os.getenv("RABBITMQ_RETRY_DELAY", "5")) + + # ================================================================ + # AUTHENTICATION & SECURITY + # ================================================================ + + # JWT Configuration + JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "change-this-in-production") + JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256") + JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("JWT_ACCESS_TOKEN_EXPIRE_MINUTES", "30")) + JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = int(os.getenv("JWT_REFRESH_TOKEN_EXPIRE_DAYS", "7")) + + # Service-to-Service Authentication + SERVICE_API_KEY: str = os.getenv("SERVICE_API_KEY", "service-api-key-change-in-production") + ENABLE_SERVICE_AUTH: bool = os.getenv("ENABLE_SERVICE_AUTH", "false").lower() == "true" + + # Password Requirements + PASSWORD_MIN_LENGTH: int = int(os.getenv("PASSWORD_MIN_LENGTH", "8")) + PASSWORD_REQUIRE_UPPERCASE: bool = os.getenv("PASSWORD_REQUIRE_UPPERCASE", "true").lower() == "true" + PASSWORD_REQUIRE_LOWERCASE: bool = os.getenv("PASSWORD_REQUIRE_LOWERCASE", "true").lower() == "true" + PASSWORD_REQUIRE_NUMBERS: bool = os.getenv("PASSWORD_REQUIRE_NUMBERS", "true").lower() == "true" + PASSWORD_REQUIRE_SYMBOLS: bool = os.getenv("PASSWORD_REQUIRE_SYMBOLS", "false").lower() == "true" + + # Security Settings + BCRYPT_ROUNDS: int = int(os.getenv("BCRYPT_ROUNDS", "12")) + MAX_LOGIN_ATTEMPTS: int = int(os.getenv("MAX_LOGIN_ATTEMPTS", "5")) + LOCKOUT_DURATION_MINUTES: int = int(os.getenv("LOCKOUT_DURATION_MINUTES", "30")) + + # ================================================================ + # INTER-SERVICE COMMUNICATION + # ================================================================ + + # Service URLs (can be overridden by environment variables) + GATEWAY_URL: str = os.getenv("GATEWAY_URL", "http://gateway:8000") + AUTH_SERVICE_URL: str = os.getenv("AUTH_SERVICE_URL", "http://auth-service:8000") + TRAINING_SERVICE_URL: str = os.getenv("TRAINING_SERVICE_URL", "http://training-service:8000") + FORECASTING_SERVICE_URL: str = os.getenv("FORECASTING_SERVICE_URL", "http://forecasting-service:8000") + DATA_SERVICE_URL: str = os.getenv("DATA_SERVICE_URL", "http://data-service:8000") + TENANT_SERVICE_URL: str = os.getenv("TENANT_SERVICE_URL", "http://tenant-service:8000") + NOTIFICATION_SERVICE_URL: str = os.getenv("NOTIFICATION_SERVICE_URL", "http://notification-service:8000") + + # HTTP Client Settings + HTTP_TIMEOUT: int = int(os.getenv("HTTP_TIMEOUT", "30")) + HTTP_RETRIES: int = int(os.getenv("HTTP_RETRIES", "3")) + HTTP_RETRY_DELAY: float = float(os.getenv("HTTP_RETRY_DELAY", "1.0")) + + # ================================================================ + # CORS & API CONFIGURATION + # ================================================================ + + CORS_ORIGINS: str = os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:3001") + CORS_ALLOW_CREDENTIALS: bool = os.getenv("CORS_ALLOW_CREDENTIALS", "true").lower() == "true" + CORS_ALLOW_METHODS: List[str] = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] + CORS_ALLOW_HEADERS: List[str] = ["*"] + + @property + def CORS_ORIGINS_LIST(self) -> List[str]: + """Get CORS origins as list""" + return [origin.strip() for origin in self.CORS_ORIGINS.split(",") if origin.strip()] + + # Rate Limiting + RATE_LIMIT_ENABLED: bool = os.getenv("RATE_LIMIT_ENABLED", "true").lower() == "true" + RATE_LIMIT_REQUESTS: int = int(os.getenv("RATE_LIMIT_REQUESTS", "100")) + RATE_LIMIT_WINDOW: int = int(os.getenv("RATE_LIMIT_WINDOW", "60")) + RATE_LIMIT_BURST: int = int(os.getenv("RATE_LIMIT_BURST", "10")) + + # API Documentation + API_DOCS_ENABLED: bool = os.getenv("API_DOCS_ENABLED", "true").lower() == "true" + API_DOCS_URL: str = "/docs" + API_REDOC_URL: str = "/redoc" + API_OPENAPI_URL: str = "/openapi.json" + + # ================================================================ + # EXTERNAL APIS & INTEGRATIONS + # ================================================================ + + # Weather API (AEMET - Spanish Weather Service) + AEMET_API_KEY: str = os.getenv("AEMET_API_KEY", "") + AEMET_BASE_URL: str = "https://opendata.aemet.es/opendata" + AEMET_TIMEOUT: int = int(os.getenv("AEMET_TIMEOUT", "30")) + + # Madrid Open Data + MADRID_OPENDATA_API_KEY: str = os.getenv("MADRID_OPENDATA_API_KEY", "") + MADRID_OPENDATA_BASE_URL: str = "https://datos.madrid.es" + MADRID_OPENDATA_TIMEOUT: int = int(os.getenv("MADRID_OPENDATA_TIMEOUT", "30")) + + # Email Configuration + SMTP_HOST: str = os.getenv("SMTP_HOST", "smtp.gmail.com") + SMTP_PORT: int = int(os.getenv("SMTP_PORT", "587")) + SMTP_USER: str = os.getenv("SMTP_USER", "") + SMTP_PASSWORD: str = os.getenv("SMTP_PASSWORD", "") + SMTP_TLS: bool = os.getenv("SMTP_TLS", "true").lower() == "true" + SMTP_SSL: bool = os.getenv("SMTP_SSL", "false").lower() == "true" + + # WhatsApp API + WHATSAPP_API_KEY: str = os.getenv("WHATSAPP_API_KEY", "") + WHATSAPP_BASE_URL: str = os.getenv("WHATSAPP_BASE_URL", "https://api.twilio.com") + WHATSAPP_FROM_NUMBER: str = os.getenv("WHATSAPP_FROM_NUMBER", "") + + # ================================================================ + # ML & AI CONFIGURATION + # ================================================================ + + # Model Storage + MODEL_STORAGE_PATH: str = os.getenv("MODEL_STORAGE_PATH", "/app/models") + MODEL_STORAGE_BACKEND: str = os.getenv("MODEL_STORAGE_BACKEND", "local") # local, s3, gcs + + # Training Configuration + MAX_TRAINING_TIME_MINUTES: int = int(os.getenv("MAX_TRAINING_TIME_MINUTES", "30")) + MIN_TRAINING_DATA_DAYS: int = int(os.getenv("MIN_TRAINING_DATA_DAYS", "30")) + TRAINING_BATCH_SIZE: int = int(os.getenv("TRAINING_BATCH_SIZE", "1000")) + + # Prophet Configuration + PROPHET_SEASONALITY_MODE: str = os.getenv("PROPHET_SEASONALITY_MODE", "additive") + PROPHET_CHANGEPOINT_PRIOR_SCALE: float = float(os.getenv("PROPHET_CHANGEPOINT_PRIOR_SCALE", "0.05")) + PROPHET_SEASONALITY_PRIOR_SCALE: float = float(os.getenv("PROPHET_SEASONALITY_PRIOR_SCALE", "10.0")) + + # Prediction Caching + PREDICTION_CACHE_TTL_HOURS: int = int(os.getenv("PREDICTION_CACHE_TTL_HOURS", "6")) + WEATHER_CACHE_TTL_HOURS: int = int(os.getenv("WEATHER_CACHE_TTL_HOURS", "1")) + TRAFFIC_CACHE_TTL_HOURS: int = int(os.getenv("TRAFFIC_CACHE_TTL_HOURS", "1")) + + # ================================================================ + # MONITORING & OBSERVABILITY + # ================================================================ + + # Logging Configuration + LOG_FORMAT: str = os.getenv("LOG_FORMAT", "json") # json, text + LOG_FILE_ENABLED: bool = os.getenv("LOG_FILE_ENABLED", "false").lower() == "true" + LOG_FILE_PATH: str = os.getenv("LOG_FILE_PATH", "/app/logs") + LOG_ROTATION_SIZE: str = os.getenv("LOG_ROTATION_SIZE", "100MB") + LOG_RETENTION_DAYS: int = int(os.getenv("LOG_RETENTION_DAYS", "30")) + + # Metrics & Monitoring + PROMETHEUS_ENABLED: bool = os.getenv("PROMETHEUS_ENABLED", "true").lower() == "true" + PROMETHEUS_PORT: int = int(os.getenv("PROMETHEUS_PORT", "9090")) + PROMETHEUS_PATH: str = "/metrics" + + # Tracing + JAEGER_ENABLED: bool = os.getenv("JAEGER_ENABLED", "false").lower() == "true" + JAEGER_AGENT_HOST: str = os.getenv("JAEGER_AGENT_HOST", "localhost") + JAEGER_AGENT_PORT: int = int(os.getenv("JAEGER_AGENT_PORT", "6831")) + + # Health Checks + HEALTH_CHECK_TIMEOUT: int = int(os.getenv("HEALTH_CHECK_TIMEOUT", "30")) + HEALTH_CHECK_INTERVAL: int = int(os.getenv("HEALTH_CHECK_INTERVAL", "30")) + + # ================================================================ + # DATA RETENTION & CLEANUP + # ================================================================ + + DATA_RETENTION_DAYS: int = int(os.getenv("DATA_RETENTION_DAYS", "365")) + LOG_RETENTION_DAYS: int = int(os.getenv("LOG_RETENTION_DAYS", "90")) + METRIC_RETENTION_DAYS: int = int(os.getenv("METRIC_RETENTION_DAYS", "90")) + TEMP_FILE_CLEANUP_HOURS: int = int(os.getenv("TEMP_FILE_CLEANUP_HOURS", "24")) + + # ================================================================ + # BUSINESS RULES & CONSTRAINTS + # ================================================================ + + # Forecasting Business Rules + MAX_FORECAST_DAYS: int = int(os.getenv("MAX_FORECAST_DAYS", "30")) + MIN_HISTORICAL_DAYS: int = int(os.getenv("MIN_HISTORICAL_DAYS", "60")) + CONFIDENCE_THRESHOLD: float = float(os.getenv("CONFIDENCE_THRESHOLD", "0.8")) + + # Spanish Business Context + TIMEZONE: str = os.getenv("TIMEZONE", "Europe/Madrid") + LOCALE: str = os.getenv("LOCALE", "es_ES.UTF-8") + CURRENCY: str = os.getenv("CURRENCY", "EUR") + + # Business Hours (24-hour format) + BUSINESS_HOUR_START: int = int(os.getenv("BUSINESS_HOUR_START", "7")) + BUSINESS_HOUR_END: int = int(os.getenv("BUSINESS_HOUR_END", "20")) + + # Spanish Holidays & Seasonal Adjustments + ENABLE_SPANISH_HOLIDAYS: bool = os.getenv("ENABLE_SPANISH_HOLIDAYS", "true").lower() == "true" + ENABLE_MADRID_HOLIDAYS: bool = os.getenv("ENABLE_MADRID_HOLIDAYS", "true").lower() == "true" + SCHOOL_CALENDAR_ENABLED: bool = os.getenv("SCHOOL_CALENDAR_ENABLED", "true").lower() == "true" + + # ================================================================ + # DEVELOPMENT & TESTING + # ================================================================ + + # Testing Configuration + TESTING: bool = os.getenv("TESTING", "false").lower() == "true" + TEST_DATABASE_URL: str = os.getenv("TEST_DATABASE_URL", "") + MOCK_EXTERNAL_APIS: bool = os.getenv("MOCK_EXTERNAL_APIS", "false").lower() == "true" + + # Development Features + AUTO_RELOAD: bool = os.getenv("AUTO_RELOAD", "false").lower() == "true" + PROFILING_ENABLED: bool = os.getenv("PROFILING_ENABLED", "false").lower() == "true" + + # ================================================================ + # VALIDATORS + # ================================================================ + + @validator('JWT_SECRET_KEY') + def validate_jwt_secret(cls, v): + if v == "change-this-in-production" and os.getenv("ENVIRONMENT") == "production": + raise ValueError("JWT_SECRET_KEY must be changed in production") + if len(v) < 32: + raise ValueError("JWT_SECRET_KEY must be at least 32 characters long") + return v + + @validator('DATABASE_URL') + def validate_database_url(cls, v): + if not v: + raise ValueError("DATABASE_URL is required") + if not v.startswith(('postgresql://', 'postgresql+asyncpg://')): + raise ValueError("DATABASE_URL must be a PostgreSQL URL") + return v + + @validator('LOG_LEVEL') + def validate_log_level(cls, v): + valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] + if v.upper() not in valid_levels: + raise ValueError(f"LOG_LEVEL must be one of: {valid_levels}") + return v.upper() + + @validator('ENVIRONMENT') + def validate_environment(cls, v): + valid_envs = ['development', 'staging', 'production', 'testing'] + if v.lower() not in valid_envs: + raise ValueError(f"ENVIRONMENT must be one of: {valid_envs}") + return v.lower() + + # ================================================================ + # COMPUTED PROPERTIES + # ================================================================ + + @property + def IS_PRODUCTION(self) -> bool: + """Check if running in production""" + return self.ENVIRONMENT == "production" + + @property + def IS_DEVELOPMENT(self) -> bool: + """Check if running in development""" + return self.ENVIRONMENT == "development" + + @property + def IS_TESTING(self) -> bool: + """Check if running tests""" + return self.TESTING or self.ENVIRONMENT == "testing" + + @property + def SERVICE_REGISTRY(self) -> Dict[str, str]: + """Get all service URLs""" + return { + "gateway": self.GATEWAY_URL, + "auth": self.AUTH_SERVICE_URL, + "training": self.TRAINING_SERVICE_URL, + "forecasting": self.FORECASTING_SERVICE_URL, + "data": self.DATA_SERVICE_URL, + "tenant": self.TENANT_SERVICE_URL, + "notification": self.NOTIFICATION_SERVICE_URL, + } + + @property + def DATABASE_CONFIG(self) -> Dict[str, Any]: + """Get database configuration for SQLAlchemy""" + return { + "url": self.DATABASE_URL, + "pool_size": self.DB_POOL_SIZE, + "max_overflow": self.DB_MAX_OVERFLOW, + "pool_timeout": self.DB_POOL_TIMEOUT, + "pool_recycle": self.DB_POOL_RECYCLE, + "echo": self.DB_ECHO, + } + + @property + def REDIS_CONFIG(self) -> Dict[str, Any]: + """Get Redis configuration""" + return { + "url": self.REDIS_URL_WITH_DB, + "max_connections": self.REDIS_MAX_CONNECTIONS, + "retry_on_timeout": self.REDIS_RETRY_ON_TIMEOUT, + "socket_keepalive": self.REDIS_SOCKET_KEEPALIVE, + "socket_keepalive_options": self.REDIS_SOCKET_KEEPALIVE_OPTIONS, + } + + # ================================================================ + # CONFIGURATION LOADING + # ================================================================ + + class Config: + env_file = ".env" + env_file_encoding = 'utf-8' + case_sensitive = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Validate critical settings in production + if self.IS_PRODUCTION: + self._validate_production_settings() + + def _validate_production_settings(self): + """Validate production-specific settings""" + critical_settings = [ + 'JWT_SECRET_KEY', + 'DATABASE_URL', + 'REDIS_URL', + 'RABBITMQ_URL' + ] + + for setting in critical_settings: + value = getattr(self, setting) + if not value or 'change' in value.lower() or 'default' in value.lower(): + raise ValueError(f"{setting} must be properly configured for production") \ No newline at end of file diff --git a/shared/config/environments.py b/shared/config/environments.py new file mode 100644 index 00000000..2c8ccc4e --- /dev/null +++ b/shared/config/environments.py @@ -0,0 +1,70 @@ +# ================================================================ +# ENVIRONMENT-SPECIFIC CONFIGURATIONS +# shared/config/environments.py +# ================================================================ + +""" +Environment-specific configuration overrides +""" + +from typing import Dict, Any + +DEVELOPMENT_OVERRIDES: Dict[str, Any] = { + "DEBUG": True, + "LOG_LEVEL": "DEBUG", + "DB_ECHO": True, + "API_DOCS_ENABLED": True, + "CORS_ORIGINS": "http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000", + "MOCK_EXTERNAL_APIS": True, + "AUTO_RELOAD": True, +} + +STAGING_OVERRIDES: Dict[str, Any] = { + "DEBUG": False, + "LOG_LEVEL": "INFO", + "DB_ECHO": False, + "API_DOCS_ENABLED": True, + "MOCK_EXTERNAL_APIS": False, + "AUTO_RELOAD": False, +} + +PRODUCTION_OVERRIDES: Dict[str, Any] = { + "DEBUG": False, + "LOG_LEVEL": "WARNING", + "DB_ECHO": False, + "API_DOCS_ENABLED": False, + "MOCK_EXTERNAL_APIS": False, + "AUTO_RELOAD": False, + "PROFILING_ENABLED": False, + "RATE_LIMIT_ENABLED": True, +} + +TESTING_OVERRIDES: Dict[str, Any] = { + "TESTING": True, + "DEBUG": True, + "LOG_LEVEL": "DEBUG", + "DATABASE_URL": "postgresql+asyncpg://test_user:test_pass@test-db:5432/test_db", + "REDIS_URL": "redis://test-redis:6379", + "MOCK_EXTERNAL_APIS": True, + "EMAIL_VERIFICATION_REQUIRED": False, + "RATE_LIMIT_ENABLED": False, +} + +def get_environment_overrides(environment: str) -> Dict[str, Any]: + """ + Get configuration overrides for specific environment + + Args: + environment: Environment name (development, staging, production, testing) + + Returns: + Dict: Configuration overrides + """ + overrides = { + "development": DEVELOPMENT_OVERRIDES, + "staging": STAGING_OVERRIDES, + "production": PRODUCTION_OVERRIDES, + "testing": TESTING_OVERRIDES, + } + + return overrides.get(environment.lower(), {}) \ No newline at end of file diff --git a/shared/config/utils.py b/shared/config/utils.py new file mode 100644 index 00000000..8af50578 --- /dev/null +++ b/shared/config/utils.py @@ -0,0 +1,83 @@ +# ================================================================ +# SHARED CONFIGURATION UTILITIES +# shared/config/utils.py +# ================================================================ + +""" +Configuration utilities and helpers +""" + +from typing import Dict, Any, Type +from shared.config.base import BaseServiceSettings + +# Service settings registry +SERVICE_SETTINGS: Dict[str, Type[BaseServiceSettings]] = { + "gateway": GatewaySettings, + "auth-service": AuthSettings, + "training-service": TrainingSettings, + "forecasting-service": ForecastingSettings, + "data-service": DataSettings, + "tenant-service": TenantSettings, + "notification-service": NotificationSettings, +} + +def get_settings_for_service(service_name: str) -> BaseServiceSettings: + """ + Get settings instance for a specific service + + Args: + service_name: Name of the service + + Returns: + BaseServiceSettings: Configured settings instance + + Raises: + ValueError: If service name is not recognized + """ + if service_name not in SERVICE_SETTINGS: + raise ValueError(f"Unknown service: {service_name}. Available: {list(SERVICE_SETTINGS.keys())}") + + settings_class = SERVICE_SETTINGS[service_name] + return settings_class() + +def validate_all_service_configs() -> Dict[str, Any]: + """ + Validate configuration for all services + + Returns: + Dict: Validation results for each service + """ + results = {} + + for service_name, settings_class in SERVICE_SETTINGS.items(): + try: + settings = settings_class() + results[service_name] = { + "status": "valid", + "config": { + "app_name": settings.APP_NAME, + "version": settings.VERSION, + "environment": settings.ENVIRONMENT, + "database_configured": bool(settings.DATABASE_URL), + "redis_configured": bool(settings.REDIS_URL), + "debug_mode": settings.DEBUG, + } + } + except Exception as e: + results[service_name] = { + "status": "error", + "error": str(e) + } + + return results + +def get_service_urls() -> Dict[str, str]: + """ + Get all service URLs from any service configuration + + Returns: + Dict: Service name to URL mapping + """ + # Use auth service settings as reference (all services have same URLs) + settings = AuthSettings() + return settings.SERVICE_REGISTRY