shelfy-v2/internal/controllers/folderController.go

244 lines
6.9 KiB
Go
Raw Normal View History

2025-07-27 14:26:30 +00:00
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"
2025-08-18 19:50:34 +00:00
// --- Sanitize du chemin courant ---
raw := c.Query("path")
cur := filepath.Clean("/" + strings.TrimSpace(raw)) // force un chemin absolu “virtuel”
if cur == "/" {
cur = "" // racine logique sous "upload"
}
// Interdit les traversées ou chemins absolus réels
if strings.Contains(cur, "..") || strings.HasPrefix(raw, "/") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Chemin invalide"})
return
}
// --- Liste des dossiers du dossier racine (upload/*) ---
2025-07-27 14:26:30 +00:00
rootEntries, err := listEntries(base, "")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list root entries"})
return
}
2025-08-18 19:50:34 +00:00
// On ne garde que les dossiers et on prépare la liste des noms pour une recherche en BDD
2025-07-27 14:26:30 +00:00
var dirs []Entry
2025-08-18 19:50:34 +00:00
var rootNames []string
2025-07-27 14:26:30 +00:00
for _, e := range rootEntries {
if e.IsDir {
dirs = append(dirs, e)
2025-08-18 19:50:34 +00:00
rootNames = append(rootNames, e.Name)
2025-07-27 14:26:30 +00:00
}
}
2025-08-18 19:50:34 +00:00
// Batch: récupère les IDs pour tous les noms racine (Path = nom simple)
// (tu peux remplacer "path = ?" par "path_name = ?" si tu préfères)
var rows []models.PathDownload
if len(rootNames) > 0 {
if err := db.Where("path IN ?", rootNames).Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "DB lookup failed"})
return
}
}
// Map nom -> ID (int64)
idByName := make(map[string]int64, len(rows))
for _, r := range rows {
idByName[r.Path] = r.ID // Path = "Film"/"Série"/...
}
// Affecte l'ID seulement pour les dossiers racine
for i := range dirs {
if id, ok := idByName[dirs[i].Name]; ok {
dirs[i].ID = id
} else {
dirs[i].ID = 0 // non trouvé en BDD
}
}
// --- Liste du chemin courant (upload/<cur>) ---
2025-07-27 14:26:30 +00:00
entries, err := listEntries(base, cur)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list entries"})
return
}
2025-08-18 19:50:34 +00:00
// IMPORTANT :
// La BDD ne contient que les dossiers racine.
// Donc on ne tente de mettre un ID aux entries QUE si on est à la racine.
if cur == "" && len(entries) > 0 {
var names []string
for _, e := range entries {
if e.IsDir {
names = append(names, e.Name)
}
}
var rows2 []models.PathDownload
if len(names) > 0 {
if err := db.Where("path IN ?", names).Find(&rows2).Error; err == nil {
tmp := make(map[string]int64, len(rows2))
for _, r := range rows2 {
tmp[r.Path] = r.ID
}
for i := range entries {
if entries[i].IsDir {
if id, ok := tmp[entries[i].Name]; ok {
entries[i].ID = id
}
}
}
}
}
} else {
// On laisse ID = 0 pour le contenu interne des catégories (non suivi en BDD)
for i := range entries {
entries[i].ID = 0
}
2025-07-27 14:26:30 +00:00
}
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)
}
}