Files
bruno cefc7eb356
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
fix: admin password permanent + Help/My Tasks content rendering
- main.py: lifespan insère toujours le hash de 'FlowDeck2026!' pour admin
- dashboard.py + my_tasks.py: content_html passe via {% block content %} (Jinja)
- Avant: content_html ignoré car base.html utilise des blocs, pas des variables
- 143 tests passent
2026-07-20 11:02:37 -04:00

148 lines
5.2 KiB
Python

"""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
from app.password_utils import hash_password
admin_hash = hash_password("FlowDeck2026!")
with get_conn() as conn:
conn.execute(
"INSERT OR IGNORE INTO users (login, full_name, email, password_hash, is_admin) VALUES ('admin', 'Admin', '', ?, 1)",
(admin_hash,)
)
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 = """<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>404 — FlowDeck</title>
<style>
:root{--bg:#191919;--text:#e0e0e0;--text-dim:#999;--accent:#2383E2}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center}
.box h1{font-size:6rem;font-weight:800;opacity:.08;line-height:1}
.box p{font-size:18px;color:var(--text-dim);margin:8px 0 24px}
.box a{color:var(--accent);text-decoration:none;font-size:14px}
.box a:hover{text-decoration:underline}
</style>
</head>
<body>
<div class="box">
<h1>404</h1>
<p>This page doesn't exist or has been moved.</p>
<a href="/">← Back to FlowDeck</a>
</div>
</body>
</html>"""
@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)