2025-06-06 07:42:55 +00:00
package download
import (
2025-06-12 15:31:12 +00:00
"app/shelfly/internal/debridlink"
2025-06-06 07:42:55 +00:00
"app/shelfly/internal/models"
2025-06-12 15:31:12 +00:00
"context"
2025-06-06 07:42:55 +00:00
"encoding/json"
"errors"
"fmt"
2025-06-12 08:57:10 +00:00
"html/template"
2025-06-06 07:42:55 +00:00
"log"
"net/http"
"os"
"strings"
"github.com/gorilla/mux"
"gorm.io/gorm"
)
2025-06-19 14:50:30 +00:00
func renderPathHTML ( path models . PathDownload ) string {
return fmt . Sprintf ( `
< div class = "column" >
< form >
< div id = "path-%d" class = "path-update grid is-col-min-1" >
< input class = "input is-primary cell" name = "id" disabled value = "%d" > < / input >
< input class = "input is-primary fff cell" name = "pathName" value = "%s" disabled > < / span >
< button class = "button is-primary is-dark" id = "btn-path-edit-%d" hx - trigger = "click" hx - target = "#path-%d" hx - swap = "outerHTML" onclick = "enableAllInputPath(%d)" > Edit < / button >
< button class = "button is-danger" type = "button" id = "btn-path-annuler-%d" onclick = "disableAllInputPath(%d)" style = "display:none" > Annuler < / button >
< button class = "button is-primary is-dark" id = "btn-path-valider-%d" hx - put = "/api/pathDownload/update/%d" hx - trigger = "click[checkGlobalState()]" hx - target = "#path-%d" hx - swap = "outerHTML" hx - ext = "json-enc" style = "display:none" > Valider < / button >
< button class = "button is-danger" hx - delete = "/api/pathDownload/delete/%d" hx - trigger = "click" hx - target = "#path-%d" hx - swap = "outerHTML" hx - confirm = "Are you sure?" hx - swap = "outerHTML swap:1s" > Delete < / button >
< / div >
< / form >
< / div > ` ,
path . ID , path . ID , path . PathName ,
path . ID , path . ID , path . ID ,
path . ID , path . ID ,
path . ID , path . ID , path . ID ,
path . ID , path . ID )
}
2025-06-12 15:31:12 +00:00
func GetFirstActiveAccount ( client * debridlink . Client ) * debridlink . DebridAccount {
ctx := context . Background ( ) // ✅ on remplace ici
accounts , err := client . ListDebridAccounts ( ctx )
if err != nil {
log . Println ( "[ERROR] Impossible de récupérer les comptes :" , err )
return nil
}
for _ , acc := range accounts {
if acc . IsActive {
return & acc
}
}
return nil
}
2025-06-06 07:42:55 +00:00
func CreateSavePath ( db * gorm . DB ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
2025-06-19 14:50:30 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/html" )
2025-06-06 07:42:55 +00:00
var pathDownload models . PathDownload
if err := json . NewDecoder ( r . Body ) . Decode ( & pathDownload ) ; err != nil {
http . Error ( w , ` { "error": "Invalid JSON format"} ` , http . StatusBadRequest )
return
}
2025-06-19 13:26:09 +00:00
if pathDownload . PathName == "" {
http . Error ( w , ` { "error": "PathName cannot be empty"} ` , http . StatusBadRequest )
2025-06-06 07:42:55 +00:00
return
}
2025-06-19 13:26:09 +00:00
fullPath := "/app/upload/" + pathDownload . PathName
if _ , err := os . Stat ( fullPath ) ; os . IsNotExist ( err ) {
if err := os . MkdirAll ( fullPath , 0755 ) ; err != nil {
http . Error ( w , ` { "error": "Failed to create directory"} ` , http . StatusInternalServerError )
return
}
}
2025-06-06 07:42:55 +00:00
2025-06-19 13:26:09 +00:00
pathDownload . Path = fullPath
if err := db . Create ( & pathDownload ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to save path"} ` , http . StatusInternalServerError )
return
}
2025-06-06 07:42:55 +00:00
2025-06-19 14:50:30 +00:00
// 👉 On renvoie uniquement la ligne HTML nouvellement créée
fmt . Fprint ( w , renderPathHTML ( pathDownload ) )
2025-06-06 07:42:55 +00:00
}
}
func ReadAllSavePath ( db * gorm . DB ) http . HandlerFunc {
2025-06-19 13:26:09 +00:00
return func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "text/html" )
2025-06-06 07:42:55 +00:00
2025-06-19 13:26:09 +00:00
var paths [ ] models . PathDownload
if err := db . Find ( & paths ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to retrieve paths"} ` , http . StatusInternalServerError )
return
}
2025-06-06 07:42:55 +00:00
2025-06-19 13:26:09 +00:00
var response strings . Builder
for _ , path := range paths {
response . WriteString ( fmt . Sprintf ( `
< div class = "column" >
< form >
< div id = "path-%d" class = "path-update grid is-col-min-1" >
< input class = "input is-primary cell" name = "id" disabled value = "%d" > < / input >
< input class = "input is-primary fff cell" name = "pathName" value = "%s" disabled > < / span >
< button class = "button is-primary is-dark" id = "btn-path-edit-%d" hx - trigger = "click" hx - target = "#path-%d" hx - swap = "outerHTML" onclick = "enableAllInputPath(%d)" > Edit < / button >
< button class = "button is-danger" type = "button" id = "btn-path-annuler-%d" onclick = "disableAllInputPath(%d)" style = "display:none" > Annuler < / button >
< button class = "button is-primary is-dark" id = "btn-path-valider-%d" hx - put = "/api/pathDownload/update/%d" hx - trigger = "click[checkGlobalState()]" hx - target = "#path-%d" hx - swap = "outerHTML" hx - ext = "json-enc" style = "display:none" > Valider < / button >
< button class = "button is-danger" hx - delete = "/api/pathDownload/delete/%d" hx - trigger = "click" hx - target = "#path-%d" hx - swap = "outerHTML" hx - confirm = "Are you sure?" hx - swap = "outerHTML swap:1s" > Delete < / button >
< / div >
< / form >
< / div > ` ,
path . ID , path . ID , path . PathName ,
path . ID , path . ID , path . ID ,
path . ID , path . ID ,
path . ID , path . ID , path . ID ,
path . ID , path . ID ) )
}
2025-06-06 07:42:55 +00:00
2025-06-19 13:26:09 +00:00
w . WriteHeader ( http . StatusOK )
fmt . Fprint ( w , response . String ( ) )
}
2025-06-06 07:42:55 +00:00
}
func UpdateSavePath ( db * gorm . DB ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
2025-06-19 14:50:30 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/html" )
2025-06-06 07:42:55 +00:00
id := mux . Vars ( r ) [ "id" ]
var pathDownload models . PathDownload
if err := json . NewDecoder ( r . Body ) . Decode ( & pathDownload ) ; err != nil {
http . Error ( w , ` { "error": "Invalid JSON format"} ` , http . StatusBadRequest )
return
}
var existingPath models . PathDownload
if err := db . First ( & existingPath , "id = ?" , id ) . Error ; err != nil {
2025-06-19 14:50:30 +00:00
http . Error ( w , ` { "error": "Path not found"} ` , http . StatusNotFound )
2025-06-06 07:42:55 +00:00
return
}
2025-06-19 13:26:09 +00:00
oldFullPath := "/app/upload/" + existingPath . PathName
newFullPath := "/app/upload/" + pathDownload . PathName
if oldFullPath != newFullPath {
if err := os . Rename ( oldFullPath , newFullPath ) ; err != nil {
http . Error ( w , ` { "error": "Failed to rename directory"} ` , http . StatusInternalServerError )
return
}
}
2025-06-06 07:42:55 +00:00
existingPath . PathName = pathDownload . PathName
2025-06-19 13:26:09 +00:00
existingPath . Path = newFullPath
2025-06-06 07:42:55 +00:00
if err := db . Save ( & existingPath ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to update path"} ` , http . StatusInternalServerError )
return
}
2025-06-19 14:50:30 +00:00
// 👉 On renvoie uniquement la ligne HTML mise à jour
fmt . Fprint ( w , renderPathHTML ( existingPath ) )
2025-06-06 07:42:55 +00:00
}
}
2025-06-19 13:26:09 +00:00
2025-06-06 07:42:55 +00:00
func DeleteSavePath ( db * gorm . DB ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
id := mux . Vars ( r ) [ "id" ]
var pathDownload models . PathDownload
if err := db . First ( & pathDownload , "id = ?" , id ) . Error ; err != nil {
if err == gorm . ErrRecordNotFound {
http . Error ( w , ` { "error": "Path not found"} ` , http . StatusNotFound )
return
}
http . Error ( w , ` { "error": "Failed to retrieve path"} ` , http . StatusInternalServerError )
return
}
2025-06-19 13:26:09 +00:00
// Supprimer physiquement le dossier
fullPath := "/app/upload/" + pathDownload . PathName
if err := os . RemoveAll ( fullPath ) ; err != nil {
http . Error ( w , ` { "error": "Failed to delete directory"} ` , http . StatusInternalServerError )
return
}
2025-06-06 07:42:55 +00:00
if err := db . Delete ( & pathDownload ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to delete path"} ` , http . StatusInternalServerError )
return
}
2025-06-19 13:26:09 +00:00
ReadAllSavePath ( db ) ( w , r )
2025-06-06 07:42:55 +00:00
}
}
2025-06-19 13:26:09 +00:00
2025-06-19 14:41:41 +00:00
func IsPathValid ( pathName string ) error {
if pathName == "" {
return errors . New ( "PathName cannot be empty" )
2025-06-06 07:42:55 +00:00
}
2025-06-19 14:41:41 +00:00
if strings . Contains ( pathName , "/" ) || strings . Contains ( pathName , "\\" ) {
return errors . New ( "PathName cannot contain '/' or '\\'" )
2025-06-06 07:42:55 +00:00
}
2025-06-19 14:41:41 +00:00
fullPath := "/app/upload/" + pathName
if _ , err := os . Stat ( fullPath ) ; err == nil {
return errors . New ( "Path already exists" )
2025-06-06 07:42:55 +00:00
}
2025-06-19 14:41:41 +00:00
// os.Stat() retourne une erreur si le dossier n'existe pas, on ignore cette erreur ici
2025-06-19 13:26:09 +00:00
return nil
2025-06-06 07:42:55 +00:00
}
2025-06-19 14:41:41 +00:00
2025-06-06 07:42:55 +00:00
// PathValidationHandler handles HTTP requests to validate a path
func PathValidationHandler ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodPost {
http . Error ( w , "Invalid request method" , http . StatusMethodNotAllowed )
return
}
var requestBody struct {
2025-06-19 14:37:39 +00:00
PathName string ` json:"pathName" `
2025-06-06 07:42:55 +00:00
}
if err := json . NewDecoder ( r . Body ) . Decode ( & requestBody ) ; err != nil {
http . Error ( w , "Invalid request body" , http . StatusBadRequest )
return
}
2025-06-19 14:37:39 +00:00
err := IsPathValid ( requestBody . PathName )
2025-06-06 07:42:55 +00:00
response := map [ string ] string {
2025-06-19 14:37:39 +00:00
"pathName" : requestBody . PathName ,
2025-06-06 07:42:55 +00:00
"status" : "valid" ,
}
if err != nil {
response [ "status" ] = "invalid"
response [ "error" ] = err . Error ( )
w . WriteHeader ( http . StatusBadRequest )
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
if err := json . NewEncoder ( w ) . Encode ( response ) ; err != nil {
http . Error ( w , "Failed to encode response" , http . StatusInternalServerError )
}
}
2025-06-12 08:57:10 +00:00
2025-06-19 14:37:39 +00:00
2025-06-12 08:57:10 +00:00
type StreamPageData struct {
StreamURL string
}
func HandleStreamPage ( ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
id := mux . Vars ( r ) [ "id" ]
job := jobs [ id ]
if job == nil || job . StreamURL == "" {
http . Error ( w , "Stream non disponible" , http . StatusNotFound )
return
}
tmpl := ` < html >
< head > < title > Streaming < / title > < / head >
< body style = "margin:0;background:#000;" >
< video controls autoplay style = "width:100vw;height:100vh;" >
< source src = "{{.StreamURL}}" type = "video/mp4" >
Votre navigateur ne supporte pas la vidéo HTML5 .
< / video >
< / body >
< / html > `
t := template . Must ( template . New ( "stream" ) . Parse ( tmpl ) )
t . Execute ( w , StreamPageData { StreamURL : job . StreamURL } )
}
}