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
psycopg2with connection pooling - All DB operations via
app/core/database.py::execute_query()helper - Parameterized queries (
%splaceholders) to prevent SQL injection - Use
RealDictCursorfor dict-like row access - Schema defined in
migrations/init.sqlwith versioned migrations
FastAPI Router Pattern
- Each domain gets a router in
app/routers/(customers, hardware, billing, system) - Routers imported and registered in
main.pywith prefix/api/v1 - Use Pydantic models from
app/models/schemas.pyfor request/response validation - Return model instances directly - FastAPI handles serialization
Configuration Management
- All settings in
app/core/config.pyusingpydantic_settings.BaseSettings - Loads from
.envfile automatically viaConfig.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_ONLYandECONOMIC_DRY_RUN - Always default to
truefor 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-reloadENABLE_RELOAD=true- Uvicorn auto-restart
Production Deployment
Production uses version-tagged GitHub releases via Gitea:
- Tag release:
git tag v1.2.3 && git push --tags - Update
.envwithRELEASE_VERSION=v1.2.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.sqlruns on first container startup via Docker entrypoint- For schema changes: create
migrations/001_feature_name.sqland run manually or via migration script
Adding New Features
- Create Pydantic models in
app/models/schemas.py(Base, Create, Full schemas) - Add database migration in
migrations/XXX_feature.sql - Create router in
app/routers/feature.pywith CRUD endpoints - Register router in
main.py:app.include_router(feature.router, prefix="/api/v1", tags=["Feature"]) - 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
loggingmodule, notprint() - Format:
logger.info("Message with %s", variable) - Emoji prefixes for visibility:
🚀startup,✅success,❌error,⚠️warning
Error Handling
- Use
HTTPExceptionfor 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,versionkeys
File Organization
- One router per domain - don't create mega-files
- Services in
app/services/for business logic (e.g.,economic.pyfor 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-reloaddocker-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_TOKENenvironment variable (Gitea personal access token) - Repo format:
ct/bmc_hub(owner/repo) - Release tags:
v1.0.0,v1.2.3etc. (semantic versioning)
Common Pitfalls to Avoid
- Don't use SQLite - this is a PostgreSQL-only project
- Don't use ORMs - use raw SQL via
execute_query()for simplicity - Don't hardcode database URLs - always use
settings.DATABASE_URL - Don't skip parameterization - SQL injection risk with string formatting
- Don't mount code in production - use version tags and Docker builds
- 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.