bmc_hub/docs/MODULE_SYSTEM.md
Christian 38fa3b6c0a feat: Add subscriptions lock feature to customers
- Added a new column `subscriptions_locked` to the `customers` table to manage subscription access.
- Implemented a script to create new modules from a template, including updates to various files (module.json, README.md, router.py, views.py, and migration SQL).
- Developed a script to import BMC Office subscriptions from an Excel file into the database, including error handling and statistics reporting.
- Created a script to lookup and update missing CVR numbers using the CVR.dk API.
- Implemented a script to relink Hub customers to e-conomic customer numbers based on name matching.
- Developed scripts to sync CVR numbers from Simply-CRM and vTiger to the local customers database.
2025-12-13 12:06:28 +01:00

9.9 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

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)