bmc_hub/.github/copilot-instructions.md
2025-12-05 14:22:39 +01:00

7.5 KiB

GitHub Copilot Instructions - BMC Hub

Project Overview

BMC Hub is a central management system for BMC Networks built with FastAPI + PostgreSQL. The architecture is inspired by the OmniSync project (/Users/christianthomas/pakkemodtagelse) but adapted for BMC's specific needs.

Tech Stack: Python 3.13, FastAPI, PostgreSQL 16, Docker, psycopg2

Architecture Principles

Database-First Design

  • PostgreSQL only - no SQLite. Use psycopg2 with connection pooling
  • All DB operations via app/core/database.py::execute_query() helper
  • Parameterized queries (%s placeholders) to prevent SQL injection
  • Use RealDictCursor for dict-like row access
  • Schema defined in migrations/init.sql with versioned migrations

FastAPI Router Pattern

  • Each domain gets a router in app/routers/ (customers, hardware, billing, system)
  • Routers imported and registered in main.py with prefix /api/v1
  • Use Pydantic models from app/models/schemas.py for request/response validation
  • Return model instances directly - FastAPI handles serialization

Configuration Management

  • All settings in app/core/config.py using pydantic_settings.BaseSettings
  • Loads from .env file automatically via Config.env_file = ".env"
  • Access via from app.core.config import settings
  • Never hardcode credentials - always use environment variables

Safety-First Integrations

  • External API integrations (e-conomic) have safety switches: ECONOMIC_READ_ONLY and ECONOMIC_DRY_RUN
  • Always default to true for both switches in new deployments
  • Log all external API calls before execution
  • Provide dry-run mode that logs without executing

Development Workflows

Local Development Setup

cp .env.example .env           # Create local environment
docker-compose up -d           # Start PostgreSQL + API
docker-compose logs -f api     # Watch logs

The local docker-compose.yml mounts source code for live reload:

  • ./app:/app/app:ro - Python code hot-reload
  • ENABLE_RELOAD=true - Uvicorn auto-restart

Production Deployment

Production uses version-tagged GitHub releases via Gitea:

  1. Tag release: git tag v1.2.3 && git push --tags
  2. Update .env with RELEASE_VERSION=v1.2.3
  3. Deploy: docker-compose -f docker-compose.prod.yml up -d --build

The production Dockerfile downloads code from Gitea if RELEASE_VERSION != "latest":

ARG RELEASE_VERSION=latest
ARG GITHUB_TOKEN
ARG GITHUB_REPO=ct/bmc_hub

Key difference from local: Production does NOT mount source code - it's baked into the image from the Git tag.

Database Migrations

  • SQL migrations in migrations/ numbered sequentially
  • init.sql runs on first container startup via Docker entrypoint
  • For schema changes: create migrations/001_feature_name.sql and run manually or via migration script

Adding New Features

  1. Create Pydantic models in app/models/schemas.py (Base, Create, Full schemas)
  2. Add database migration in migrations/XXX_feature.sql
  3. Create router in app/routers/feature.py with CRUD endpoints
  4. Register router in main.py: app.include_router(feature.router, prefix="/api/v1", tags=["Feature"])
  5. Add tests (when test framework exists)

Code Patterns

Database Query Pattern

from app.core.database import execute_query

# Fetch rows
query = "SELECT * FROM customers WHERE id = %s"
result = execute_query(query, (customer_id,))

# Insert/Update (returns affected rows)
query = "INSERT INTO customers (name) VALUES (%s) RETURNING *"
result = execute_query(query, (name,))

Router Endpoint Pattern

from fastapi import APIRouter, HTTPException
from app.models.schemas import Customer, CustomerCreate

router = APIRouter()

@router.get("/customers/{id}", response_model=Customer)
async def get_customer(id: int):
    result = execute_query("SELECT * FROM customers WHERE id = %s", (id,))
    if not result:
        raise HTTPException(status_code=404, detail="Not found")
    return result[0]

Configuration Access Pattern

from app.core.config import settings

# Check feature flags
if settings.ECONOMIC_READ_ONLY:
    logger.warning("Read-only mode enabled")
    return {"message": "Dry run only"}

Project-Specific Conventions

Logging

  • Use Python's logging module, not print()
  • Format: logger.info("Message with %s", variable)
  • Emoji prefixes for visibility: 🚀 startup, success, error, ⚠️ warning

Error Handling

  • Use HTTPException for API errors with appropriate status codes
  • Log exceptions with logger.error() before raising
  • Return user-friendly error messages, log technical details

API Response Format

  • Use Pydantic response models - let FastAPI handle serialization
  • For lists: response_model=List[ModelName]
  • For health checks: return dict with status, service, version keys

File Organization

  • One router per domain - don't create mega-files
  • Services in app/services/ for business logic (e.g., economic.py for API integration)
  • Jobs in app/jobs/ for scheduled tasks
  • Keep routers thin - delegate complex logic to services

Docker & Deployment

Environment Files

  • .env.example - Local development template (weak passwords OK)
  • .env.prod.example - Production template with placeholders and security notes
  • Never commit .env - it's in .gitignore

Docker Compose Files

  • docker-compose.yml - Local dev with code mounting and auto-reload
  • docker-compose.prod.yml - Production with version tags, no code mounts, always restart

Gitea Integration

The production deployment pulls code from the Gitea server at g.bmcnetworks.dk:

  • Requires GITHUB_TOKEN environment variable (Gitea personal access token)
  • Repo format: ct/bmc_hub (owner/repo)
  • Release tags: v1.0.0, v1.2.3 etc. (semantic versioning)

Common Pitfalls to Avoid

  1. Don't use SQLite - this is a PostgreSQL-only project
  2. Don't use ORMs - use raw SQL via execute_query() for simplicity
  3. Don't hardcode database URLs - always use settings.DATABASE_URL
  4. Don't skip parameterization - SQL injection risk with string formatting
  5. Don't mount code in production - use version tags and Docker builds
  6. Don't disable safety switches in .env.example - always default to safe mode

Integration with OmniSync

This project shares architectural patterns with /Users/christianthomas/pakkemodtagelse:

  • Similar FastAPI structure with routers, services, jobs
  • PostgreSQL with psycopg2 (no ORM)
  • Docker Compose for both dev and production
  • Environment-based configuration
  • Migration-based schema management

Key differences:

  • BMC Hub uses version-tagged deployments from Gitea
  • Simpler domain model (customers, hardware, billing vs. full invoice management)
  • No Ollama/LLM integration (not needed for this use case)

Quick Reference

# Import patterns
from app.core.config import settings
from app.core.database import execute_query
from app.models.schemas import Customer, CustomerCreate
from fastapi import APIRouter, HTTPException

# Database query
result = execute_query("SELECT * FROM table WHERE id = %s", (id,))

# Configuration
settings.DATABASE_URL
settings.ECONOMIC_READ_ONLY

# Router registration (main.py)
app.include_router(router, prefix="/api/v1", tags=["Feature"])

Health Check Endpoint

Always maintain /health and /api/v1/system/health endpoints:

  • Test database connectivity
  • Report configuration state
  • Return service name and version

This ensures monitoring systems (Uptime Kuma) can track service health.