package controllers import ( "canguidev/shelfy/internal/models" "net/http" "net/url" "os" "path/filepath" "strings" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type Entry struct { ID int64 `json:"ID"` Name, Path string IsDir bool ModTime time.Time Size int64 Children []Entry `json:"Children,omitempty"` Url string `json:"Url,omitempty"` // <- Ajout ici } func listEntries(base, rel string) ([]Entry, error) { dir := filepath.Join(base, rel) fis, err := os.ReadDir(dir) if err != nil { return nil, err } out := make([]Entry, 0, len(fis)) for _, fi := range fis { info, _ := fi.Info() out = append(out, Entry{ Name: fi.Name(), Path: filepath.ToSlash(filepath.Join(rel, fi.Name())), IsDir: fi.IsDir(), ModTime: info.ModTime(), Size: info.Size(), }) } return out, nil } func StreamHandler(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { base := "upload" cur := c.Query("path") // Liste des dossiers du dossier racine rootEntries, err := listEntries(base, "") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list root entries"}) return } var dirs []Entry for _, e := range rootEntries { if e.IsDir { var pathDownload models.PathDownload // Recherche par PathName (== e.Name) db.Where("path_name = ?", e.Name).First(&pathDownload) e.ID = pathDownload.ID // 0 si non trouvé dirs = append(dirs, e) } } // Liste du chemin courant entries, err := listEntries(base, cur) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list entries"}) return } for i := range entries { var pathDownload models.PathDownload db.Where("path_name = ?", entries[i].Name).First(&pathDownload) entries[i].ID = pathDownload.ID // 0 si non trouvé } c.JSON(http.StatusOK, gin.H{ "dirs": dirs, "entries": entries, "currentPath": cur, }) } } func DetailHandler() gin.HandlerFunc { return func(c *gin.Context) { base := "upload" rel := c.Query("path") rel = filepath.Clean("/" + rel) rel = strings.TrimPrefix(rel, "/") // => chemin relatif à "upload" absPath := filepath.Join(base, rel) absPath, err := filepath.Abs(absPath) if err != nil { c.JSON(500, gin.H{"error": "Path error"}) return } baseAbs, err := filepath.Abs(base) if err != nil { c.JSON(500, gin.H{"error": "Base path error"}) return } if !strings.HasPrefix(absPath, baseAbs) { c.JSON(403, gin.H{"error": "Access outside base directory is forbidden"}) return } info, err := os.Stat(absPath) if err != nil { c.JSON(404, gin.H{"error": "Path not found"}) return } type Entry struct { ID int64 `json:"ID,omitempty"` Name string `json:"Name"` Path string `json:"Path"` // <--- Toujours RELATIF à "upload" IsDir bool `json:"IsDir"` ModTime time.Time `json:"ModTime"` Size int64 `json:"Size"` Children []Entry `json:"Children,omitempty"` Url string `json:"Url,omitempty"` // <--- Lien d'accès au fichier } var buildTree func(absPath, relPath string, fi os.FileInfo) (Entry, error) buildTree = func(absPath, relPath string, fi os.FileInfo) (Entry, error) { entry := Entry{ Name: fi.Name(), Path: relPath, // <-- toujours RELATIF à la racine upload IsDir: fi.IsDir(), ModTime: fi.ModTime(), Size: fi.Size(), } if !fi.IsDir() { entry.Url = "/api/v1/folders/file?path=" + url.QueryEscape(relPath) } if fi.IsDir() { f, err := os.Open(absPath) if err != nil { return entry, err } defer f.Close() files, err := f.Readdir(0) if err != nil { return entry, err } for _, child := range files { childAbs := filepath.Join(absPath, child.Name()) childRel := filepath.Join(relPath, child.Name()) childEntry, err := buildTree(childAbs, childRel, child) if err != nil { continue } entry.Children = append(entry.Children, childEntry) } } return entry, nil } rootEntry, err := buildTree(absPath, rel, info) if err != nil { c.JSON(500, gin.H{"error": "Tree error"}) return } c.JSON(200, rootEntry) } }