feat: add FastAPI admin dashboard with sync orchestration and test suite
Replace Flask admin with FastAPI app (api/app/) featuring: - Dashboard with stat cards, sync control, and history - Mappings CRUD for ARTICOLE_TERTI with CSV import/export - Article autocomplete from NOM_ARTICOLE - SKU pre-validation before import - Sync orchestration: read JSONs -> validate -> import -> log to SQLite - APScheduler for periodic sync from UI - File logging to logs/sync_comenzi_YYYYMMDD_HHMMSS.log - Oracle pool None guard (503 vs 500 on unavailable) Test suite: - test_app_basic.py: 30 tests (imports + routes) without Oracle - test_integration.py: 9 integration tests with Oracle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
91
api/app/main.py
Normal file
91
api/app/main.py
Normal file
@@ -0,0 +1,91 @@
|
||||
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 init_oracle, 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
|
||||
try:
|
||||
init_oracle()
|
||||
except Exception as e:
|
||||
logger.error(f"Oracle init failed: {e}")
|
||||
# Allow app to start even without Oracle for development
|
||||
|
||||
# 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", "5"))
|
||||
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)
|
||||
Reference in New Issue
Block a user