import asyncio from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from pathlib import Path import logging import logging.handlers import os from .config import settings from .database import ensure_oracle_pool, close_oracle, init_sqlite # Configure logging with both stream and file handlers _log_level = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO) _log_format = '%(asctime)s | %(levelname)s | %(name)s | %(message)s' _formatter = logging.Formatter(_log_format) _stream_handler = logging.StreamHandler() _stream_handler.setFormatter(_formatter) _log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'logs') os.makedirs(_log_dir, exist_ok=True) # Rotating handler (10MB x 5 backups) instead of a new timestamped file per # start — caps log growth and stops file proliferation across restarts. Fixed # name still matches the QA glob `sync_comenzi_*.log`. _file_handler = logging.handlers.RotatingFileHandler( os.path.join(_log_dir, "sync_comenzi_current.log"), maxBytes=10 * 1024 * 1024, backupCount=5, encoding='utf-8') _file_handler.setFormatter(_formatter) _root_logger = logging.getLogger() _root_logger.setLevel(_log_level) _root_logger.addHandler(_stream_handler) _root_logger.addHandler(_file_handler) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """Startup and shutdown events.""" logger.info("Starting GoMag Import Manager...") # Initialize Oracle pool (non-fatal: app still starts if Oracle is down; # each sync cycle calls ensure_oracle_pool() and self-heals when it returns) if not ensure_oracle_pool(): logger.error("Oracle pool not ready at startup — will retry on each sync cycle") # Initialize SQLite init_sqlite() # Initialize scheduler (restore saved config) from .services import scheduler_service, sqlite_service scheduler_service.init_scheduler() try: config = await sqlite_service.get_scheduler_config() if config.get("enabled") == "True": interval = int(config.get("interval_minutes", "10")) scheduler_service.start_scheduler(interval) except Exception: pass # Daily DB/log maintenance (prune audit history + cleanup old logs) + a # one-shot catch-up so a long-down service reclaims immediately on start. try: from .services import maintenance_service scheduler_service.start_maintenance_job() asyncio.create_task(maintenance_service.run_daily_maintenance()) except Exception as e: logger.warning(f"Maintenance scheduling failed: {e}") logger.info("GoMag Import Manager started") yield # Shutdown scheduler_service.shutdown_scheduler() close_oracle() logger.info("GoMag Import Manager stopped") app = FastAPI( title="GoMag Import Manager", description="Import comenzi web GoMag → ROA Oracle", version="1.0.0", lifespan=lifespan ) # Static files and templates static_dir = Path(__file__).parent / "static" templates_dir = Path(__file__).parent / "templates" static_dir.mkdir(parents=True, exist_ok=True) (static_dir / "css").mkdir(exist_ok=True) (static_dir / "js").mkdir(exist_ok=True) templates_dir.mkdir(parents=True, exist_ok=True) app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") # Include routers from .routers import health, dashboard, mappings, articles, validation, sync app.include_router(health.router) app.include_router(dashboard.router) app.include_router(mappings.router) app.include_router(articles.router) app.include_router(validation.router) app.include_router(sync.router)