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

207 lines
7.5 KiB
Markdown

# 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.