2025-07-27 14:26:30 +00:00
|
|
|
|
package routes
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"canguidev/shelfy/internal/controllers"
|
|
|
|
|
|
"canguidev/shelfy/internal/middlewares"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"log"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func AddRoutes(superRoute *gin.RouterGroup,db *gorm.DB) {
|
|
|
|
|
|
userRoutes(superRoute,db)
|
|
|
|
|
|
testAcces(superRoute)
|
|
|
|
|
|
loginRoutes(superRoute,db)
|
|
|
|
|
|
pathRoute(superRoute,db)
|
|
|
|
|
|
folderRoute(superRoute,db)
|
|
|
|
|
|
downloadRoute(superRoute,db)
|
|
|
|
|
|
}
|
|
|
|
|
|
func loginRoutes(superRoute *gin.RouterGroup,db *gorm.DB) {
|
|
|
|
|
|
loginRouter := superRoute.Group("/login")
|
|
|
|
|
|
{
|
|
|
|
|
|
loginRouter.POST("/", controllers.Login(db))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func userRoutes( superRoute *gin.RouterGroup,db*gorm.DB){
|
|
|
|
|
|
userRoutes := superRoute.Group("/users")
|
|
|
|
|
|
{
|
|
|
|
|
|
userRoutes.POST("/",middlewares.CheckAuth(db),controllers.CreateUser(db))
|
|
|
|
|
|
userRoutes.GET("/", middlewares.CheckAuth(db),controllers.ReadAllUser(db))
|
|
|
|
|
|
userRoutes.GET("/:id", middlewares.CheckAuth(db),controllers.FindUserById(db))
|
|
|
|
|
|
userRoutes.PUT("/:id", middlewares.CheckAuth(db),controllers.UpdateUser(db))
|
|
|
|
|
|
userRoutes.DELETE("/:id", middlewares.CheckAuth(db),controllers.DeleteUser(db))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
func testAcces(superRoute *gin.RouterGroup){
|
|
|
|
|
|
test := superRoute.Group("/test")
|
|
|
|
|
|
{
|
|
|
|
|
|
test.GET("/",controllers.TestPing())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func pathRoute (superRoute *gin.RouterGroup,db *gorm.DB){
|
|
|
|
|
|
pathRoutes := superRoute.Group("/path")
|
|
|
|
|
|
{
|
|
|
|
|
|
pathRoutes.POST("/save-path", middlewares.CheckAuth(db),controllers.CreateSavePath(db))
|
|
|
|
|
|
pathRoutes.GET("/save-path", middlewares.CheckAuth(db),controllers.ReadAllSavePath(db))
|
|
|
|
|
|
pathRoutes.PUT("/save-path/:id", middlewares.CheckAuth(db),controllers.UpdateSavePath(db))
|
|
|
|
|
|
pathRoutes.DELETE("/save-path/:id", middlewares.CheckAuth(db),controllers.DeleteSavePath(db))
|
|
|
|
|
|
pathRoutes.POST("/save-path/validate", middlewares.CheckAuth(db),controllers.PathValidationHandler)
|
2025-09-28 11:42:05 +00:00
|
|
|
|
|
2025-07-27 14:26:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func folderRoute (superRoute *gin.RouterGroup,db *gorm.DB){
|
|
|
|
|
|
|
|
|
|
|
|
folderRoutes :=superRoute.Group("/folders")
|
|
|
|
|
|
{
|
|
|
|
|
|
folderRoutes.GET("/",middlewares.CheckAuth(db),controllers.StreamHandler(db))
|
|
|
|
|
|
folderRoutes.GET("/details",middlewares.CheckAuth(db),controllers.DetailHandler())
|
2025-09-28 11:42:05 +00:00
|
|
|
|
folderRoutes.DELETE("/", middlewares.CheckAuth(db), controllers.DeleteHandler())
|
|
|
|
|
|
folderRoutes.GET("/stats", middlewares.CheckAuth(db), controllers.FolderStatsHandler())
|
2025-07-27 14:26:30 +00:00
|
|
|
|
folderRoutes.GET("/file", middlewares.CheckAuth(db), func(c *gin.Context) {
|
|
|
|
|
|
path := c.Query("path")
|
|
|
|
|
|
filePath := filepath.Join("upload", filepath.Clean("/"+path))
|
2025-07-27 14:55:26 +00:00
|
|
|
|
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
|
|
|
|
c.Header("Pragma", "no-cache")
|
|
|
|
|
|
c.Header("Expires", "0")
|
|
|
|
|
|
|
|
|
|
|
|
// Supprime le support de "If-Modified-Since" pour forcer le 200
|
|
|
|
|
|
c.Request.Header.Del("If-Modified-Since")
|
2025-07-27 14:26:30 +00:00
|
|
|
|
|
|
|
|
|
|
ext := strings.ToLower(filepath.Ext(filePath))
|
|
|
|
|
|
if ext == ".mp4" || ext == ".mkv" || ext == ".webm" || ext == ".mp3" {
|
|
|
|
|
|
streamFile(c, filePath)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
c.File(filePath)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func downloadRoute (superRoute *gin.RouterGroup,db *gorm.DB){
|
|
|
|
|
|
downloadRoutes := superRoute.Group("/download")
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
downloadRoutes.GET("/jobs", middlewares.CheckAuth(db), controllers.HandleListJobs(db))
|
|
|
|
|
|
downloadRoutes.GET("/debrid-status",middlewares.CheckAuth(db),controllers.GetDebridStatus(db))
|
|
|
|
|
|
downloadRoutes.POST("/account", middlewares.CheckAuth(db),controllers.PostDebridLogin(db))
|
|
|
|
|
|
downloadRoutes.POST("/add", middlewares.CheckAuth(db),controllers.HandleAddJobsMultiple(db))
|
|
|
|
|
|
downloadRoutes.POST("/jobs/:id/start", middlewares.CheckAuth(db),controllers.HandleStartJob(db))
|
|
|
|
|
|
downloadRoutes.POST("/jobs/:id/pause", middlewares.CheckAuth(db),controllers.HandlePauseJob())
|
|
|
|
|
|
downloadRoutes.POST("/jobs/:id/resume", middlewares.CheckAuth(db),controllers.HandleResumeJob(db))
|
|
|
|
|
|
downloadRoutes.DELETE("/jobs/:id", middlewares.CheckAuth(db),controllers.HandleDeleteJob(db))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func streamFile(c *gin.Context, filePath string) {
|
|
|
|
|
|
log.Printf("[STREAM] ➡️ Requête streaming sur %s", filePath)
|
|
|
|
|
|
|
|
|
|
|
|
f, err := os.Open(filePath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("[STREAM][ERREUR] Impossible d'ouvrir: %v", err)
|
|
|
|
|
|
c.Status(http.StatusNotFound)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
|
|
fileStat, err := f.Stat()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("[STREAM][ERREUR] Stat impossible: %v", err)
|
|
|
|
|
|
c.Status(http.StatusInternalServerError)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fileSize := fileStat.Size()
|
|
|
|
|
|
log.Printf("[STREAM] Taille du fichier: %d octets", fileSize)
|
|
|
|
|
|
c.Header("Accept-Ranges", "bytes")
|
|
|
|
|
|
c.Header("Content-Type", mimeTypeByExt(filePath))
|
|
|
|
|
|
|
|
|
|
|
|
rangeHeader := c.GetHeader("Range")
|
|
|
|
|
|
log.Printf("[STREAM] Range header reçu: %q", rangeHeader)
|
|
|
|
|
|
|
|
|
|
|
|
if rangeHeader == "" {
|
|
|
|
|
|
// Pas de Range, tout le fichier
|
|
|
|
|
|
c.Header("Content-Length", strconv.FormatInt(fileSize, 10))
|
|
|
|
|
|
log.Printf("[STREAM] 📦 Pas de Range, envoi du fichier complet (200 OK)")
|
|
|
|
|
|
http.ServeContent(c.Writer, c.Request, fileStat.Name(), fileStat.ModTime(), f)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Analyse du Range
|
|
|
|
|
|
var start, end int64
|
|
|
|
|
|
start, end = 0, fileSize-1
|
|
|
|
|
|
|
|
|
|
|
|
if strings.HasPrefix(rangeHeader, "bytes=") {
|
|
|
|
|
|
rangeParts := strings.Split(strings.TrimPrefix(rangeHeader, "bytes="), "-")
|
|
|
|
|
|
if len(rangeParts) == 2 {
|
|
|
|
|
|
if s, err := strconv.ParseInt(rangeParts[0], 10, 64); err == nil {
|
|
|
|
|
|
start = s
|
|
|
|
|
|
}
|
|
|
|
|
|
if rangeParts[1] != "" {
|
|
|
|
|
|
if e, err := strconv.ParseInt(rangeParts[1], 10, 64); err == nil {
|
|
|
|
|
|
end = e
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
log.Printf("[STREAM] Parsed range: start=%d, end=%d", start, end)
|
|
|
|
|
|
|
|
|
|
|
|
if start > end || start < 0 || end >= fileSize {
|
|
|
|
|
|
log.Printf("[STREAM][ERREUR] Range invalide: %d-%d sur %d", start, end, fileSize)
|
|
|
|
|
|
c.Status(http.StatusRequestedRangeNotSatisfiable)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
c.Status(http.StatusPartialContent)
|
|
|
|
|
|
c.Header("Content-Range", "bytes "+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(fileSize, 10))
|
|
|
|
|
|
c.Header("Content-Length", strconv.FormatInt(end-start+1, 10))
|
|
|
|
|
|
|
|
|
|
|
|
log.Printf("[STREAM] ▶️ Envoi bytes %d-%d sur %d [%s]", start, end, fileSize, filePath)
|
|
|
|
|
|
f.Seek(start, io.SeekStart)
|
|
|
|
|
|
n, err := io.CopyN(c.Writer, f, end-start+1)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("[STREAM][ERREUR] pendant l'envoi: %v", err)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.Printf("[STREAM] ✔️ Fini, envoyé %d octets", n)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper : extension → Content-Type (très basique)
|
|
|
|
|
|
func mimeTypeByExt(filePath string) string {
|
|
|
|
|
|
ext := strings.ToLower(filepath.Ext(filePath))
|
|
|
|
|
|
switch ext {
|
|
|
|
|
|
case ".mp4":
|
|
|
|
|
|
return "video/mp4"
|
|
|
|
|
|
case ".mkv":
|
|
|
|
|
|
return "video/x-matroska"
|
|
|
|
|
|
case ".webm":
|
|
|
|
|
|
return "video/webm"
|
|
|
|
|
|
case ".mp3":
|
|
|
|
|
|
return "audio/mpeg"
|
|
|
|
|
|
case ".pdf":
|
|
|
|
|
|
return "application/pdf"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "application/octet-stream"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|