After a power loss the app started before Oracle was ready; init_oracle() failed
once, the pool stayed None forever (no retry), and every sync silently failed
("Oracle pool not initialized") while still hammering the GoMag API each minute,
and order-detail 500'd.
- database.ensure_oracle_pool(force): thread-safe (re)create of the pool, called
at the start of every sync cycle → self-heals within one cycle once Oracle is
back (incl. after an Oracle service restart). init_oracle_client made idempotent
so re-init can't fall back to thin mode.
- database.oracle_status() exposed; main.py startup is non-fatal via ensure pool.
- run_sync ensures the pool before the GoMag download; on failure it records a
clear run status instead of crashing and skips the wasted API calls.
- /api/sync/health reports oracle_ready/last_error; dashboard health pill shows
"Oracle indisponibil" (top priority). Recovery via the existing Start Sync button.
- order_detail degrades gracefully (200 without CODMAT + notice) instead of 500.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
from contextlib import asynccontextmanager
|
|
from datetime import datetime
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
from pathlib import Path
|
|
import logging
|
|
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)
|
|
_log_filename = f"sync_comenzi_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
_file_handler = logging.FileHandler(os.path.join(_log_dir, _log_filename), 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
|
|
|
|
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)
|