471 lines
9.9 KiB
Markdown
471 lines
9.9 KiB
Markdown
|
|
# 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:**
|
||
|
|
```bash
|
||
|
|
cp -r app/modules/_template app/modules/my_module
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Rediger `module.json`:**
|
||
|
|
```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`:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- BAD: Risiko for kollision
|
||
|
|
CREATE TABLE customers (...);
|
||
|
|
|
||
|
|
-- GOOD: Isoleret med prefix
|
||
|
|
CREATE TABLE mymod_customers (...);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Database Queries
|
||
|
|
|
||
|
|
Brug ALTID helper functions:
|
||
|
|
|
||
|
|
```python
|
||
|
|
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`:**
|
||
|
|
```bash
|
||
|
|
# 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
|
||
|
|
|
||
|
|
```python
|
||
|
|
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:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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`:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"enabled": true
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Vigtigt:** Kræver app restart!
|
||
|
|
|
||
|
|
```bash
|
||
|
|
docker-compose restart api
|
||
|
|
```
|
||
|
|
|
||
|
|
## Migrations
|
||
|
|
|
||
|
|
### Kør Migration
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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:**
|
||
|
|
```sql
|
||
|
|
-- 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
psql -U bmc_hub -d bmc_hub -f app/modules/invoice_parser/migrations/001_init.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Enable Modul
|
||
|
|
|
||
|
|
```json
|
||
|
|
// module.json
|
||
|
|
{
|
||
|
|
"enabled": true
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. Restart & Test
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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
|
||
|
|
|
||
|
|
```python
|
||
|
|
# 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:
|
||
|
|
```python
|
||
|
|
@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:**
|
||
|
|
```bash
|
||
|
|
cat app/modules/my_module/module.json | grep enabled
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Check logs:**
|
||
|
|
```bash
|
||
|
|
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:**
|
||
|
|
```sql
|
||
|
|
SELECT tablename FROM pg_tables WHERE tablename LIKE 'mymod_%';
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Verify migration:**
|
||
|
|
```bash
|
||
|
|
psql -U bmc_hub -d bmc_hub -c "\d mymod_items"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Import fejl
|
||
|
|
|
||
|
|
Sørg for at `__init__.py` findes i alle directories:
|
||
|
|
```bash
|
||
|
|
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)
|