119 lines
3.9 KiB
Python
119 lines
3.9 KiB
Python
import logging
|
||
import asyncio
|
||
import os
|
||
from contextlib import asynccontextmanager
|
||
from fastapi import FastAPI, Depends
|
||
from fastapi.staticfiles import StaticFiles
|
||
|
||
from app.core.database import init_db, async_session
|
||
from app.core.server_info import get_app_version
|
||
from app.core.scheduler import start_scheduler
|
||
from app.core.state import state_manager, discovery_service
|
||
from sqlalchemy import select
|
||
from app.models.device import GroupModel
|
||
from app.api.routes import devices, control, schedules, api_keys, stats, system
|
||
from app.api.deps import verify_token
|
||
|
||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
||
|
||
logging.basicConfig(level=LOG_LEVEL, format="%(asctime)s | %(levelname)s | %(message)s")
|
||
logger = logging.getLogger(__name__)
|
||
|
||
UI_CONTENT_SECURITY_POLICY = (
|
||
"default-src 'self'; "
|
||
"script-src 'self' 'unsafe-eval'; "
|
||
"style-src 'self' 'unsafe-inline'; "
|
||
"img-src 'self' data:; "
|
||
"font-src 'self' data:; "
|
||
"connect-src 'self'; "
|
||
"object-src 'none'; "
|
||
"base-uri 'self'; "
|
||
"frame-ancestors 'none'; "
|
||
"form-action 'self'"
|
||
)
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
# 1. БД
|
||
await init_db()
|
||
|
||
# 2. Загрузка групп
|
||
async with async_session() as session:
|
||
result = await session.execute(select(GroupModel))
|
||
for g in result.scalars().all():
|
||
state_manager.groups[g.id] = g
|
||
logger.info(f"📂 Загружена группа: {g.name}")
|
||
|
||
# 3. Startup discovery до старта фонового цикла
|
||
await discovery_service.startup_refresh(state_manager)
|
||
|
||
# 4. Планировщик после загрузки метаданных групп
|
||
await start_scheduler()
|
||
|
||
# 5. Фоновый Discovery
|
||
discovery_task = asyncio.create_task(
|
||
discovery_service.start_background_discovery(state_manager)
|
||
)
|
||
|
||
yield
|
||
|
||
discovery_task.cancel()
|
||
logger.info("🛑 Ignis Core остановлен")
|
||
|
||
|
||
app = FastAPI(title="Ignis Core API", version=get_app_version(), lifespan=lifespan)
|
||
|
||
|
||
@app.middleware("http")
|
||
async def add_security_headers(request, call_next):
|
||
response = await call_next(request)
|
||
response.headers.setdefault("Cache-Control", "no-store")
|
||
response.headers.setdefault("Pragma", "no-cache")
|
||
response.headers.setdefault("Referrer-Policy", "no-referrer")
|
||
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||
response.headers.setdefault("X-Frame-Options", "DENY")
|
||
response.headers.setdefault("Cross-Origin-Opener-Policy", "same-origin")
|
||
response.headers.setdefault("Cross-Origin-Resource-Policy", "same-origin")
|
||
response.headers.setdefault(
|
||
"Permissions-Policy",
|
||
"camera=(), geolocation=(), microphone=()",
|
||
)
|
||
response.headers.setdefault("Content-Security-Policy", UI_CONTENT_SECURITY_POLICY)
|
||
return response
|
||
|
||
|
||
# Регистрация роутеров
|
||
app.include_router(devices.router, prefix="/devices", tags=["Devices & Groups"])
|
||
app.include_router(control.router, prefix="/control", tags=["Control"])
|
||
app.include_router(schedules.router, prefix="/schedules", tags=["Schedules"])
|
||
app.include_router(api_keys.router, prefix="/api-keys", tags=["API Keys"])
|
||
app.include_router(stats.router, prefix="/stats", tags=["Stats"])
|
||
app.include_router(system.router, prefix="/system", tags=["System"])
|
||
|
||
# Статика
|
||
# Мы убираем html=True из корня, чтобы 404-е ошибки API не превращались в загрузку index.html
|
||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||
|
||
|
||
@app.get("/")
|
||
async def read_index():
|
||
from fastapi.responses import FileResponse
|
||
|
||
return FileResponse("static/index.html")
|
||
|
||
|
||
@app.get("/auth/me")
|
||
async def auth_me(auth=Depends(verify_token)):
|
||
return {
|
||
"is_admin": auth.is_admin,
|
||
"is_master": auth.is_master,
|
||
"name": auth.key_name,
|
||
}
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
|
||
uvicorn.run(app, host="0.0.0.0", port=8000)
|