Skip to content
Open
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ npm-debug.log*
/.env*.local
/.env.development
/.env.production
backend/.env

# typescript
*.tsbuildinfo
Expand All @@ -32,6 +33,11 @@ npm-debug.log*
# Build Dir
/out

# Playwright test artifacts
/test-results
/tests/screenshots

# python
venv
__pycache__
playwright-report/
57 changes: 57 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# BRC Analytics Backend

Backend infrastructure for BRC Analytics, organized as a collection of services.

## Structure

```
backend/
├── docker-compose.yml # Service orchestration
├── nginx.conf # Reverse proxy configuration
├── docker-build.sh # Build script with version sync
└── api/ # FastAPI REST API service
├── app/ # Application code
├── Dockerfile # API service container
├── pyproject.toml # Python dependencies
└── README.md # API-specific documentation
```

## Quick Start

```bash
cd backend

# Create environment file
cp api/.env.example api/.env

# Build with version from package.json
./docker-build.sh

# Start all services
docker compose up -d

# Check health
curl http://localhost:8080/api/v1/health

# View logs
docker compose logs -f

# Stop services
docker compose down
```

## Services

- **nginx** (port 8080): Reverse proxy, public-facing entry point
- **api**: FastAPI REST API (internal, accessed via nginx)
- **redis**: Cache layer (internal)

See `api/README.md` for API-specific documentation.

## Port Configuration

For security, only nginx is exposed externally. Backend services (API, Redis) are only accessible within the Docker network.

- External access: `http://localhost:8080` → nginx → routes to services
- Direct backend access (dev only): `docker compose exec backend curl http://localhost:8000`
35 changes: 35 additions & 0 deletions backend/api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Python
__pycache__
*.pyc
*.pyo
*.pyd
.Python
.pytest_cache
.mypy_cache

# Virtual environments
.env
.venv
env/
venv/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Git
.git
.gitignore

# Documentation
*.md
docs/

# Tests
tests/
14 changes: 14 additions & 0 deletions backend/api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Redis Configuration
REDIS_URL=redis://redis:6379/0

# Database Configuration (for future use)
DATABASE_URL=postgresql://user:pass@localhost/dbname

# Application Configuration
CORS_ORIGINS=http://localhost:3000,http://localhost
LOG_LEVEL=INFO
ENVIRONMENT=development

# Rate Limiting
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=60
50 changes: 50 additions & 0 deletions backend/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Multi-stage build for uv-based Python application
FROM python:3.12-slim as builder

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

WORKDIR /app

# Copy dependency files
COPY pyproject.toml uv.lock ./

# Install dependencies into .venv
RUN uv sync --frozen --no-install-project

# Production stage
FROM python:3.12-slim as runtime

# Accept version as build argument
ARG APP_VERSION=0.15.0
ENV APP_VERSION=${APP_VERSION}

# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*

# Copy uv for runtime
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

# Create non-root user
RUN useradd --create-home --shell /bin/bash app

WORKDIR /app

# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv

# Add venv to PATH
ENV PATH="/app/.venv/bin:$PATH"

# Copy application code
COPY . .

# Change ownership
RUN chown -R app:app /app
USER app

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
107 changes: 107 additions & 0 deletions backend/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# BRC Analytics API Service

FastAPI REST API service for BRC Analytics.

## Features

- FastAPI REST API
- Redis caching with TTL support
- Health check endpoints
- Docker deployment with nginx reverse proxy
- uv for dependency management

## Quick Start

### Development (Local)

```bash
cd backend/api
uv sync
uv run uvicorn app.main:app --reload
```

API documentation: http://localhost:8000/api/docs

### Production (Docker)

Docker Compose orchestration is managed from the parent `/backend` directory.

```bash
cd backend

# Create environment file
cp api/.env.example api/.env
# Edit api/.env if needed (defaults work for local development)

# Build with version from package.json
./docker-build.sh

# Start all services (nginx + api + redis)
docker compose up -d

# Check service health
curl http://localhost:8080/api/v1/health

# View logs
docker compose logs -f backend

# Rebuild after code changes
docker compose up -d --build

# Stop all services
docker compose down
```

Services:

- nginx: http://localhost:8080 (reverse proxy, public-facing)
- API service: internal only, accessible via nginx
- API docs: http://localhost:8080/api/docs
- redis: internal only

## API Endpoints

### Health & Monitoring

- `GET /api/v1/health` - Overall service health status
- `GET /api/v1/cache/health` - Redis cache connectivity check
- `GET /api/v1/version` - API version and environment information

### Documentation

- `GET /api/docs` - Interactive Swagger UI
- `GET /api/redoc` - ReDoc API documentation

## Configuration

Environment variables (see `.env.example`):

```bash
# Redis
REDIS_URL=redis://localhost:6379/0

# Application
CORS_ORIGINS=http://localhost:3000,http://localhost
LOG_LEVEL=INFO
```

## Testing

```bash
# Run e2e tests
npm run test:e2e

# Or with Playwright directly
npx playwright test tests/e2e/03-api-health.spec.ts
```

## Architecture

```
nginx (port 80)
├── /api/* → FastAPI backend (port 8000)
└── /* → Next.js static files

FastAPI backend
└── Redis cache (port 6379)
```
1 change: 1 addition & 0 deletions backend/api/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# BRC Analytics Backend
1 change: 1 addition & 0 deletions backend/api/app/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# API package
1 change: 1 addition & 0 deletions backend/api/app/api/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# API v1 package
31 changes: 31 additions & 0 deletions backend/api/app/api/v1/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from fastapi import APIRouter, Depends, HTTPException

from app.core.cache import CacheService
from app.core.dependencies import get_cache_service

router = APIRouter()


@router.get("/health")
async def cache_health(cache: CacheService = Depends(get_cache_service)):
"""Check if cache service is healthy"""
try:
# Try to set and get a test value
test_key = "health_check"
test_value = "ok"

await cache.set(test_key, test_value, ttl=60)
result = await cache.get(test_key)
await cache.delete(test_key)

if result == test_value:
return {"status": "healthy", "cache": "connected"}
else:
raise HTTPException(
status_code=503, detail="Cache service not responding correctly"
)

except Exception as e:
raise HTTPException(
status_code=503, detail=f"Cache service unhealthy: {str(e)}"
)
19 changes: 19 additions & 0 deletions backend/api/app/api/v1/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from datetime import datetime

from fastapi import APIRouter

from app.core.config import get_settings

router = APIRouter()
settings = get_settings()


@router.get("/health")
async def health_check():
"""Health check endpoint for monitoring system status"""
return {
"status": "healthy",
"version": settings.APP_VERSION,
"timestamp": datetime.utcnow().isoformat(),
"service": "BRC Analytics API",
}
16 changes: 16 additions & 0 deletions backend/api/app/api/v1/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from fastapi import APIRouter

from app.core.config import get_settings

router = APIRouter()
settings = get_settings()


@router.get("")
async def get_version():
"""Get API version and build information"""
return {
"version": settings.APP_VERSION,
"environment": settings.ENVIRONMENT,
"service": "BRC Analytics API",
}
1 change: 1 addition & 0 deletions backend/api/app/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Core package
Loading
Loading