feat: onglet Réparation dans paramètres — gestion modules fantômes

- GET /api/repair/modules : liste les modules non-core en DB
- DELETE /api/repair/modules/{id} : supprime un module de la DB
- settings.html : onglet Réparation avec liste + bouton Supprimer
- app.js : loadRepair() + resetModule() dans settingsPage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
enzo 2026-03-22 18:27:37 +01:00
parent ab834600ba
commit 3bc55a4c6f
4 changed files with 132 additions and 1 deletions

View file

@ -533,3 +533,52 @@ func (h *SettingsHandler) InstallRegistryModule(w http.ResponseWriter, r *http.R
"rebuilding": rebuilding,
})
}
// ── Réparation ────────────────────────────────────────────────────────────
// repairModule est une entrée retournée par GetRepairStatus.
type repairModule struct {
ID string `json:"id"`
Name string `json:"name"`
IsEnabled bool `json:"is_enabled"`
HasBackend bool `json:"has_backend"`
InstalledAt string `json:"installed_at"`
}
// GetRepairStatus retourne les modules non-core présents en DB (potentiellement fantômes).
// GET /api/repair/modules
func (h *SettingsHandler) GetRepairStatus(w http.ResponseWriter, r *http.Request) {
rows, err := h.db.Query(
`SELECT id, name, is_enabled, has_backend, COALESCE(installed_at,'') FROM modules WHERE is_core = 0`)
if err != nil {
JSONResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
defer rows.Close()
modules := []repairModule{}
for rows.Next() {
var m repairModule
rows.Scan(&m.ID, &m.Name, &m.IsEnabled, &m.HasBackend, &m.InstalledAt)
modules = append(modules, m)
}
JSONResponse(w, http.StatusOK, map[string]interface{}{"modules": modules})
}
// ResetModule supprime un module non-core de la DB (permet de le réinstaller proprement).
// DELETE /api/repair/modules/{id}
func (h *SettingsHandler) ResetModule(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
res, err := h.db.Exec(`DELETE FROM modules WHERE id = ? AND is_core = 0`, id)
if err != nil {
JSONResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
n, _ := res.RowsAffected()
if n == 0 {
JSONResponse(w, http.StatusNotFound, map[string]string{"error": "module introuvable ou module core"})
return
}
h.auditLogger.Log(r.Context(), "repair.reset_module", id, "")
JSONResponse(w, http.StatusOK, map[string]string{"message": "Module supprimé de la DB"})
}