Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6f46fd8
refactor: Split models into domain-specific modules
Jenish-1235 Dec 13, 2025
232d51e
add auth implementation plan
Jenish-1235 Dec 13, 2025
5e17cb7
fix session models
Jenish-1235 Dec 14, 2025
ee816ae
fix interaction and protocol models
Jenish-1235 Dec 14, 2025
b4bb44a
refactor: Update user and session models for improved IP address hand…
Jenish-1235 Dec 14, 2025
32365b4
implement auth, exceptions and telemetry for core module
Jenish-1235 Dec 14, 2025
91c8930
feat(infra): add Docker Compose infrastructure and database schema
Jenish-1235 Dec 14, 2025
db9d884
feat(config): expand Settings with all configuration options
Jenish-1235 Dec 14, 2025
38524c3
feat(config): add Azure App Configuration integration with retry
Jenish-1235 Dec 14, 2025
5906d68
feat(core): add application state management and connection protocols
Jenish-1235 Dec 14, 2025
e2e6855
feat(main): implement production-ready lifespan with startup sequence
Jenish-1235 Dec 14, 2025
2f57ddf
feat(api): add production health endpoints
Jenish-1235 Dec 14, 2025
e07dc31
test(startup): add tests for config loader, app state, and health
Jenish-1235 Dec 14, 2025
8b6aceb
refactor(tests): reorganize core module tests into tests/core/
Jenish-1235 Dec 14, 2025
4aaae9e
update docker compose health test
Jenish-1235 Dec 14, 2025
5f53bf2
feat(dependencies): add azure-appconfiguration package and update req…
Jenish-1235 Dec 14, 2025
8959d62
fix(tests): format test_telemetry.py to pass ruff format check
Jenish-1235 Dec 14, 2025
384ca37
add __init__.py to tests
Jenish-1235 Dec 15, 2025
ab81696
fix tests
Jenish-1235 Dec 17, 2025
3eaae46
fix telemetry warnings
Jenish-1235 Dec 17, 2025
7ff9901
(docs) : update core module implementation documentation
Jenish-1235 Dec 17, 2025
ed3bf51
Merge pull request #1 from nerospatial/core
Jenish-1235 Dec 17, 2025
aea9ce4
Merge branch 'dev' of github.com:nerospatial/nerospatial-backend into…
Jenish-1235 Dec 18, 2025
aa33410
feat: Enhance Redis and session management capabilities
Jenish-1235 Dec 19, 2025
ad6e676
refactor: streamline string formatting and method signatures across m…
Jenish-1235 Dec 19, 2025
36baebb
redis dependency fixed
Jenish-1235 Dec 19, 2025
c806a6f
ci : fix python version
Jenish-1235 Dec 19, 2025
47da45b
docker : update python runtime version
Jenish-1235 Dec 19, 2025
db1d9d0
tests : fix all the integration tests
Jenish-1235 Dec 19, 2025
2b4dcf3
ci : update tests ci to use redis
Jenish-1235 Dec 19, 2025
8c44fc5
refactor(redis): optimize session management with Hash-based mappings…
Jenish-1235 Dec 19, 2025
9e94622
update .gitignore
Jenish-1235 Dec 19, 2025
4246a24
fix server startup issues, env loading and added postgres service to …
Jenish-1235 Dec 19, 2025
4d41a4c
fix : remove redundant health endpoint in gateway router, fix None ch…
Jenish-1235 Dec 19, 2025
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
91 changes: 91 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# =============================================================================
# NeroSpatial Backend - Environment Configuration Template
# =============================================================================
# Copy this file to .env and fill in your values:
# cp .env.example .env
#
# IMPORTANT: Never commit .env to Git!
# =============================================================================

# -----------------------------------------------------------------------------
# Application
# -----------------------------------------------------------------------------
APP_NAME=NeroSpatial Backend
APP_VERSION=0.1.0
DEBUG=true
ENVIRONMENT=development
LOG_LEVEL=INFO

# -----------------------------------------------------------------------------
# Server
# -----------------------------------------------------------------------------
HOST=0.0.0.0
PORT=8000

# -----------------------------------------------------------------------------
# Azure Key Vault (REQUIRED for production/staging)
# All secrets are loaded from Key Vault. Only these credentials go in .env
# -----------------------------------------------------------------------------
AZURE_KEY_VAULT_URL=https://your-vault-name.vault.azure.net/
AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_SECRET=your-client-secret-here

