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"
"app/shelfly/query"
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"
2025-06-12 08:57:10 +00:00
2025-06-06 07:42:55 +00:00
"github.com/gorilla/mux"
"gorm.io/gorm"
)
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 ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
var pathDownload models . PathDownload
// Décoder les données de la requête
if err := json . NewDecoder ( r . Body ) . Decode ( & pathDownload ) ; err != nil {
http . Error ( w , ` { "error": "Invalid JSON format"} ` , http . StatusBadRequest )
return
}
q := query . Use ( db )
if err := q . PathDownload . Create ( & pathDownload ) ; err != nil {
http . Error ( w , ` { "error": "Failed to create path"} ` , http . StatusInternalServerError )
return
}
// Retourner un élément HTML dynamique pour HTMX
// Récupérer tous les chemins
var paths [ ] models . PathDownload
if err := db . Find ( & paths ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to retrieve paths"} ` , http . StatusInternalServerError )
return
}
// Construire les lignes HTML
var response strings . Builder
for _ , path := range paths {
log . Println ( path )
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 = "path" value = "%s" disabled > < / span >
< input class = "input is-primary fff cell" name = "pathName" value = "%s" disabled > < / span >
< button class = "button is-primary type=" button " 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 . Path , path . PathName , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID ) )
}
w . WriteHeader ( http . StatusOK )
fmt . Fprint ( w , response . String ( ) )
}
}
func ReadAllSavePath ( db * gorm . DB ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "text/html" )
// Récupérer tous les chemins
var paths [ ] models . PathDownload
if err := db . Find ( & paths ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to retrieve paths"} ` , http . StatusInternalServerError )
return
}
// Construire les lignes HTML
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 = "path" value = "%s" disabled > < / span >
< 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 hx-trigger=" click [ checkGlobalState ( ) ] " type=" button " is-dark" id = "btn-path-valider-%d" hx - put = "/api/pathDownload/update/%d" 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 . Path , path . PathName , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID , path . ID ) )
}
// Retourner les lignes HTML
w . WriteHeader ( http . StatusOK )
fmt . Fprint ( w , response . String ( ) )
}
}
func UpdateSavePath ( db * gorm . DB ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
// Récupérer l'ID depuis les paramètres de l'URL
id := mux . Vars ( r ) [ "id" ]
var pathDownload models . PathDownload
log . Println ( pathDownload ) ;
// Décoder les données de la requête
if err := json . NewDecoder ( r . Body ) . Decode ( & pathDownload ) ; err != nil {
http . Error ( w , ` { "error": "Invalid JSON format"} ` , http . StatusBadRequest )
return
}
// Vérifier si la ressource existe
var existingPath models . PathDownload
if err := db . First ( & existingPath , "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
}
// Mettre à jour les champs souhaités
existingPath . Path = pathDownload . Path
existingPath . PathName = pathDownload . PathName
if err := db . Save ( & existingPath ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to update path"} ` , http . StatusInternalServerError )
return
}
response := fmt . Sprintf ( `
< 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 = "path" value = "%s" disabled > < / span >
< 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 type=" button " 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 >
` , existingPath . ID , existingPath . ID , existingPath . Path , existingPath . PathName , existingPath . ID , existingPath . ID , existingPath . ID , existingPath . ID , existingPath . ID , existingPath . ID , existingPath . ID , existingPath . ID , existingPath . ID , existingPath . ID )
w . WriteHeader ( http . StatusOK )
fmt . Fprint ( w , response )
// // Retourner le nouvel élément HTML
// var paths []models.PathDownload
// if err := db.Find(&paths).Error; err != nil {
// http.Error(w, `{"error": "Failed to retrieve paths"}`, http.StatusInternalServerError)
// return
// }
// // Construire les lignes HTML
// var response strings.Builder
// for _, path := range paths {
// log.Println(path)
// response.WriteString(fmt.Sprintf(`
// <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="path" value="%s" disabled></span>
// <input class="input is-primary fff cell" name="pathName" value="%s" disabled></span>
// <button class="button is-primary is-dark" type="button" 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" id="btn-path-annuler-%d" type="button" 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" 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">Delete</button>
// </div>
// </form>
// `, path.ID, path.ID, path.Path, path.PathName,path.ID,path.ID,path.ID, path.ID,path.ID ,path.ID,path.ID,path.ID, path.ID,path.ID))
// }
w . WriteHeader ( http . StatusOK )
//fmt.Fprint(w, response.String())
}
}
func DeleteSavePath ( db * gorm . DB ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
// Récupérer l'ID depuis les paramètres de l'URL
id := mux . Vars ( r ) [ "id" ]
// Vérifier si la ressource existe
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
}
// Supprimer la ressource
if err := db . Delete ( & pathDownload ) . Error ; err != nil {
http . Error ( w , ` { "error": "Failed to delete path"} ` , http . StatusInternalServerError )
return
}
// Répondre avec succès
w . WriteHeader ( http . StatusOK )
fmt . Fprintf ( w , ` < div class = "notification is-primary" id = "notificationPath" style = "display:block" >
< button class = "delete" type = "button" onclick = "hide('notificationPath')" > < / button >
Delete ok
< / div > ` )
}
}
func IsPathValid ( path string ) error {
if path == "" {
return errors . New ( "path is empty" )
}
info , err := os . Stat ( path )
if os . IsNotExist ( err ) {
return errors . New ( "path does not exist" )
}
if err != nil {
return errors . New ( "unable to access path: " + err . Error ( ) )
}
if ! info . IsDir ( ) && ! info . Mode ( ) . IsRegular ( ) {
return errors . New ( "path is neither a file nor a directory" )
}
return nil // Path is valid
}
// 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 {
Path string ` json:"path" `
}
if err := json . NewDecoder ( r . Body ) . Decode ( & requestBody ) ; err != nil {
http . Error ( w , "Invalid request body" , http . StatusBadRequest )
return
}
err := IsPathValid ( requestBody . Path )
response := map [ string ] string {
"path" : requestBody . Path ,
"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
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 } )
}
}