Files
breakpilot-core/backend-core/main.py
Benjamin Admin 92c86ec6ba [split-required] [guardrail-change] Enforce 500 LOC budget across all services
Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook)
and split all 44 files exceeding 500 LOC into domain-focused modules:

- consent-service (Go): models, handlers, services, database splits
- backend-core (Python): security_api, rbac_api, pdf_service, auth splits
- admin-core (TypeScript): 5 page.tsx + sidebar extractions
- pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits
- voice-service (Python): enhanced_task_orchestrator split

Result: 0 violations, 36 exempted (pipeline, tests, pure-data files).
Go build verified clean. No behavior changes — pure structural splits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 00:09:30 +02:00

148 lines
5.4 KiB
Python

"""
BreakPilot Core Backend
Shared APIs for authentication, RBAC, notifications, email templates,
system health, security (DevSecOps), and common middleware.
This is the extracted "core" service from the monorepo backend.
It runs on port 8000 and uses the `core` schema in PostgreSQL.
"""
import os
import logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
# ---------------------------------------------------------------------------
# Router imports (shared APIs only)
# ---------------------------------------------------------------------------
from auth_api import router as auth_router
from rbac_api import router as rbac_router
from rbac_teachers_api import router as rbac_teachers_router
from notification_api import router as notification_router
from email_template_api import (
router as email_template_router,
versions_router as email_template_versions_router,
)
from system_api import router as system_router
from security_api import router as security_router
# ---------------------------------------------------------------------------
# Middleware imports
# ---------------------------------------------------------------------------
from middleware import (
RequestIDMiddleware,
SecurityHeadersMiddleware,
RateLimiterMiddleware,
PIIRedactor,
InputGateMiddleware,
)
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger("backend-core")
# ---------------------------------------------------------------------------
# Application
# ---------------------------------------------------------------------------
app = FastAPI(
title="BreakPilot Core Backend",
description="Shared APIs: Auth, RBAC, Notifications, Email Templates, System, Security",
version="1.0.0",
)
# ---------------------------------------------------------------------------
# CORS
# ---------------------------------------------------------------------------
ALLOWED_ORIGINS = os.getenv("CORS_ORIGINS", "*").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ---------------------------------------------------------------------------
# Custom middleware stack (order matters -- outermost first)
# ---------------------------------------------------------------------------
# 1. Request-ID (outermost so every response has it)
app.add_middleware(RequestIDMiddleware)
# 2. Security headers
app.add_middleware(SecurityHeadersMiddleware)
# 3. Input gate (body-size / content-type validation)
app.add_middleware(InputGateMiddleware)
# 4. Rate limiter (Valkey-backed)
VALKEY_URL = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0"))
app.add_middleware(RateLimiterMiddleware, valkey_url=VALKEY_URL)
# ---------------------------------------------------------------------------
# Routers
# ---------------------------------------------------------------------------
# Auth (proxy to consent-service)
app.include_router(auth_router, prefix="/api")
# RBAC (role / assignment / custom-role management)
app.include_router(rbac_router, prefix="/api")
# RBAC Teachers (teacher CRUD, listing, roles per teacher)
app.include_router(rbac_teachers_router, prefix="/api")
# Notifications (proxy to consent-service)
app.include_router(notification_router, prefix="/api")
# Email templates (proxy to consent-service)
app.include_router(email_template_router) # already has /api/consent/admin/email-templates prefix
app.include_router(email_template_versions_router) # already has /api/consent/admin/email-template-versions prefix
# System (health, local-ip)
app.include_router(system_router) # already has paths defined in router
# Security / DevSecOps dashboard
app.include_router(security_router, prefix="/api")
# ---------------------------------------------------------------------------
# Startup / Shutdown events
# ---------------------------------------------------------------------------
@app.on_event("startup")
async def on_startup():
logger.info("backend-core starting up")
# Ensure DATABASE_URL uses search_path=core,public
db_url = os.getenv("DATABASE_URL", "")
if db_url and "search_path" not in db_url:
separator = "&" if "?" in db_url else "?"
new_url = f"{db_url}{separator}search_path=core,public"
os.environ["DATABASE_URL"] = new_url
logger.info("DATABASE_URL updated with search_path=core,public")
elif "search_path" in db_url:
logger.info("DATABASE_URL already contains search_path")
else:
logger.warning("DATABASE_URL is not set -- database features will fail")
@app.on_event("shutdown")
async def on_shutdown():
logger.info("backend-core shutting down")
# ---------------------------------------------------------------------------
# Entrypoint (for `python main.py` during development)
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=int(os.getenv("PORT", "8000")),
reload=os.getenv("ENVIRONMENT", "development") == "development",
)