# 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 ```bash 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"`: ```dockerfile 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 ```python 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 ```python 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 ```python 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 ```python # 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.