228 lines
8.6 KiB
Markdown
228 lines
8.6 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
|
|
|
|
## 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.
|