Files
breakpilot-core/backend-core/main.py
Benjamin Boenisch b7d21daa24
All checks were successful
CI / test-bqas (push) Successful in 32s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 46s
CI / test-python-voice (push) Successful in 38s
feat: Add DevSecOps tools, Woodpecker proxy, Vault persistent storage, pitch-deck annex slides
- Install Gitleaks, Trivy, Grype, Syft, Semgrep, Bandit in backend-core Dockerfile
- Add Woodpecker SQLite proxy API (fallback without API token)
- Mount woodpecker_data volume read-only to backend-core
- Add backend proxy fallback in admin-core Woodpecker route
- Add Vault file-based persistent storage (config.hcl, init-vault.sh)
- Auto-init, unseal and root-token persistence for Vault
- Add 6 pitch-deck annex slides (Assumptions, Architecture, GTM, Regulatory, Engineering, AI Pipeline)
- Dynamic margin/amortization KPIs in BusinessModelSlide
- Market sources modal with citations in MarketSlide
- Redesign nginx landing page to 3-column layout (Lehrer/Compliance/Core)
- Extend MkDocs nav with Services and SDK documentation sections
- Add SDK Protection architecture doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 15:42:43 +01:00

147 lines
5.3 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 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
from woodpecker_proxy_api import router as woodpecker_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 (teacher / role management)
app.include_router(rbac_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")
app.include_router(woodpecker_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",
)