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
- 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>
147 lines
5.3 KiB
Python
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",
|
|
)
|