Spaces:
Runtime error
Runtime error
| from fastapi import FastAPI, Depends, HTTPException, Request, status, Form | |
| from fastapi.responses import HTMLResponse, FileResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from prometheus_client import Counter, generate_latest, CONTENT_TYPE_LATEST | |
| from sqlalchemy.orm import Session | |
| from loguru import logger | |
| from starlette.middleware.base import BaseHTTPMiddleware | |
| from starlette.responses import Response | |
| from starlette.templating import Jinja2Templates | |
| from .config import Settings | |
| from .database import Base, engine, get_db | |
| from .models import * | |
| from .schemas import * | |
| from .security import * | |
| from .tasks import celery_app, generate_pptx, generate_docx | |
| from .telemetry import setup_tracing | |
| import os | |
| settings = Settings() | |
| # Observability | |
| setup_tracing() | |
| # DB init | |
| Base.metadata.create_all(bind=engine) | |
| # FastAPI app | |
| app = FastAPI(title="GrowthOps OS", version="0.1.0") | |
| # CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| API_CALLS = Counter("growthops_api_calls", "Total API calls", ["path", "method", "status"]) | |
| templates = Jinja2Templates(directory="templates") | |
| class AuditMiddleware(BaseHTTPMiddleware): | |
| async def dispatch(self, request: Request, call_next): | |
| response = None | |
| error_text = None | |
| try: | |
| response = await call_next(request) | |
| return response | |
| except Exception as e: | |
| error_text = str(e) | |
| logger.exception("Unhandled error") | |
| raise | |
| finally: | |
| try: | |
| tenant_id = None | |
| user_id = None | |
| auth = request.headers.get("authorization", "") | |
| if auth.startswith("Bearer "): | |
| token = auth.split(" ", 1)[1] | |
| try: | |
| payload = parse_token(token) | |
| db = next(get_db()) | |
| user = db.query(User).filter(User.email == payload.get("sub")).first() | |
| if user: | |
| tenant_id = user.tenant_id | |
| user_id = user.id | |
| except Exception: | |
| pass | |
| db = next(get_db()) | |
| al = AuditLog( | |
| tenant_id=tenant_id, | |
| user_id=user_id, | |
| path=request.url.path, | |
| method=request.method, | |
| status_code=getattr(response, "status_code", 500), | |
| meta={"client": request.client.host if request.client else None} | |
| ) | |
| db.add(al) | |
| if error_text: | |
| err = ErrorLog(tenant_id=tenant_id, user_id=user_id, path=request.url.path, error=error_text) | |
| db.add(err) | |
| db.commit() | |
| API_CALLS.labels(path=request.url.path, method=request.method, status=str(getattr(response, "status_code", 500))).inc() | |
| except Exception as e: | |
| logger.error(f"audit failed: {e}") | |
| app.add_middleware(AuditMiddleware) | |
| def healthz(): | |
| return {"ok": True} | |
| def metrics(): | |
| return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST) | |
| # ------------------ Auth ------------------ | |
| def bootstrap_admin(body: dict, db: Session = Depends(get_db)): | |
| email = body.get("email") | |
| password = body.get("password") | |
| if not email or not password: | |
| raise HTTPException(400, "email/password required") | |
| user = db.query(User).filter(User.email == email).first() | |
| if user: | |
| return {"message": "exists"} | |
| tenant = db.query(Tenant).filter(Tenant.name == "default").first() | |
| if not tenant: | |
| tenant = Tenant(name="default") | |
| db.add(tenant) | |
| db.commit() | |
| db.refresh(tenant) | |
| u = User(email=email, password_hash=hash_password(password), tenant_id=tenant.id, is_tenant_admin=True) | |
| db.add(u) | |
| db.commit() | |
| return {"message": "admin created"} | |
| def register(user: UserCreate, current: User = Depends(require_tenant_admin), db: Session = Depends(get_db)): | |
| if db.query(User).filter(User.email == user.email).first(): | |
| raise HTTPException(400, "Email already exists") | |
| u = User(email=user.email, password_hash=hash_password(user.password), tenant_id=user.tenant_id, is_tenant_admin=user.is_tenant_admin) | |
| db.add(u) | |
| db.commit() | |
| db.refresh(u) | |
| return u | |
| def login(req: LoginRequest, db: Session = Depends(get_db)): | |
| user = db.query(User).filter(User.email == req.email).first() | |
| if not user or not verify_password(req.password, user.password_hash): | |
| raise HTTPException(401, "Invalid credentials") | |
| token = create_access_token(sub=user.email, expires_in=3600) | |
| return {"access_token": token, "token_type": "bearer"} | |
| def me(current: User = Depends(get_current_user)): | |
| return current | |
| # ------------------ Tenants / Plans ------------------ | |
| def create_tenant_api(t: TenantCreate, current: User = Depends(require_tenant_admin), db: Session = Depends(get_db)): | |
| if db.query(Tenant).filter(Tenant.name == t.name).first(): | |
| raise HTTPException(400, "Tenant exists") | |
| tenant = Tenant(name=t.name) | |
| db.add(tenant) | |
| db.commit() | |
| db.refresh(tenant) | |
| return tenant | |
| def list_tenants(current: User = Depends(require_tenant_admin), db: Session = Depends(get_db)): | |
| return db.query(Tenant).all() | |
| def create_plan(p: PlanCreate, current: User = Depends(require_tenant_admin), db: Session = Depends(get_db)): | |
| plan = Plan(name=p.name, monthly_quota=p.monthly_quota, features=p.features) | |
| db.add(plan) | |
| db.commit() | |
| return {"message": "ok"} | |
| def create_subscription(s: SubscriptionCreate, current: User = Depends(require_tenant_admin), db: Session = Depends(get_db)): | |
| sub = Subscription(tenant_id=s.tenant_id, plan_id=s.plan_id, active=True) | |