# -----------------------------------------------------------------------------
# Azure App Configuration (REQUIRED for production/staging)
# Single source of truth for all non-secret configuration
# -----------------------------------------------------------------------------
AZURE_APP_CONFIG_URL=https://your-config-name.azconfig.io

# -----------------------------------------------------------------------------
# PostgreSQL (URLs only - password from Key Vault)
# These are defaults/overrides. Production uses App Configuration.
# -----------------------------------------------------------------------------
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=nerospatial
POSTGRES_USER=nerospatial
# POSTGRES_PASSWORD - Loaded from Key Vault secret "postgres-password"
POSTGRES_POOL_MIN=5
POSTGRES_POOL_MAX=20

# -----------------------------------------------------------------------------
# Redis (URLs only - password from Key Vault)
# These are defaults/overrides. Production uses App Configuration.
# -----------------------------------------------------------------------------
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
# REDIS_PASSWORD - Loaded from Key Vault secret "redis-password"

# -----------------------------------------------------------------------------
# JWT Authentication
# Keys loaded from Key Vault. These are defaults/overrides.
# -----------------------------------------------------------------------------
JWT_ALGORITHM=RS256
JWT_ACCESS_TOKEN_TTL=900
JWT_REFRESH_TOKEN_TTL=604800
JWT_CACHE_TTL=300
# JWT_PRIVATE_KEY - Loaded from Key Vault secret "jwt-private-key"
# JWT_PUBLIC_KEY - Loaded from Key Vault secret "jwt-public-key"

# -----------------------------------------------------------------------------
# OpenTelemetry
# -----------------------------------------------------------------------------
OTEL_ENDPOINT=http://localhost:4317
OTEL_ENABLE_TRACING=true
OTEL_ENABLE_METRICS=true

# -----------------------------------------------------------------------------
# Startup Configuration
# -----------------------------------------------------------------------------
STARTUP_TIMEOUT_SECONDS=30
STARTUP_RETRY_ATTEMPTS=3
STARTUP_RETRY_DELAY_SECONDS=2

# -----------------------------------------------------------------------------
# Google OAuth (Skip for now - Phase 2)
# -----------------------------------------------------------------------------
# GOOGLE_CLIENT_ID - Loaded from Key Vault (when implemented)
# GOOGLE_CLIENT_SECRET - Loaded from Key Vault (when implemented)
# GOOGLE_REDIRECT_URI=http://localhost:8000/auth/callback
15 changes: 13 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
enable-cache: true

- name: Set up Python
run: uv python install 3.11
run: uv python install 3.13.7

- name: Install dependencies
run: uv sync --extra dev
Expand All @@ -47,6 +47,17 @@ jobs:
runs-on: ubuntu-latest
needs: lint

services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -57,7 +68,7 @@ jobs:
enable-cache: true

- name: Set up Python
run: uv python install 3.11
run: uv python install 3.13.7

- name: Install dependencies
run: uv sync --extra dev
Expand Down
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ENV/
.env.local
.env.*.local
.env
keys/

# OS
.DS_Store
Expand All @@ -47,5 +48,5 @@ Thumbs.db

# UV
.uv/
**.mdc**
.cursor/
.cursor/
*.mdc*
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Multi-stage build for optimized production image

# Stage 1: Build stage with uv for fast dependency installation
FROM python:3.11-slim AS builder
FROM python:3.13.7-slim AS builder

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
Expand All @@ -28,7 +28,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \


# Stage 2: Production runtime
FROM python:3.11-slim AS runtime
FROM python:3.13.7-slim AS runtime

# Create non-root user for security
RUN groupadd --gid 1000 appgroup && \
Expand Down
81 changes: 76 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,86 @@ uv run python main.py

## Endpoints

- `GET /health` - Health check endpoint
- `GET /helloword` - Hello world endpoint
### Health Endpoints

- `GET /health` - Detailed health check with dependency status
- `GET /ready` - Readiness probe (Kubernetes/load balancer)
- `GET /live` - Liveness probe (Kubernetes)

### Application Endpoints

- `GET /helloworld` - Hello world endpoint

## Infrastructure Setup

### Local Development

Start infrastructure services (PostgreSQL, Redis, Jaeger) using Docker Compose:

