fix telegram
This commit is contained in:
296
deploy-package-20260223-151231/backend/config.py
Normal file
296
deploy-package-20260223-151231/backend/config.py
Normal file
@@ -0,0 +1,296 @@
|
||||
"""
|
||||
Unified Configuration for ROA2WEB Backend
|
||||
Consolidates settings from Reports, Data Entry, and Telegram modules
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic import BaseModel
|
||||
from functools import lru_cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OracleServerConfig(BaseModel):
|
||||
"""Configuration for a single Oracle server instance."""
|
||||
id: str # Unique identifier (e.g., "romfast", "client_a")
|
||||
name: str # Human-readable name (e.g., "Romfast - Producție")
|
||||
host: str = "localhost"
|
||||
port: int = 1521
|
||||
user: str
|
||||
password: str
|
||||
sid: Optional[str] = None
|
||||
service_name: Optional[str] = None
|
||||
|
||||
def get_dsn(self) -> str:
|
||||
"""Build DSN string for this server."""
|
||||
if self.service_name:
|
||||
return f"{self.host}:{self.port}/{self.service_name}"
|
||||
elif self.sid:
|
||||
return f"{self.host}:{self.port}:{self.sid}"
|
||||
else:
|
||||
return f"{self.host}:{self.port}/ROA"
|
||||
|
||||
|
||||
class UnifiedSettings(BaseSettings):
|
||||
"""Unified application settings for all modules."""
|
||||
|
||||
# ============================================================================
|
||||
# GENERAL APPLICATION SETTINGS
|
||||
# ============================================================================
|
||||
app_name: str = "ROA2WEB Unified Backend"
|
||||
app_version: str = "1.0.0"
|
||||
debug: bool = False
|
||||
api_host: str = "0.0.0.0"
|
||||
api_port: int = 8000
|
||||
|
||||
# ============================================================================
|
||||
# ORACLE DATABASE (Shared by all modules)
|
||||
# ============================================================================
|
||||
# Legacy single-server configuration (backward compatible)
|
||||
oracle_user: str = ""
|
||||
oracle_password: str = ""
|
||||
oracle_host: str = "localhost"
|
||||
oracle_port: int = 1526
|
||||
oracle_sid: str = "ROA"
|
||||
|
||||
# ============================================================================
|
||||
# MULTI-ORACLE SERVER CONFIGURATION (Optional)
|
||||
# ============================================================================
|
||||
# JSON array of server configs. If not set, uses legacy single-server config.
|
||||
# Example: ORACLE_SERVERS='[{"id": "romfast", "name": "Romfast", "host": "localhost", "port": 1521, "user": "USER", "password": "PASS", "sid": "ROA"}]'
|
||||
oracle_servers: Optional[str] = None # Raw JSON string from env
|
||||
|
||||
# Parsed server configurations (populated in model_post_init)
|
||||
_oracle_servers_parsed: List[OracleServerConfig] = []
|
||||
|
||||
def model_post_init(self, __context) -> None:
|
||||
"""Parse ORACLE_SERVERS JSON and build server list.
|
||||
|
||||
Oracle passwords are loaded from:
|
||||
1. secrets/{server_id}.oracle_pass file (preferred, more secure)
|
||||
2. password field in ORACLE_SERVERS JSON (fallback)
|
||||
"""
|
||||
servers = []
|
||||
secrets_dir = Path(__file__).parent / "secrets"
|
||||
|
||||
if self.oracle_servers:
|
||||
# Parse multi-server JSON configuration
|
||||
try:
|
||||
servers_data = json.loads(self.oracle_servers)
|
||||
if not isinstance(servers_data, list):
|
||||
raise ValueError("ORACLE_SERVERS must be a JSON array")
|
||||
|
||||
for server_data in servers_data:
|
||||
server_id = server_data.get("id", "default")
|
||||
|
||||
# Try to load password from secrets file
|
||||
pass_file = secrets_dir / f"{server_id}.oracle_pass"
|
||||
if pass_file.exists():
|
||||
server_data["password"] = pass_file.read_text().strip()
|
||||
logger.debug(f"Loaded Oracle password for '{server_id}' from {pass_file}")
|
||||
elif "password" not in server_data:
|
||||
logger.warning(f"No password found for server '{server_id}' - check secrets/{server_id}.oracle_pass")
|
||||
|
||||
servers.append(OracleServerConfig(**server_data))
|
||||
|
||||
logger.info(f"Loaded {len(servers)} Oracle servers from ORACLE_SERVERS config")
|
||||
for srv in servers:
|
||||
logger.info(f" - {srv.id}: {srv.name} ({srv.host}:{srv.port})")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse ORACLE_SERVERS JSON: {e}")
|
||||
raise ValueError(f"Invalid ORACLE_SERVERS JSON format: {e}")
|
||||
else:
|
||||
# Backward compatibility: build default server from legacy config
|
||||
if self.oracle_user:
|
||||
# Try to load password from secrets file
|
||||
password = self.oracle_password
|
||||
pass_file = secrets_dir / "default.oracle_pass"
|
||||
if pass_file.exists():
|
||||
password = pass_file.read_text().strip()
|
||||
logger.debug(f"Loaded Oracle password from {pass_file}")
|
||||
|
||||
default_server = OracleServerConfig(
|
||||
id="default",
|
||||
name="Default Server",
|
||||
host=self.oracle_host,
|
||||
port=self.oracle_port,
|
||||
user=self.oracle_user,
|
||||
password=password,
|
||||
sid=self.oracle_sid,
|
||||
)
|
||||
servers.append(default_server)
|
||||
logger.info("Using legacy single-server Oracle configuration (ORACLE_USER/HOST/etc)")
|
||||
logger.info(f" - default: {default_server.host}:{default_server.port}/{default_server.sid}")
|
||||
|
||||
object.__setattr__(self, '_oracle_servers_parsed', servers)
|
||||
|
||||
def get_oracle_servers(self) -> List[OracleServerConfig]:
|
||||
"""Get list of configured Oracle servers."""
|
||||
return self._oracle_servers_parsed
|
||||
|
||||
def get_oracle_server(self, server_id: str) -> Optional[OracleServerConfig]:
|
||||
"""Get a specific Oracle server by ID."""
|
||||
for server in self._oracle_servers_parsed:
|
||||
if server.id == server_id:
|
||||
return server
|
||||
return None
|
||||
|
||||
def get_default_oracle_server(self) -> Optional[OracleServerConfig]:
|
||||
"""Get the default Oracle server (first in list or 'default')."""
|
||||
if not self._oracle_servers_parsed:
|
||||
return None
|
||||
# Try to find server with id='default', otherwise return first
|
||||
for server in self._oracle_servers_parsed:
|
||||
if server.id == "default":
|
||||
return server
|
||||
return self._oracle_servers_parsed[0]
|
||||
|
||||
# ============================================================================
|
||||
# JWT AUTHENTICATION (Shared by all modules)
|
||||
# ============================================================================
|
||||
jwt_secret_key: str = "change-me-in-production"
|
||||
jwt_algorithm: str = "HS256"
|
||||
access_token_expire_minutes: int = 30
|
||||
refresh_token_expire_days: int = 7
|
||||
|
||||
# ============================================================================
|
||||
# SESSION SECURITY - EMAIL 2FA (Telegram module)
|
||||
# ============================================================================
|
||||
auth_session_secret: str = "change-me-in-production"
|
||||
|
||||
# ============================================================================
|
||||
# CORS
|
||||
# ============================================================================
|
||||
cors_origins: str = "http://localhost:3000,http://localhost:5173"
|
||||
|
||||
# ============================================================================
|
||||
# REPORTS MODULE - CACHE CONFIGURATION
|
||||
# ============================================================================
|
||||
reports_cache_enabled: bool = True
|
||||
reports_cache_type: str = "hybrid"
|
||||
reports_cache_sqlite_path: str = "./data/cache/roa2web_cache.db"
|
||||
reports_cache_memory_max_size: int = 1000
|
||||
reports_cache_default_ttl: int = 900
|
||||
|
||||
# Cache TTL per type (seconds)
|
||||
reports_cache_ttl_schema: int = 86400
|
||||
reports_cache_ttl_companies: int = 1800
|
||||
reports_cache_ttl_dashboard_summary: int = 1800
|
||||
reports_cache_ttl_dashboard_trends: int = 1800
|
||||
reports_cache_ttl_invoices: int = 600
|
||||
reports_cache_ttl_invoices_summary: int = 900
|
||||
reports_cache_ttl_treasury: int = 600
|
||||
|
||||
# Cache maintenance
|
||||
reports_cache_cleanup_interval: int = 3600
|
||||
reports_cache_auto_invalidate: bool = False
|
||||
reports_cache_check_interval: int = 300
|
||||
reports_cache_track_performance: bool = True
|
||||
reports_cache_benchmark_on_startup: bool = False
|
||||
|
||||
# ============================================================================
|
||||
# DATA ENTRY MODULE - CONFIGURATION
|
||||
# ============================================================================
|
||||
data_entry_sqlite_database_path: str = "data/receipts/receipts.db"
|
||||
data_entry_upload_path: str = "data/receipts/uploads"
|
||||
data_entry_max_upload_size_mb: int = 10
|
||||
data_entry_allowed_mime_types: List[str] = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"application/pdf",
|
||||
]
|
||||
|
||||
# ============================================================================
|
||||
# TELEGRAM MODULE - BOT CONFIGURATION
|
||||
# ============================================================================
|
||||
telegram_bot_token: str = ""
|
||||
telegram_smtp_host: str = ""
|
||||
telegram_smtp_port: int = 587
|
||||
telegram_smtp_user: str = ""
|
||||
telegram_smtp_password: str = ""
|
||||
telegram_smtp_from_email: str = ""
|
||||
telegram_smtp_from_name: str = "ROA2WEB"
|
||||
telegram_smtp_use_tls: bool = True
|
||||
telegram_email_max_retries: int = 3
|
||||
telegram_email_retry_delay: float = 2.0
|
||||
telegram_sqlite_database_path: str = "data/telegram/telegram.db"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
extra = "ignore"
|
||||
case_sensitive = False
|
||||
|
||||
# ============================================================================
|
||||
# COMPUTED PROPERTIES
|
||||
# ============================================================================
|
||||
|
||||
@property
|
||||
def oracle_dsn(self) -> str:
|
||||
"""Get Oracle DSN string."""
|
||||
return f"{self.oracle_host}:{self.oracle_port}/{self.oracle_sid}"
|
||||
|
||||
@property
|
||||
def cors_origins_list(self) -> List[str]:
|
||||
"""Get CORS origins as list."""
|
||||
return [origin.strip() for origin in self.cors_origins.split(",")]
|
||||
|
||||
# Data Entry properties
|
||||
@property
|
||||
def data_entry_database_url(self) -> str:
|
||||
"""Get SQLite database URL for async (Data Entry)."""
|
||||
# Resolve to absolute path for Windows/IIS compatibility
|
||||
abs_path = Path(self.data_entry_sqlite_database_path).resolve()
|
||||
return f"sqlite+aiosqlite:///{abs_path}"
|
||||
|
||||
@property
|
||||
def data_entry_sync_database_url(self) -> str:
|
||||
"""Get SQLite database URL for sync operations (Alembic)."""
|
||||
# Resolve to absolute path for Windows/IIS compatibility
|
||||
abs_path = Path(self.data_entry_sqlite_database_path).resolve()
|
||||
return f"sqlite:///{abs_path}"
|
||||
|
||||
@property
|
||||
def data_entry_upload_path_resolved(self) -> Path:
|
||||
"""Get resolved upload path."""
|
||||
path = Path(self.data_entry_upload_path)
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
@property
|
||||
def data_entry_max_upload_size_bytes(self) -> int:
|
||||
"""Get max upload size in bytes."""
|
||||
return self.data_entry_max_upload_size_mb * 1024 * 1024
|
||||
|
||||
# Reports cache properties
|
||||
@property
|
||||
def reports_cache_sqlite_path_resolved(self) -> Path:
|
||||
"""Get resolved cache SQLite path."""
|
||||
path = Path(self.reports_cache_sqlite_path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
# Telegram properties
|
||||
@property
|
||||
def telegram_sqlite_path_resolved(self) -> Path:
|
||||
"""Get resolved Telegram SQLite path."""
|
||||
path = Path(self.telegram_sqlite_database_path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_settings() -> UnifiedSettings:
|
||||
"""Get cached settings instance."""
|
||||
return UnifiedSettings()
|
||||
|
||||
|
||||
# Convenience instance
|
||||
settings = get_settings()
|
||||
Reference in New Issue
Block a user