diff --git a/internal/utils/lib.go b/internal/utils/lib.go index d78845c..9f8744f 100644 --- a/internal/utils/lib.go +++ b/internal/utils/lib.go @@ -6,6 +6,7 @@ import ( "log" "os" "path/filepath" + "strings" "gorm.io/gorm" @@ -13,69 +14,122 @@ import ( ) func CreateDefaultFolder(db *gorm.DB) { - type item struct { - Name string - } - defs := []item{ - {Name: "Film"}, - {Name: "Série"}, - {Name: "Manga"}, - {Name: "Magazine"}, + // noms “logiques” attendus en base + targets := []string{"Film", "Série", "Manga", "Magazine"} + + // 0) Nettoyage global : si des lignes ont path "upload/XXX", on les normalise en "XXX" + if err := normalizeExistingPaths(db); err != nil { + log.Printf("[DB] Warning: normalisation initiale partielle: %v", err) } - for _, it := range defs { - path := filepath.Join("upload", it.Name) - - // 1) Dossier : on s'assure qu'il existe - if err := os.MkdirAll(path, 0o755); err != nil { - log.Printf("[FOLDER] Erreur création '%s' : %v", path, err) + for _, name := range targets { + diskPath := filepath.Join("upload", name) // répertoire physique + // 1) Toujours créer le dossier physique + if err := os.MkdirAll(diskPath, 0o755); err != nil { + log.Printf("[FOLDER] Erreur création '%s' : %v", diskPath, err) continue } - // 2) Base : chercher une ligne existante par path OU path_name - var existing models.PathDownload - err := db.Where("path = ? OR path_name = ?", path, it.Name).First(&existing).Error + // 2) En base : on veut UNE ligne unique avec Path=Name et PathName=Name + // On cherche tout ce qui pourrait correspondre à ce nom (avec ou sans "upload/") + var rows []models.PathDownload + if err := db.Where("path = ? OR path = ? OR path_name = ? OR path_name = ?", + name, "upload/"+name, name, "upload/"+name, + ).Find(&rows).Error; err != nil { + log.Printf("[DB] Lookup '%s' échouée : %v", name, err) + continue + } - switch { - case errors.Is(err, gorm.ErrRecordNotFound): - // 2a) Pas trouvé -> on crée - row := models.PathDownload{ - Path: path, - PathName: it.Name, - } + switch len(rows) { + case 0: + // 2a) Rien trouvé → créer la ligne normalisée + row := models.PathDownload{Path: name, PathName: name} if err := db.Create(&row).Error; err != nil { - log.Printf("[DB] Échec création PathDownload(%s, %s) : %v", path, it.Name, err) + log.Printf("[DB] Création PathDownload('%s') KO : %v", name, err) } else { - log.Printf("[DB] Ligne créée PathDownload id=%v (%s | %s)", row.ID, row.PathName, row.Path) + log.Printf("[DB] Créé PathDownload id=%v (%s)", row.ID, name) } - case err != nil: - // 2b) Erreur DB - log.Printf("[DB] Erreur recherche PathDownload(%s | %s) : %v", path, it.Name, err) - default: - // 2c) Trouvé -> on normalise si besoin - updates := map[string]interface{}{} - if existing.Path != path { - updates["path"] = path - } - if existing.PathName != it.Name { - updates["path_name"] = it.Name - } - if len(updates) > 0 { - if err := db.Model(&existing).Updates(updates).Error; err != nil { - log.Printf("[DB] Échec mise à jour PathDownload id=%v : %v", existing.ID, err) + // 2b) On a des lignes → on les normalise et on garde une seule + // 1) mettre à jour la première ligne en Path=Name, PathName=Name + main := rows[0] + needUpdate := (main.Path != name) || (main.PathName != name) + if needUpdate { + if err := db.Model(&main).Updates(map[string]any{ + "path": name, + "path_name": name, + }).Error; err != nil { + log.Printf("[DB] Update id=%v -> (%s|%s) KO : %v", main.ID, name, name, err) } else { - log.Printf("[DB] Mis à jour PathDownload id=%v -> %#v", existing.ID, updates) + log.Printf("[DB] Normalisé id=%v -> (%s|%s)", main.ID, name, name) + } + } + + // 2) supprimer les doublons résiduels (même nom logique) + if len(rows) > 1 { + var dupIDs []uint + for _, r := range rows[1:] { + dupIDs = append(dupIDs, r.ID) + } + if err := db.Where("id IN ?", dupIDs).Delete(&models.PathDownload{}).Error; err != nil { + log.Printf("[DB] Suppression doublons (%v) KO : %v", dupIDs, err) + } else { + log.Printf("[DB] Doublons supprimés pour '%s' : %v", name, dupIDs) } - } else { - log.Printf("[DB] OK PathDownload id=%v déjà synchro (%s | %s)", existing.ID, existing.PathName, existing.Path) } } - // 3) (Facultatif) log côté fichiers - if fi, err := os.Stat(path); err == nil && fi.IsDir() { - fmt.Printf("[FOLDER] OK : %s\n", path) - } + log.Printf("[FOLDER] OK : %s (DB='%s')", diskPath, name) } } + +// normalizeExistingPaths met à jour toutes les lignes dont path commence par "upload/" +// en supprimant ce préfixe, et aligne path_name si nécessaire. Elle supprime aussi +// d’éventuels doublons créés par cette normalisation. +func normalizeExistingPaths(db *gorm.DB) error { + // 1) Récupérer les lignes impactées + var rows []models.PathDownload + if err := db.Where("path LIKE ?", "upload/%").Find(&rows).Error; err != nil { + return err + } + + for _, r := range rows { + trimmed := strings.TrimPrefix(r.Path, "upload/") + updates := map[string]any{"path": trimmed} + if r.PathName == "" || strings.HasPrefix(r.PathName, "upload/") { + updates["path_name"] = trimmed + } + if err := db.Model(&r).Updates(updates).Error; err != nil { + return fmt.Errorf("update id=%v (%s -> %s) : %w", r.ID, r.Path, trimmed, err) + } + } + + // 2) Éliminer d’éventuels doublons (même path normalisé), on garde le plus petit id + type rec struct { + ID uint + Path string + } + var all []rec + if err := db.Model(&models.PathDownload{}).Select("id, path").Order("path, id").Scan(&all).Error; err != nil { + return err + } + seen := make(map[string]uint) + var dupIDs []uint + for _, r := range all { + if keep, ok := seen[r.Path]; ok { + // doublon : on marque pour suppression + dupIDs = append(dupIDs, r.ID) + _ = keep + } else { + seen[r.Path] = r.ID + } + } + if len(dupIDs) > 0 { + if err := db.Where("id IN ?", dupIDs).Delete(&models.PathDownload{}).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("delete dups %v : %w", dupIDs, err) + } + log.Printf("[DB] Doublons supprimés après normalisation: %v", dupIDs) + } + return nil +}