```bash
docker compose -f docker-compose.infra.yml up -d
```

This will start:

- PostgreSQL on port 5432
- Redis on port 6379
- Jaeger (tracing) on ports 4317 (OTLP) and 16686 (UI)

### Database Initialization

The database schema is automatically initialized when the PostgreSQL container starts for the first time via `scripts/init-db.sql`.

### JWT Key Generation

Generate JWT RS256 keys for authentication:

```bash
./scripts/generate-keys.sh
```

This creates `keys/private.pem` and `keys/public.pem`. Store these in Azure Key Vault for production.

### Azure Key Vault Setup

Set up Azure Key Vault and upload secrets:

```bash
./scripts/setup-keyvault.sh
```

This script will:

1. Create Key Vault (if not exists)
2. Create Service Principal with proper permissions
3. Upload JWT keys and other secrets
4. Output credentials for your `.env` file

## Configuration

Configuration is managed through:
Configuration is managed through a hierarchy:

1. **Azure App Configuration** (single source of truth for production/staging)

- Non-secret settings (URLs, ports, feature flags)
- Environment-specific configuration using labels

2. **Azure Key Vault** (secrets)

- Passwords, JWT keys, OAuth credentials
- Referenced from App Configuration

3. **`.env` file** (bootstrap and development fallback)
- Azure credentials to access App Config and Key Vault
- Local overrides for development
- Minimal - only what's needed to bootstrap

### Environment Validation

- **Production/Staging**: Requires Azure App Config and Key Vault URLs. Server will not start without them.
- **Development**: Optional Azure services. Falls back to `.env` file if not configured.

- `config.py` - Configuration module using Pydantic Settings
- `.env` - Environment variables for secrets (will be replaced by Azure Key Vault in the future)
See `.env.example` for all available configuration options.

## Pre-commit Hooks

Expand Down
1 change: 1 addition & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""API routes package."""
91 changes: 91 additions & 0 deletions api/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
Health check endpoints for production monitoring.

Provides /health, /ready, and /live endpoints for Kubernetes and load balancers.
"""

from datetime import UTC, datetime

from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse

from core.app_state import AppState

router = APIRouter(tags=["Health"])


@router.get("/health")
async def health_check(request: Request) -> JSONResponse:
"""
Detailed health check with dependency status.

Returns:
- status: overall health status
- checks: individual service checks
- metadata: app info and uptime
"""
state: AppState = request.app.state.app_state

checks = {
"database": state.db_pool is not None and await state.db_pool.ping() if hasattr(state.db_pool, "ping") else state.db_pool is not None,
"redis": await state.redis_client.ping() if state.redis_client else False,
"key_vault": state.key_vault.is_available() if state.key_vault else False,
}

all_healthy = all(checks.values())
uptime = (datetime.now(UTC) - state.started_at).total_seconds()

return JSONResponse(
status_code=200 if all_healthy else 503,
content={
"status": "healthy" if all_healthy else "unhealthy",
"checks": checks,
"metadata": {
"service": state.settings.app_name,
"version": state.settings.app_version,
"environment": state.settings.environment,
"uptime_seconds": uptime,
},
},
)


@router.get("/ready")
async def readiness_check(request: Request) -> JSONResponse:
"""
Readiness probe for Kubernetes/load balancers.

Returns 200 only when app is fully initialized and ready.
Used by load balancers to know when to send traffic.
"""
state: AppState = request.app.state.app_state

if not state.is_ready:
return JSONResponse(
status_code=503,
content={"status": "not_ready", "errors": state.startup_errors},
)

# Verify critical dependencies
db_ok = state.db_pool is not None and (await state.db_pool.ping() if hasattr(state.db_pool, "ping") else True)
redis_ok = state.redis_client is not None and await state.redis_client.ping()

if not (db_ok and redis_ok):
return JSONResponse(
status_code=503,
content={"status": "not_ready", "database": db_ok, "redis": redis_ok},
)

return JSONResponse(content={"status": "ready"})


@router.get("/live")
async def liveness_check() -> JSONResponse:
"""
Liveness probe for Kubernetes.

Returns 200 if the process is alive.
Does NOT check dependencies - only process health.
Used by Kubernetes to know when to restart container.
"""
return JSONResponse(content={"status": "alive"})
Loading
Loading