"""FlowDeck — Kanban léger intégré à Gitea.""" from __future__ import annotations import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.sessions import SessionMiddleware from app.config import settings from app.db import init_db from app.middleware.csrf import CSRFMiddleware from app.middleware.security import ContentSecurityPolicyMiddleware, RateLimitMiddleware from app.routers import dashboard, board, notes, api, auth, webhooks, collections, my_tasks, workspace, library, public_api, admin, sharing from app.routers.gitea import router as gitea_router from app.routers.github_routes import router as github_router from app.services.gitea_client import gitea from app.services.webhook_outbound import init_webhook_tables logging.basicConfig( level=getattr(logging, settings.log_level.upper(), logging.INFO), format="%(asctime)s [%(levelname)s] %(message)s", ) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(_app: FastAPI): init_db() init_webhook_tables() from app.db import get_conn with get_conn() as conn: conn.execute( "INSERT OR IGNORE INTO users (login, full_name, email, is_admin) VALUES ('admin', 'Admin', '', 1)" ) conn.commit() logger.info("FlowDeck v4.0.0 started on port %d", settings.app_port) yield app = FastAPI( title="FlowDeck", version="4.0.0", docs_url="/docs" if settings.log_level == "DEBUG" else None, redoc_url=None, lifespan=lifespan, ) app.add_middleware(SessionMiddleware, secret_key=settings.app_secret_key, max_age=3600) app.add_middleware(CSRFMiddleware) app.add_middleware(ContentSecurityPolicyMiddleware) app.add_middleware(RateLimitMiddleware) app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) app.include_router(auth.router) app.include_router(dashboard.router) app.include_router(board.router) app.include_router(notes.router) app.include_router(api.router) app.include_router(webhooks.router) app.include_router(collections.router) app.include_router(my_tasks.router) app.include_router(workspace.router) app.include_router(library.router) app.include_router(admin.router) app.include_router(gitea_router) app.include_router(github_router) app.include_router(public_api.router) app.include_router(sharing.router) app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/manifest.json") async def pwa_manifest(): return { "name": "FlowDeck", "short_name": "FlowDeck", "start_url": "/", "display": "standalone", "background_color": "#191919", "theme_color": "#191919", "icons": [{"src": "/static/icon-192.png", "sizes": "192x192", "type": "image/png"}], } # ═══════════ API aliases (v4.0.1) ═══════════ @app.get("/api/pages") async def api_pages_alias(request: Request): """Alias /api/pages → /board/api/pages for API path consistency.""" from fastapi.responses import RedirectResponse qs = str(request.url.query) target = f"/board/api/pages{'?' + qs if qs else ''}" return RedirectResponse(url=target, status_code=307) @app.post("/api/pages") async def api_pages_post_alias(request: Request): """Alias POST /api/pages → /board/api/pages for API path consistency.""" from fastapi.responses import RedirectResponse return RedirectResponse(url="/board/api/pages", status_code=307) # ═══════════ Styled 404 handler ═══════════ NOT_FOUND_HTML = """ 404 — FlowDeck

404

This page doesn't exist or has been moved.

← Back to FlowDeck
""" @app.exception_handler(404) async def not_found_handler(request: Request, exc): """Styled 404 page matching the FlowDeck dark theme.""" from fastapi.responses import HTMLResponse # API routes should get JSON, not HTML if request.url.path.startswith("/api/") or request.url.path.startswith("/board/api/"): from fastapi.responses import JSONResponse return JSONResponse({"detail": "Not found"}, status_code=404) return HTMLResponse(NOT_FOUND_HTML, status_code=404)