bmc_hub/docs/MODULE_SYSTEM.md
Christian 0502a7b080 feat: Implement central tagging system with CRUD operations, entity tagging, and workflow management
- Added API endpoints for tag management (create, read, update, delete).
- Implemented entity tagging functionality to associate tags with various entities.
- Created workflow management for tag-triggered actions.
- Developed frontend views for tag administration using FastAPI and Jinja2.
- Designed HTML template for tag management interface with Bootstrap styling.
- Added JavaScript for tag picker component with keyboard shortcuts and dynamic tag filtering.
- Created database migration scripts for tags, entity_tags, and tag_workflows tables.
- Included default tags for initial setup in the database.
2025-12-17 07:56:33 +01:00

12 KiB

BMC Hub Module System

Oversigt

BMC Hub har nu et dynamisk modul-system der tillader isoleret udvikling af features uden at påvirke core systemet. Moduler kan udvikles, testes og deployes uafhængigt.

Arkitektur

Core vs Modules

Core System (forbliver i app/):

  • auth/ - Authentication
  • customers/ - Customer management
  • hardware/ - Hardware tracking
  • billing/ - Billing integration
  • contacts/ - Contact management
  • vendors/ - Vendor management
  • settings/ - Settings
  • system/ - System utilities
  • dashboard/ - Dashboard
  • devportal/ - Developer portal
  • timetracking/ - Time tracking
  • emails/ - Email system

Dynamic Modules (nye features i app/modules/):

  • Isolerede i egen directory
  • Dynamisk loaded ved startup
  • Hot-reload support (restart påkrævet)
  • Egen database namespace (table prefix)
  • Egen konfiguration (miljøvariable)
  • Egne migrations

File Struktur

app/modules/
├── _template/              # Template for nye moduler (IKKE loaded)
│   ├── module.json         # Metadata og config
│   ├── README.md
│   ├── backend/
│   │   ├── __init__.py
│   │   └── router.py       # FastAPI endpoints
│   ├── frontend/
│   │   ├── __init__.py
│   │   └── views.py        # HTML views
│   ├── templates/
│   │   └── index.html      # Jinja2 templates
│   └── migrations/
│       └── 001_init.sql    # Database migrations
│
└── my_module/              # Eksempel modul
    ├── module.json
    ├── ...

Opret Nyt Modul

Via CLI Tool

python scripts/create_module.py my_feature "My awesome feature"

Dette opretter:

  • Komplet modul struktur
  • Opdateret module.json med modul navn
  • Placeholder kode i router og views
  • Database migration template

Manuel Oprettelse

  1. Kopiér template:

    cp -r app/modules/_template app/modules/my_module
    
  2. Rediger module.json:

    {
      "name": "my_module",
      "version": "1.0.0",
      "description": "Min feature beskrivelse",
      "author": "Dit navn",
      "enabled": false,
      "dependencies": [],
      "table_prefix": "mymod_",
      "api_prefix": "/api/v1/mymod",
      "tags": ["My Module"]
    }
    
  3. Opdater kode:

    • backend/router.py - Implementer dine endpoints
    • frontend/views.py - Implementer HTML views
    • templates/index.html - Design UI
    • migrations/001_init.sql - Opret database tabeller

Database Isolering

Customer Linking Pattern

VIGTIG ARKITEKTUR BESLUTNING:

Core customers tabel er single source of truth for økonomi, fakturering og CVR.

Hvornår skal moduler have egen kunde-tabel?

Opret modul-specifik kunde-tabel hvis:

  • Modulet syncer fra eksternt system (vTiger, CRM, etc.)
  • Mange module-specifikke felter nødvendige (external_id, sync_status, etc.)
  • Custom lifecycle/workflow
  • Eksempel: tmodule_customers (sync fra vTiger)

🚫 Brug direkte customers.id foreign key hvis:

  • Simple relationer uden external sync
  • Få/ingen custom felter
  • Standard workflow
  • Eksempel: ticket_system → direkte FK til customers.id

Hvis modul-kunde-tabel oprettes:

SKAL altid have:

hub_customer_id INTEGER REFERENCES customers(id)  -- Link til core

SKAL have auto-link trigger:

CREATE OR REPLACE FUNCTION auto_link_{module}_customer()
RETURNS TRIGGER AS $$
DECLARE
    matched_hub_id INTEGER;
