shelfy/internal/route/main.go
2025-07-15 17:25:58 +02:00

351 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package route
import (
"app/shelfly/internal/download"
"app/shelfly/internal/library"
"app/shelfly/internal/login"
"app/shelfly/internal/models"
"app/shelfly/internal/users"
"app/shelfly/renders"
"encoding/base64"
"fmt"
"golang.org/x/crypto/bcrypt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/gorilla/mux"
"golang.org/x/net/webdav"
"gorm.io/gorm"
)
func checkUserCredentials(db *gorm.DB, email string, password string) bool {
var user models.User
// On cherche l'utilisateur par email
result := db.Where("email = ?", email).First(&user)
if result.Error != nil {
return false
}
// On vérifie le mot de passe via bcrypt comme dans ton LoginHandler
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
return err == nil
}
type spaHandler struct {
staticPath string
indexPath string
}
// Routes non protégées
func checkAuth(authHeader, username, password string) bool {
const prefix = "Basic "
if !strings.HasPrefix(authHeader, prefix) {
return false
}
decoded, err := base64.StdEncoding.DecodeString(authHeader[len(prefix):])
if err != nil {
return false
}
pair := strings.SplitN(string(decoded), ":", 2)
if len(pair) != 2 {
return false
}
return pair[0] == username && pair[1] == password
}
func RoutesPublic(r *mux.Router, bd *gorm.DB) {
// Fichiers statiques (CSS, JS, etc.)
staticDir := "./templates/assets/"
r.PathPrefix("/templates/assets/").Handler(
http.StripPrefix("/templates/assets/", http.FileServer(http.Dir(staticDir))),
)
r.PathPrefix("/static/").Handler(
http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
// Page de login
r.HandleFunc("/login", renders.Login)
r.HandleFunc("/api/login", login.LoginHandler(bd)).Methods("POST")
r.HandleFunc("/apiV2/login", login.LoginHandlerApi(bd)).Methods("POST")
r.HandleFunc("/api/scan/{id}", library.ScanFolder(bd)).Methods("GET")
r.HandleFunc("/api/download/stream", renders.HandleJobsStream(bd))
// Génération playlist
r.HandleFunc("/playlist.m3u", func(w http.ResponseWriter, r *http.Request) {
uploadDir := "/app/upload"
w.Header().Set("Content-Type", "audio/x-mpegurl")
fmt.Fprintln(w, "#EXTM3U")
err := filepath.Walk(uploadDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
relPath, _ := filepath.Rel(uploadDir, path)
relPath = filepath.ToSlash(relPath)
fileURL := fmt.Sprintf("https://media.canguidev.fr/upload/%s", relPath)
fmt.Fprintln(w, fileURL)
return nil
})
if err != nil {
http.Error(w, "Erreur lors de la génération de la playlist", http.StatusInternalServerError)
}
})
r.PathPrefix("/webdav/").Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authHeader := req.Header.Get("Authorization")
if authHeader == "" {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Authentification HTTP Basic en base de données
email, password, ok := req.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
log.Printf("✅ email saisi: %s", email)
log.Printf("✅ password saisi: %s", password)
var user models.User
result := bd.Where("email = ?", email).First(&user)
if result.Error != nil {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// ✅ Ici on autorise TOUTES les méthodes WebDAV (lecture/écriture/suppression)
log.Printf("✅ WebDAV FULL ACCESS for user: %s", email)
// Headers WebDAV que certains clients attendent
w.Header().Set("DAV", "1,2")
w.Header().Set("MS-Author-Via", "DAV")
// Handler WebDAV complet
webdavHandler := &webdav.Handler{
Prefix: "/webdav/",
FileSystem: webdav.Dir("/app/upload"),
LockSystem: webdav.NewMemLS(),
}
webdavHandler.ServeHTTP(w, req)
}))
// WebDAV sécurisé
// username := "tonuser" // ton login
// password := "tonpassword" // ton password
// webdavHandler := &webdav.Handler{
// Prefix: "/webdav/",
// FileSystem: webdav.Dir("/app/upload"),
// LockSystem: webdav.NewMemLS(),
// }
// r.PathPrefix("/webdav/").Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// // Authentification
// auth := req.Header.Get("Authorization")
// if auth == "" || !checkAuth(auth, username, password) {
// w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
// http.Error(w, "Unauthorized", http.StatusUnauthorized)
// return
// }
// // Protection lecture seule
// if req.Method != "GET" && req.Method != "HEAD" && req.Method != "OPTIONS" && req.Method != "PROPFIND" {
// http.Error(w, "Read-Only", http.StatusForbidden)
// return
// }
// log.Printf("WebDAV request: %s %s", req.Method, req.URL.Path)
// // Headers WebDAV que VLC attend
// w.Header().Set("DAV", "1,2")
// w.Header().Set("MS-Author-Via", "DAV")
// webdavHandler.ServeHTTP(w, req)
// }))
}
// Routes protégées
func RoutesProtected(r *mux.Router, bd *gorm.DB) {
// Ici on place les vues et API qui doivent être protégées
r.HandleFunc("/stream", StreamHandler)
r.HandleFunc("/dashboard", renders.Dashboard(bd))
r.HandleFunc("/settings", renders.Settings)
r.HandleFunc("/library", renders.Library)
r.HandleFunc("/menuLibary", renders.Library)
r.HandleFunc("/godownloader/downloads", renders.GoDownload)
r.HandleFunc("/godownloader/linkcollectors", renders.GoDownloadLinkCollectors)
r.HandleFunc("/godownloader/settings", renders.GoDownloadSetting(bd))
r.HandleFunc("/godownloader/poll-status", renders.PollStatusHandler(bd))
r.HandleFunc("/godownloader/table-refresh", renders.GoDownloadPartialTable(bd))
r.HandleFunc("/godownloader/settings/delete", renders.GoDownloadSettingDelete(bd))
r.HandleFunc("/api/download/add", renders.HandleAddJob(bd)).Methods("POST")
r.HandleFunc("/api/download/all", renders.HandleListJobsPartial(bd)).Methods("GET")
r.HandleFunc("/downloads", renders.GoDownload2(bd))
r.HandleFunc("/stream/{id}", download.HandleStreamPage()).Methods("GET")
r.HandleFunc("/api/download/start/{id}", renders.HandleStartJob(bd)).Methods("POST")
r.HandleFunc("/api/download/pause/{id}", renders.HandlePauseJob).Methods("POST")
r.HandleFunc("/api/download/resume/{id}", renders.HandleResumeJob(bd)).Methods("POST")
r.HandleFunc("/api/download/delete/{id}", renders.HandleDeleteJob(bd)).Methods("DELETE")
r.HandleFunc("/api/download/delete-multiple", renders.HandleDeleteMultipleJobs(bd)).Methods("POST")
// API user
r.HandleFunc("/api/user/create", users.CreateUser(bd)).Methods("POST")
r.HandleFunc("/api/user/update/{id}", users.UpdateUser(bd)).Methods("PUT")
r.HandleFunc("/api/user/delete/{id}", users.DeleteUser(bd)).Methods("DELETE")
r.HandleFunc("/api/user/all/", users.ReadAllUser(bd)).Methods("GET")
r.HandleFunc("/api/user/{id}", users.FindUserById(bd)).Methods("GET")
// API download
r.HandleFunc("/api/pathDownload/create", download.CreateSavePath(bd)).Methods("POST")
r.HandleFunc("/api/pathDownload/update/{id}", download.UpdateSavePath(bd)).Methods("PUT")
r.HandleFunc("/api/pathDownload/delete/{id}", download.DeleteSavePath(bd)).Methods("DELETE")
r.HandleFunc("/api/pathDownload/all/", download.ReadAllSavePath(bd)).Methods("GET")
r.HandleFunc("/api/download/add-multiple", renders.HandleAddJobsMultiple(bd)).Methods("POST")
//API Check path
r.HandleFunc("/validate-path", download.PathValidationHandler)
r.HandleFunc("/folders", renders.StreamHandler)
r.HandleFunc("/folders/detail", renders.DetailHandler).Methods("GET")
r.HandleFunc("/api/paths/{id:[0-9]+}/media", renders.PathMedia(bd)).Methods("GET")
// r.HandleFunc("/stream/{partID:[0-9]+}", renders.Stream(bd)).Methods("GET")
r.HandleFunc("/media/{partID:[0-9]+}", renders.MediaDetail(bd)).Methods("GET")
r.HandleFunc("/hls/{partID:[0-9]+}/{file}", renders.HLSStream(bd)).Methods("GET")
r.HandleFunc("/hls/{partID:[0-9]+}/", renders.HLSStream(bd)).Methods("GET")
//API Scan folder
// —————— JSON API routes apiV2 ——————
r.HandleFunc("/apiV2/dashboard", renders.DashboardJSON(bd)).Methods("GET")
r.HandleFunc("/apiV2/menu-library", renders.MenuLibraryJSON(bd)).Methods("GET")
r.HandleFunc("/apiV2/settings", renders.SettingsJSON()).Methods("GET")
r.HandleFunc("/apiV2/library", renders.LibraryJSON()).Methods("GET")
r.HandleFunc("/apiV2/godownloader/download", renders.GoDownloadJSON()).Methods("GET")
r.HandleFunc("/apiV2/godownloader/linkcollectors", renders.GoDownloadLinkCollectorsJSON()).Methods("GET")
r.HandleFunc("/apiV2/godownloader/settings/delete", renders.GoDownloadSettingDeleteJSON(bd)).Methods("POST")
r.HandleFunc("/apiV2/godownloader/settings/toggle", renders.GoDownloadSettingToggleActiveJSON(bd)).Methods("POST")
r.HandleFunc("/apiV2/godownloader/settings", renders.GoDownloadSettingJSON(bd)).Methods("GET", "POST")
r.HandleFunc("/apiV2/godownloader/settings/table", renders.GoDownloadPartialTableJSON(bd)).Methods("GET")
r.HandleFunc("/apiV2/godownloader2", renders.GoDownload2JSON(bd)).Methods("GET")
r.HandleFunc("/apiV2/add-job", renders.HandleAddJobJSON(bd)).Methods("POST")
r.HandleFunc("/apiV2/jobs/list", renders.HandleListJobsPartialJSON(bd)).Methods("GET")
r.HandleFunc("/apiV2/add-jobs-multiple", renders.HandleAddJobsMultipleJSON(bd)).Methods("POST")
r.HandleFunc("/apiV2/stream", renders.StreamHandlerJSON()).Methods("GET")
r.HandleFunc("/apiV2/pathmedia/{id}", renders.PathMediaJSON(bd)).Methods("GET")
//r.HandleFunc("/api/media/detail/{partID}", renders.MediaDetailJSON(bd)).Methods("GET")
}
// func RoutesProtected(r *mux.Router, db *gorm.DB) {
// // —————— HTML routes ——————
// r.HandleFunc("/login", Login).Methods("GET")
// r.HandleFunc("/dashboard", Dashboard(db)).Methods("GET")
// r.HandleFunc("/menu-library", MenuLibrary(db)).Methods("GET")
// r.HandleFunc("/settings", Settings).Methods("GET")
// r.HandleFunc("/library", Library).Methods("GET")
// r.HandleFunc("/godownloader/download", GoDownload).Methods("GET")
// r.HandleFunc("/godownloader/linkcollectors", GoDownloadLinkCollectors).Methods("GET")
// r.HandleFunc("/godownloader/settings/delete", GoDownloadSettingDelete(db)).Methods("GET")
// r.HandleFunc("/godownloader/settings/toggle", GoDownloadSettingToggleActive(db)).Methods("GET")
// r.HandleFunc("/godownloader/settings", GoDownloadSetting(db)).Methods("GET", "POST")
// r.HandleFunc("/godownloader/settings/table", GoDownloadPartialTable(db)).Methods("GET")
// r.HandleFunc("/godownloader2", GoDownload2(db)).Methods("GET")
// r.HandleFunc("/add-job", HandleAddJob(db)).Methods("POST")
// r.HandleFunc("/jobs/stream", HandleJobsStream(db)).Methods("GET")
// r.HandleFunc("/jobs/list", HandleListJobsPartial(db)).Methods("GET")
// r.HandleFunc("/jobs/start/{id}", HandleStartJob(db)).Methods("POST")
// r.HandleFunc("/jobs/pause/{id}", HandlePauseJob).Methods("POST")
// r.HandleFunc("/jobs/resume/{id}", HandleResumeJob(db)).Methods("POST")
// r.HandleFunc("/jobs/delete/{id}", HandleDeleteJob(db)).Methods("POST")
// r.HandleFunc("/jobs/delete-multiple", HandleDeleteMultipleJobs(db)).Methods("POST")
// r.HandleFunc("/stream", StreamHandler).Methods("GET")
// r.HandleFunc("/detail", DetailHandler).Methods("GET")
// r.HandleFunc("/add-jobs-multiple", HandleAddJobsMultiple(db)).Methods("POST")
// r.HandleFunc("/pathmedia/{id}", PathMedia(db)).Methods("GET")
// r.HandleFunc("/media/detail/{partID}", MediaDetail(db)).Methods("GET")
// r.PathPrefix("/hls/").Handler(HLSStream(db))
// // —————— JSON API routes ——————
// r.HandleFunc("/api/dashboard", DashboardJSON(db)).Methods("GET")
// r.HandleFunc("/api/menu-library", MenuLibraryJSON(db)).Methods("GET")
// r.HandleFunc("/api/settings", SettingsJSON()).Methods("GET")
// r.HandleFunc("/api/library", LibraryJSON()).Methods("GET")
// r.HandleFunc("/api/godownloader/download", GoDownloadJSON()).Methods("GET")
// r.HandleFunc("/api/godownloader/linkcollectors", GoDownloadLinkCollectorsJSON()).Methods("GET")
// r.HandleFunc("/api/godownloader/settings/delete", GoDownloadSettingDeleteJSON(db)).Methods("POST")
// r.HandleFunc("/api/godownloader/settings/toggle", GoDownloadSettingToggleActiveJSON(db)).Methods("POST")
// r.HandleFunc("/api/godownloader/settings", GoDownloadSettingJSON(db)).Methods("GET", "POST")
// r.HandleFunc("/api/godownloader/settings/table", GoDownloadPartialTableJSON(db)).Methods("GET")
// r.HandleFunc("/api/godownloader2", GoDownload2JSON(db)).Methods("GET")
// r.HandleFunc("/api/add-job", HandleAddJobJSON(db)).Methods("POST")
// r.HandleFunc("/api/jobs/list", HandleListJobsPartialJSON(db)).Methods("GET")
// r.HandleFunc("/api/add-jobs-multiple", HandleAddJobsMultipleJSON(db)).Methods("POST")
// r.HandleFunc("/api/stream", StreamHandlerJSON()).Methods("GET")
// r.HandleFunc("/api/pathmedia/{id}", PathMediaJSON(db)).Methods("GET")
// r.HandleFunc("/api/media/detail/{partID}", MediaDetailJSON(db)).Methods("GET")
// }
func StreamHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Le streaming nest pas supporté par ce serveur", http.StatusInternalServerError)
return
}
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
// Boucle infinie (ou jusqu'à annulation)
for {
select {
case <-ticker.C:
fmt.Fprintf(w, "data: <p>Message #%d</p>\n\n")
flusher.Flush()
case <-r.Context().Done():
// Le client a probablement fermé la connexion
log.Println("Client déconnecté")
return
}
}
}