// Package modules — Loader de modules. // Découvre les modules disponibles, vérifie leur état en DB, et les initialise si activés. // Un module désactivé ne fait appel à aucune de ses méthodes Register(). package modules import ( "database/sql" "fmt" "log" "net/http" ) // Loader charge et gère les modules actifs. type Loader struct { db *sql.DB registry *coreRegistry modules []Module } // NewLoader crée un Loader avec le router et la DB fournis. func NewLoader(db *sql.DB) *Loader { return &Loader{ db: db, registry: newCoreRegistry(db), } } // RegisterModule enregistre un module disponible (appelé à l'init, depuis main.go). // Le module sera initialisé seulement s'il est activé en base. func (l *Loader) RegisterModule(m Module) { l.modules = append(l.modules, m) } // LoadActive charge et initialise tous les modules activés en base de données. func (l *Loader) LoadActive() error { for _, m := range l.modules { enabled, err := l.isEnabled(m.ID()) if err != nil { return fmt.Errorf("vérification module %s : %w", m.ID(), err) } if !enabled { log.Printf("Module %s : désactivé, ignoré", m.ID()) continue } log.Printf("Module %s : chargement...", m.ID()) if err := m.Register(l.registry); err != nil { return fmt.Errorf("initialisation module %s : %w", m.ID(), err) } log.Printf("Module %s : chargé avec succès", m.ID()) } return nil } // isEnabled vérifie en base de données si un module est activé. func (l *Loader) isEnabled(id string) (bool, error) { var enabled int err := l.db.QueryRow(`SELECT is_enabled FROM modules WHERE id = ?`, id).Scan(&enabled) if err == sql.ErrNoRows { return false, nil // Module inconnu = désactivé } return enabled == 1, err } // Registry retourne le registry partagé (pour accès par le serveur HTTP). func (l *Loader) Registry() *coreRegistry { return l.registry } // ---- Implémentation interne du Registry ---- // RouteEntry décrit une route HTTP enregistrée par un module. type RouteEntry struct { Method string Path string Handler http.HandlerFunc RequireAdmin bool } type migrationEntry struct { version int sql string fn MigrationFn } type translationEntry struct { lang string keys map[string]string } // coreRegistry implémente l'interface Registry. type coreRegistry struct { db *sql.DB routes []RouteEntry wsChannels map[string]WSHandler widgets []WidgetDef settingsTabs []SettingsTabDef migrations []migrationEntry translations []translationEntry } func newCoreRegistry(db *sql.DB) *coreRegistry { return &coreRegistry{ db: db, wsChannels: make(map[string]WSHandler), } } func (r *coreRegistry) RegisterRoute(method, path string, handler http.HandlerFunc, requireAdmin bool) { r.routes = append(r.routes, RouteEntry{method, path, handler, requireAdmin}) } func (r *coreRegistry) RegisterWSChannel(channel string, handler WSHandler) { r.wsChannels[channel] = handler } func (r *coreRegistry) RegisterWidget(widget WidgetDef) { r.widgets = append(r.widgets, widget) } func (r *coreRegistry) RegisterSettingsTab(tab SettingsTabDef) { r.settingsTabs = append(r.settingsTabs, tab) } func (r *coreRegistry) RegisterTranslations(lang string, keys map[string]string) { r.translations = append(r.translations, translationEntry{lang, keys}) } func (r *coreRegistry) RegisterMigration(version int, sqlStr string, fn MigrationFn) { r.migrations = append(r.migrations, migrationEntry{version, sqlStr, fn}) } func (r *coreRegistry) DB() *sql.DB { return r.db } // GetRoutes retourne les routes enregistrées par les modules. func (r *coreRegistry) GetRoutes() []RouteEntry { return r.routes } // GetWidgets retourne les types de widgets disponibles. func (r *coreRegistry) GetWidgets() []WidgetDef { return r.widgets } // GetSettingsTabs retourne les onglets de paramètres des modules. func (r *coreRegistry) GetSettingsTabs() []SettingsTabDef { return r.settingsTabs }