BEGIN
    IF NEW.hub_customer_id IS NOT NULL THEN
        RETURN NEW;
    END IF;
    
    SELECT id INTO matched_hub_id
    FROM customers
    WHERE LOWER(TRIM(name)) = LOWER(TRIM(NEW.name))
    LIMIT 1;
    
    IF matched_hub_id IS NOT NULL THEN
        NEW.hub_customer_id := matched_hub_id;
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_auto_link_{module}_customer
    BEFORE INSERT OR UPDATE OF name
    ON {module}_customers
    FOR EACH ROW
    EXECUTE FUNCTION auto_link_{module}_customer();

Hvorfor dette pattern?

  • E-conomic export virker automatisk (via hub_customer_id → customers.economic_customer_number)
  • Billing integration automatisk
  • Ingen manuel linking nødvendig
  • Nye sync'ede kunder linkes automatisk

Se migrations/028_auto_link_tmodule_customers.sql for live eksempel.

Table Prefix Pattern

Alle tabeller SKAL bruge prefix fra module.json:

-- BAD: Risiko for kollision
CREATE TABLE customers (...);

-- GOOD: Isoleret med prefix
CREATE TABLE mymod_customers (...);

Database Queries

Brug ALTID helper functions:

from app.core.database import execute_query, execute_insert

# Hent data
customers = execute_query(
    "SELECT * FROM mymod_customers WHERE active = %s",
    (True,)
)

# Insert med auto-returned ID
customer_id = execute_insert(
    "INSERT INTO mymod_customers (name) VALUES (%s)",
    ("Test",)
)

Konfiguration

Environment Variables

Modul-specifik config følger pattern: MODULES__{MODULE_NAME}__{KEY}

Eksempel .env:

# Global module system
MODULES_ENABLED=true
MODULES_AUTO_RELOAD=true

# Specific module config
MODULES__MY_MODULE__API_KEY=secret123
MODULES__MY_MODULE__READ_ONLY=false
MODULES__MY_MODULE__DRY_RUN=false

I Kode

from app.core.config import get_module_config

# Hent config med fallback
api_key = get_module_config("my_module", "API_KEY")
read_only = get_module_config("my_module", "READ_ONLY", "false") == "true"

Safety Switches

Best Practice: Safety First

Alle moduler BØR have safety switches:

# I backend/router.py
read_only = get_module_config("my_module", "READ_ONLY", "true") == "true"
dry_run = get_module_config("my_module", "DRY_RUN", "true") == "true"

if read_only:
    return {"success": False, "message": "READ_ONLY mode"}
    
if dry_run:
    logger.info("🧪 DRY_RUN: Would perform action")
    return {"success": True, "dry_run": True}

Anbefalet defaults:

  • READ_ONLY=true - Bloker alle writes indtil eksplicit enabled
  • DRY_RUN=true - Log actions uden at udføre

Enable/Disable Moduler

Via API

# Enable
curl -X POST http://localhost:8000/api/v1/modules/my_module/enable

# Disable
curl -X POST http://localhost:8000/api/v1/modules/my_module/disable

# List alle
curl http://localhost:8000/api/v1/modules

Manuelt

Rediger app/modules/my_module/module.json:

{
  "enabled": true
}

Vigtigt: Kræver app restart!

docker-compose restart api

Migrations

Kør Migration

# Via psql
psql -U bmc_hub -d bmc_hub -f app/modules/my_module/migrations/001_init.sql

# Via Python
python apply_migration.py my_module 001_init.sql

Migration Best Practices

  1. Nummering: Sekventiel (001, 002, 003...)
  2. Idempotent: Brug CREATE TABLE IF NOT EXISTS
  3. Table prefix: Alle tabeller med prefix
  4. Rollback: Inkluder rollback instructions i kommentar

Eksempel:

-- Migration: 001_init.sql
-- Rollback: DROP TABLE mymod_items;

