"""KiraStream — IPTV Management Server.""" import asyncio import logging import sys from contextlib import asynccontextmanager from fastapi import FastAPI, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.security import OAuth2PasswordRequestForm import json from .config import settings from .database import init_db, AsyncSessionLocal from .core.pool import pool from .core.epg_manager import epg_background_task from .core.health_checker import health_check_loop from .database import get_db logging.basicConfig( level=getattr(logging, settings.log_level), format="%(asctime)s %(levelname)s %(name)s — %(message)s", handlers=[logging.StreamHandler(sys.stdout)], ) logger = logging.getLogger(__name__) async def _auto_sync_loop() -> None: """Check every 30 minutes if any provider is due for auto-sync.""" from sqlalchemy import select from .models.provider import ProviderAccount from .api.admin.providers import _run_sync, _sync_status await asyncio.sleep(60) # delay first check so startup completes while True: try: async with AsyncSessionLocal() as db: result = await db.execute( select(ProviderAccount).where(ProviderAccount.auto_sync_hours > 0, ProviderAccount.is_active == True) # noqa: E712 ) providers = result.scalars().all() now = asyncio.get_event_loop().time() import time as _time wall_now = _time.time() for p in providers: interval_secs = p.auto_sync_hours * 3600 last_sync_ts = p.last_sync_at.timestamp() if p.last_sync_at else 0 if wall_now - last_sync_ts >= interval_secs: if _sync_status.get(p.id, {}).get("status") == "running": continue logger.info(f"Auto-sync triggered for provider {p.id} ({p.name})") cats = json.loads(p.last_sync_categories) if p.last_sync_categories else {} asyncio.create_task(_run_sync( p.id, p.base_url, p.username, p.password, selected_live=set(cats["live"]) if cats.get("live") is not None else None, selected_movie=set(cats["movie"]) if cats.get("movie") is not None else None, selected_series=set(cats["series"]) if cats.get("series") is not None else None, )) except Exception as e: logger.error(f"Auto-sync loop error: {e}") await asyncio.sleep(1800) # check every 30 minutes @asynccontextmanager async def lifespan(app: FastAPI): logger.info("KiraStream starting up...") await init_db() await pool.init() epg_task = asyncio.create_task(epg_background_task(get_db)) auto_sync_task = asyncio.create_task(_auto_sync_loop()) health_task = asyncio.create_task(health_check_loop()) logger.info("KiraStream ready") yield logger.info("KiraStream shutting down...") await pool.close() epg_task.cancel() auto_sync_task.cancel() health_task.cancel() for t in (epg_task, auto_sync_task, health_task): try: await t except asyncio.CancelledError: pass app = FastAPI( title="KiraStream", description="Professional IPTV Management with Xtream Codes", version="1.0.0", lifespan=lifespan, docs_url="/api/docs", redoc_url="/api/redoc", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # --- Admin auth --- from .api.auth import login, Token # noqa: E402 @app.post("/api/admin/auth/token", response_model=Token, tags=["admin"]) async def admin_login(form_data: OAuth2PasswordRequestForm = Depends()): return await login(form_data) # --- Admin routers --- from .api.admin.providers import router as providers_router # noqa: E402 from .api.admin.users import router as users_router # noqa: E402 from .api.admin.catalogs import router as catalogs_router # noqa: E402 from .api.admin.epg_admin import router as epg_admin_router # noqa: E402 from .api.admin.monitoring import router as monitoring_router # noqa: E402 from .api.admin.custom_categories import router as custom_cats_router # noqa: E402 from .api.admin.logs import router as logs_router # noqa: E402 from .api.admin.settings import router as settings_router # noqa: E402 from .api.admin.jellyfin_admin import router as jellyfin_router # noqa: E402 app.include_router(providers_router, prefix="/api/admin") app.include_router(users_router, prefix="/api/admin") app.include_router(catalogs_router, prefix="/api/admin") app.include_router(epg_admin_router, prefix="/api/admin") app.include_router(monitoring_router, prefix="/api/admin") app.include_router(custom_cats_router, prefix="/api/admin") app.include_router(logs_router, prefix="/api/admin") app.include_router(settings_router, prefix="/api/admin") app.include_router(jellyfin_router, prefix="/api/admin") # --- Xtream Codes compatible API (public) --- from .api.xtream import router as xtream_router # noqa: E402 app.include_router(xtream_router) @app.get("/health") async def health(): return {"status": "ok", "service": "kirastream"} @app.get("/") async def root(): return JSONResponse({"message": "KiraStream IPTV Server", "docs": "/api/docs"})