- 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.
537 lines
12 KiB
Markdown
537 lines
12 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
|
|
|
|
### 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:**
|
|
```sql
|
|
hub_customer_id INTEGER REFERENCES customers(id) -- Link til core
|
|
```
|
|
|
|
**SKAL have auto-link trigger:**
|
|
```sql
|
|
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`:
|
|
|
|
```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)
|