Files
rssWorkFlow/backend/main.py
T

132 lines
3.8 KiB
Python
Raw Normal View History

"""RSS Platform FastAPI application."""
from contextlib import asynccontextmanager
from uuid import uuid4
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from app.api.v1 import auth, articles, feeds, health, settings
from app.api.v1.admin import locks
from app.core.config import settings
from app.core.database import close_db
from app.core.exceptions import add_exception_handlers
from app.core.logging import configure_logging, request_id_var
from app.core.redis import close_redis
from app.services.settings_service import apply_db_settings_to_config, init_default_settings
configure_logging(settings.LOG_LEVEL)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager."""
from app.core.database import AsyncSessionLocal
app.state.startup_warnings = []
async with AsyncSessionLocal() as db:
await init_default_settings(db)
await apply_db_settings_to_config(db)
warnings = await _create_default_admin(db)
app.state.startup_warnings.extend(warnings)
yield
# Shutdown
await close_db()
await close_redis()
async def _create_default_admin(db) -> list[str]:
"""Create default admin user if no users exist."""
from sqlalchemy import select
from app.core.auth import get_password_hash
from app.models.user import User
warnings: list[str] = []
result = await db.execute(select(User))
if result.scalar_one_or_none():
return warnings
if (
settings.DEFAULT_ADMIN_USERNAME == "admin"
and settings.DEFAULT_ADMIN_PASSWORD == "admin"
):
warnings.append(
"Default admin credentials are admin/admin. Please change the password immediately."
)
admin = User(
username=settings.DEFAULT_ADMIN_USERNAME,
password_hash=get_password_hash(settings.DEFAULT_ADMIN_PASSWORD),
role="admin",
is_active=True,
)
db.add(admin)
await db.commit()
return warnings
app = FastAPI(
title="RSS Platform",
description="模块化、工业化、AI 驱动的 RSS 信息处理平台",
version="0.1.0",
lifespan=lifespan,
)
# CORS
cors_origins = settings.cors_origins
if not cors_origins:
# In production, CORS_ALLOWED_ORIGINS must be configured explicitly.
# Dev fallback uses the known frontend origin instead of wildcard.
cors_origins = ["http://localhost:5173"]
app.add_middleware(
CORSMiddleware,
allow_origins=cors_origins,
allow_credentials=False,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-API-Key", "X-Request-ID"],
)
@app.middleware("http")
async def request_id_middleware(request: Request, call_next):
"""Attach request_id from header or generate a new one for logging."""
request_id = request.headers.get("X-Request-ID") or str(uuid4())
token = request_id_var.set(request_id)
try:
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
return response
finally:
request_id_var.reset(token)
# Exception handlers
add_exception_handlers(app)
# API routers
app.include_router(auth.router, prefix="/api/v1")
app.include_router(feeds.router, prefix="/api/v1")
app.include_router(articles.router, prefix="/api/v1")
app.include_router(health.router, prefix="/api/v1")
app.include_router(settings.router, prefix="/api/v1")
app.include_router(locks.router, prefix="/api/v1/admin")
@app.get("/")
async def root():
"""Root endpoint."""
return {"message": "RSS Platform API", "version": "0.1.0"}
# Static files (frontend build)
import os
static_dir = os.path.join(os.path.dirname(__file__), "static")
if os.path.isdir(static_dir):
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")