CREATE TABLE IF NOT EXISTS mymod_items (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Development Workflow

1. Opret Modul

python scripts/create_module.py invoice_parser "Parse invoice PDFs"

2. Implementer Features

Rediger:

  • backend/router.py - API endpoints
  • frontend/views.py - HTML pages
  • templates/*.html - UI components

3. Kør Migration

psql -U bmc_hub -d bmc_hub -f app/modules/invoice_parser/migrations/001_init.sql

4. Enable Modul

// module.json
{
  "enabled": true
}

5. Restart & Test

docker-compose restart api

# Test API
curl http://localhost:8000/api/v1/invoice_parser/health

# Test UI
open http://localhost:8000/invoice_parser

Hot Reload

Systemet understøtter hot-reload i development mode:

# I docker-compose.yml
environment:
  - ENABLE_RELOAD=true

# Source code mounted
volumes:
  - ./app:/app/app:ro

Når skal jeg genstarte?

  • IKKE nødvendigt: Python kode ændringer i eksisterende filer
  • Restart påkrævet: Enable/disable modul, nye filer, module.json ændringer

Fejlhåndtering

Startup Errors

Hvis et modul fejler under loading:

  • Core systemet fortsætter
  • Modulet bliver ikke loaded
  • Fejl logges til console + logs/app.log

Log output:

📦 Fundet modul: my_module v1.0.0 (enabled=true)
❌ Kunne ikke loade modul my_module: ModuleNotFoundError
✅ Loaded 2 modules: ['other_module', 'third_module']

Runtime Errors

Endpoints i moduler er isolerede:

  • Exception i ét modul påvirker ikke andre
  • FastAPI returner 500 med error message
  • Logger fejl med module context

API Documentation

Alle modul endpoints vises automatisk i FastAPI docs:

http://localhost:8000/api/docs

Endpoints grupperes under modul tags fra module.json.

Testing

Unit Tests

# tests/modules/test_my_module.py
import pytest
from app.core.database import execute_query

def test_my_module_item_creation():
    result = execute_query(
        "SELECT * FROM mymod_items WHERE name = %s",
        ("Test",),
        fetchone=True
    )
    assert result is not None

Integration Tests

Brug samme test database som core:

@pytest.fixture
def test_db():
    # Setup test data
    yield
    # Cleanup

Eksempel Modul

Se app/modules/_template/ for komplet working example.

Key files:

  • backend/router.py - CRUD endpoints med safety switches
  • frontend/views.py - HTML view med Jinja2
  • migrations/001_init.sql - Table creation med prefix
  • module.json - Metadata og config

Troubleshooting

Modul loader ikke

  1. Check enabled flag:

    cat app/modules/my_module/module.json | grep enabled
    
  2. Check logs:

    docker-compose logs -f api | grep my_module
    
  3. Verify dependencies: Hvis modul har dependencies i module.json, check at de er loaded først.

Database fejl

  1. Check table prefix:

    SELECT tablename FROM pg_tables WHERE tablename LIKE 'mymod_%';
    
  2. Verify migration:

    psql -U bmc_hub -d bmc_hub -c "\d mymod_items"
    

Import fejl

Sørg for at __init__.py findes i alle directories:

touch app/modules/my_module/backend/__init__.py
touch app/modules/my_module/frontend/__init__.py

Best Practices

DO

  • Brug table prefix konsistent
  • Implementer safety switches (READ_ONLY, DRY_RUN)
  • Log alle actions med emoji prefix
  • Brug execute_query() helpers
  • Dokumenter API endpoints i docstrings
  • Version moduler semantisk (1.0.0 → 1.1.0)
  • Test isoleret før enable

DON'T

  • Tilgå andre modulers tabeller direkte
  • Hardcode credentials i kode
  • Skip migration versioning
  • Glem table prefix
  • Disable safety switches uden grund
  • Commit .env files

Migration Path

Eksisterende Features → Moduler?

Beslutning: Core features forbliver i app/ structure.

Rationale:

  • Proven patterns
  • Stabil foundation
  • Ingen gevinst ved migration
  • Risk af breakage

Nye features: Brug modul-system fra dag 1.

Fremtidig Udvidelse

Potentielle Features

  1. Hot reload uden restart - inotify watching af module.json
  2. Module marketplace - External repository
  3. Dependency resolution - Automatisk enable dependencies
  4. Version constraints - Min/max BMC Hub version
  5. Module API versioning - Breaking changes support
  6. Rollback support - Automatic migration rollback
  7. Module metrics - Usage tracking per module
  8. Module permissions - RBAC per module

Support

Issues: Se logs i logs/app.log Documentation: Se app/modules/_template/README.md Examples: Se template implementation


Status: Klar til brug (13. december 2025)