Files
stealergram/web/app.py
anti 4c104cddd2 Add web frontend with JWT auth, RBAC, SSE dashboard, and config editor
- FastAPI + htmx + Jinja2 web frontend, started with --web flag
- JWT HS256 auth (WEB_SECRET_KEY) with httpOnly cookies; access (15 min) +
  refresh (7 day) tokens; refresh rotation + JTI revocation in data/web.db
- RBAC: superadmin > admin > reader enforced per route
- Live SSE dashboard fed by tui/events broadcast queue
- Config editor: keyword groups and channel list saved to data/runtime_config.json
  and hot-reloaded in-process (scorer.reload_from_config, signal_channel_changed)
- config.py migrated to load groups/channels from runtime_config.json;
  falls back to hardcoded defaults when file absent
- tui/events.py: subscribe/unsubscribe broadcast, set_bot_context/signal_channel_changed
- utils/scorer.py: import config as _config (fixes local binding); reload_from_config()
- utils/database.py: count_by_severity, recent_for_domains, count_by_severity_for_domains
- 53 new tests (events bus, JWT lifecycle, web DB CRUD, RBAC enforcement,
  config round-trip); total 141 passing
2026-04-02 11:41:46 -03:00

56 lines
1.5 KiB
Python

"""
web/app.py — FastAPI application factory.
Usage:
from web.app import create_app
app = create_app()
uvicorn.run(app, host=host, port=port)
The app is created fresh per uvicorn startup (no module-level state).
Templates and static files are mounted from web/templates/ and web/static/.
"""
from contextlib import asynccontextmanager
from pathlib import Path
import jinja2
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from web import db as webdb
from web.routes import auth, dashboard, config_routes, users
_WEB_DIR = Path(__file__).parent
@asynccontextmanager
async def _lifespan(app: FastAPI):
webdb.init_db()
yield
def create_app() -> FastAPI:
app = FastAPI(title="ULPgrammer", lifespan=_lifespan)
# Use a custom Environment with caching disabled.
# Jinja2's LRUCache has a Python 3.14 hashability issue with its cache key;
# cache_size=0 disables the LRUCache code path entirely.
_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(str(_WEB_DIR / "templates")),
autoescape=jinja2.select_autoescape(),
cache_size=0,
)
app.state.templates = Jinja2Templates(env=_env)
# Static files
app.mount("/static", StaticFiles(directory=str(_WEB_DIR / "static")), name="static")
# Routers
app.include_router(auth.router)
app.include_router(dashboard.router)
app.include_router(config_routes.router)
app.include_router(users.router)
return app