import logging import os from datetime import datetime from fastapi import Depends, FastAPI from fastapi.middleware.cors import CORSMiddleware from starlette.requests import Request from app.services import bank_scraper from app.workers.celery_tasks import load_transactions, load_all_transactions from app.models.user import User, OAuthAccount from app.services.user_service import current_active_verified_user from app.api.auth import router as auth_router from app.api.csas import router as csas_router from app.api.categories import router as categories_router from app.api.transactions import router as transactions_router from app.services.user_service import auth_backend, current_active_verified_user, fastapi_users, get_oauth_provider, UserManager, get_jwt_strategy from app.core.security import extract_bearer_token, is_token_revoked, decode_and_verify_jwt from app.services.user_service import SECRET from fastapi import FastAPI import sentry_sdk from fastapi_users.db import SQLAlchemyUserDatabase from app.core.db import async_session_maker sentry_sdk.init( dsn=os.getenv("SENTRY_DSN"), send_default_pii=True, ) fastApi = FastAPI() app = fastApi # CORS for frontend dev server fastApi.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:5173", "http://127.0.0.1:5173", os.getenv("FRONTEND_DOMAIN_SCHEME", "") ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) fastApi.include_router(auth_router) fastApi.include_router(categories_router) fastApi.include_router(transactions_router) logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s %(message)s') @fastApi.middleware("http") async def auth_guard(request: Request, call_next): # Enforce revoked/expired JWTs are rejected globally token = extract_bearer_token(request) if token: # Deny if token is revoked if is_token_revoked(token): from fastapi import Response, status as _status return Response(status_code=_status.HTTP_401_UNAUTHORIZED) # Deny if token is expired or invalid try: decode_and_verify_jwt(token, SECRET) except Exception: from fastapi import Response, status as _status return Response(status_code=_status.HTTP_401_UNAUTHORIZED) return await call_next(request) @fastApi.middleware("http") async def log_traffic(request: Request, call_next): start_time = datetime.now() response = await call_next(request) process_time = (datetime.now() - start_time).total_seconds() client_host = request.client.host log_params = { "request_method": request.method, "request_url": str(request.url), "request_size": request.headers.get("content-length"), "request_headers": dict(request.headers), "response_status": response.status_code, "response_size": response.headers.get("content-length"), "response_headers": dict(response.headers), "process_time": process_time, "client_host": client_host } logging.info(str(log_params)) return response fastApi.include_router( fastapi_users.get_oauth_router( get_oauth_provider("MojeID"), auth_backend, "SECRET", associate_by_email=True, redirect_url=os.getenv("FRONTEND_DOMAIN_SCHEME", "http://localhost:3000") + "/auth/mojeid/callback", ), prefix="/auth/mojeid", tags=["auth"], ) fastApi.include_router( fastapi_users.get_oauth_router( get_oauth_provider("BankID"), auth_backend, "SECRET", associate_by_email=True, redirect_url=os.getenv("FRONTEND_DOMAIN_SCHEME", "http://localhost:3000") + "/auth/bankid/callback", ), prefix="/auth/bankid", tags=["auth"], ) fastApi.include_router(csas_router) # Liveness/root endpoint @fastApi.get("/", include_in_schema=False) async def root(): return {"status": "ok"} @fastApi.get("/authenticated-route") async def authenticated_route(user: User = Depends(current_active_verified_user)): return {"message": f"Hello {user.email}!"} @fastApi.get("/sentry-debug") async def trigger_error(): division_by_zero = 1 / 0 @fastApi.get("/debug/scrape/csas/all", tags=["debug"]) async def debug_scrape_csas_all(): logging.info("[Debug] Queueing CSAS scrape for all users via HTTP endpoint (Celery)") task = load_all_transactions.delay() return {"status": "queued", "action": "csas_scrape_all", "task_id": getattr(task, 'id', None)} @fastApi.post("/debug/scrape/csas/{user_id}", tags=["debug"]) async def debug_scrape_csas_user(user_id: str, user: User = Depends(current_active_verified_user)): logging.info("[Debug] Queueing CSAS scrape for single user via HTTP endpoint (Celery) | user_id=%s", user_id) task = load_transactions.delay(user_id) return {"status": "queued", "action": "csas_scrape_single", "user_id": user_id, "task_id": getattr(task, 'id', None)}