Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/auth/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from ..config import get_settings


# Configuración del esquema de seguridad
security = HTTPBearer(auto_error=False)

Expand Down
47 changes: 33 additions & 14 deletions app/backoffice/router.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
"""
Backoffice dashboard router.

Provides administrative HTML endpoints for NeuroBank.
This module must only expose an APIRouter instance named `router`.
"""

from fastapi import APIRouter
from fastapi.responses import HTMLResponse


router = APIRouter(
prefix="/backoffice",
tags=["Backoffice Dashboard"],
tags=["Backoffice"],
)


@router.get("/", response_class=HTMLResponse)
async def backoffice_home():
return """
<html>
<head>
<title>NeuroBank Backoffice</title>
</head>
<body>
<h1>NeuroBank Admin Dashboard</h1>
<p>Backoffice is up and running.</p>
</body>
</html>
"""
@router.get(
"/",
response_class=HTMLResponse,
name="backoffice_home",
summary="Backoffice dashboard home",
)
async def backoffice_home() -> HTMLResponse:
"""Render the main backoffice dashboard page."""
return HTMLResponse(
content="""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>NeuroBank Backoffice</title>
</head>
<body>
<h1>NeuroBank Admin Dashboard</h1>
<p>Backoffice is up and running.</p>
</body>
</html>
""",
status_code=200,
)
5 changes: 3 additions & 2 deletions app/backoffice/router_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
Enterprise-grade admin panel para impresionar reclutadores bancarios
"""

import random
import uuid
from datetime import datetime, timedelta
from decimal import Decimal
from enum import Enum
import random
from typing import Any, Dict, List
import uuid

from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field


# Router configuration
router = APIRouter(prefix="/backoffice", tags=["Backoffice Dashboard"])
templates = Jinja2Templates(directory="app/backoffice/templates")
Expand Down
49 changes: 34 additions & 15 deletions app/config.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,71 @@
"""
Application configuration and settings.

This module provides a centralized configuration management system using Pydantic.
It MUST NOT import FastAPI, routers, or app.main to avoid circular dependencies.
Centralized configuration management using Pydantic Settings.
This module MUST NOT import FastAPI, routers, or app.main
to avoid circular dependencies.
"""

from functools import lru_cache
from typing import List

from pydantic_settings import BaseSettings
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
"""Application settings loaded from environment variables."""

# ------------------------------------------------------------------
# Application
# ------------------------------------------------------------------
app_name: str = "NeuroBank FastAPI Toolkit"
app_version: str = "1.0.0"
environment: str = "development"
debug: bool = False

# ------------------------------------------------------------------
# Security
api_key: str = ""
secret_key: str = ""
# ------------------------------------------------------------------
api_key: str = Field(default="", repr=False)
secret_key: str = Field(default="", repr=False)

# ------------------------------------------------------------------
# CORS
cors_origins: List[str] = ["*"]
# ------------------------------------------------------------------
cors_origins: List[str] = Field(default_factory=lambda: ["*"])

# ------------------------------------------------------------------
# Logging
# ------------------------------------------------------------------
log_level: str = "INFO"

class Config:
env_file = ".env"
case_sensitive = False
extra = "ignore" # Ignore extra environment variables
# ------------------------------------------------------------------
# Pydantic Settings config (v2 style)
# ------------------------------------------------------------------
model_config = SettingsConfigDict(
env_file=".env",
case_sensitive=False,
extra="ignore",
)

@property
def is_production(self) -> bool:
"""Check if running in production environment."""
"""Return True if running in production environment."""
return self.environment.lower() == "production"

@property
def security_enabled(self) -> bool:
"""Check whether security credentials are configured."""
return bool(self.api_key and self.secret_key)


@lru_cache()
@lru_cache
def get_settings() -> Settings:
"""
Get cached settings instance.
Return cached Settings instance.

Uses lru_cache to ensure settings are loaded once and reused.
This avoids repeated environment variable reads.
Ensures environment variables are read once
and prevents configuration drift.
"""
return Settings()
49 changes: 27 additions & 22 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@
Dependencies flow: main.py -> config.py, routers (NEVER the reverse)
"""

import logging
from contextlib import asynccontextmanager
import logging
from typing import Dict

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from app.backoffice.router import router as backoffice_router
from app.config import get_settings
from app.routers import operator
from app.utils.logging import setup_logging

Expand All @@ -29,35 +30,29 @@ async def lifespan(app: FastAPI):
Application lifespan manager.

Configures logging on startup and ensures clean shutdown.
Settings are imported lazily inside the function to avoid circular imports.
"""
# Lazy import to avoid circular dependency
from app.config import get_settings

settings = get_settings()

# Startup
try:
setup_logging() # NO arguments - CodeQL requirement
setup_logging() # MUST be zero-arg (CodeQL)
logging.info("Logging configured successfully")
logging.info(f"Starting {settings.app_name} v{settings.app_version}")
logging.info(f"Environment: {settings.environment}")
except Exception as exc:
logging.info("Starting %s v%s", settings.app_name, settings.app_version)
logging.info("Environment: %s", settings.environment)
except Exception as exc: # pragma: no cover - defensive fallback
logging.basicConfig(level=logging.INFO)
logging.error("Failed to configure logging, using basic config", exc_info=exc)

yield

# Shutdown
logging.info("Application shutdown completed")


# Lazy settings access
from app.config import get_settings
# -------------------------------------------------------------------
# App initialization
# -------------------------------------------------------------------

settings = get_settings()

# Create FastAPI app
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
Expand All @@ -67,7 +62,10 @@ async def lifespan(app: FastAPI):
redoc_url="/redoc",
)

# Configure CORS middleware
# -------------------------------------------------------------------
# Middleware
# -------------------------------------------------------------------

app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
Expand All @@ -76,10 +74,17 @@ async def lifespan(app: FastAPI):
allow_headers=["*"],
)

# Include routers
app.include_router(operator.router, prefix="/api", tags=["API"])
# -------------------------------------------------------------------
# Routers
# -------------------------------------------------------------------

app.include_router(operator.router)
app.include_router(backoffice_router)

# -------------------------------------------------------------------
# Endpoints
# -------------------------------------------------------------------


@app.get(
"/",
Expand All @@ -98,14 +103,14 @@ async def root() -> Dict[str, object]:
},
"endpoints": {
"health_check": "/health",
"operator_operations": "/api",
"api": "/api",
"backoffice": "/backoffice",
},
"features": [
"🏦 Banking Operations",
"🔐 API Key Authentication",
"📊 Admin Backoffice Dashboard",
"☁️ Cloud & Serverless Ready",
"Banking Operations API",
"API Key Authentication",
"Admin Backoffice Dashboard",
"Cloud & Serverless Ready",
],
}

Expand Down
Loading