# 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

## Design System

### "Nordic Top" Standard
The UI/UX is based on the **Nordic Top** design template found in `docs/design_reference/`.
- **Style**: Minimalist, clean, "Nordic" aesthetic.
- **Layout**: Fluid width, top navigation bar, card-based content.
- **Primary Color**: Deep Blue (`#0f4c75`).

### Mandatory Features
All frontend implementations MUST support:
1.  **Dark Mode**: A toggle to switch between Light and Dark themes.
2.  **Color Themes**: Architecture must allow for dynamic accent color changes (CSS Variables).
3.  **Responsive Design**: Mobile-first approach using Bootstrap 5 grid.

### Reference Implementation
- **Components**: See `docs/design_reference/components.html` for the master list of UI elements.
- **Templates**: Use `index.html`, `customers.html`, `form.html` in `docs/design_reference/` as base templates.

## 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
- **Feature-Based Structure (Vertical Slices)**: Organize code by domain/feature.
  - **Pattern**: `/<feature>/frontend` and `/<feature>/backend`
  - **Example**: `Customers_companies/frontend` (Templates, UI) and `Customers_companies/backend` (Routers, Models)
- **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.
