Files
personotes/internal/api/favorites.go
2025-11-12 17:16:13 +01:00

323 lines
8.4 KiB
Go

package api
import (
"encoding/json"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"sort"
"time"
)
// Favorite représente un élément favori (note ou dossier)
type Favorite struct {
Path string `json:"path"`
IsDir bool `json:"is_dir"`
Title string `json:"title"`
AddedAt time.Time `json:"added_at"`
Order int `json:"order"`
}
// FavoritesData contient la liste des favoris
type FavoritesData struct {
Items []Favorite `json:"items"`
}
// getFavoritesFilePath retourne le chemin du fichier de favoris
func (h *Handler) getFavoritesFilePath() string {
return filepath.Join(h.notesDir, ".favorites.json")
}
// loadFavorites charge les favoris depuis le fichier JSON
func (h *Handler) loadFavorites() (*FavoritesData, error) {
path := h.getFavoritesFilePath()
// Si le fichier n'existe pas, retourner une liste vide
if _, err := os.Stat(path); os.IsNotExist(err) {
return &FavoritesData{Items: []Favorite{}}, nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var favorites FavoritesData
if err := json.Unmarshal(data, &favorites); err != nil {
return nil, err
}
// Trier par ordre
sort.Slice(favorites.Items, func(i, j int) bool {
return favorites.Items[i].Order < favorites.Items[j].Order
})
return &favorites, nil
}
// saveFavorites sauvegarde les favoris dans le fichier JSON
func (h *Handler) saveFavorites(favorites *FavoritesData) error {
path := h.getFavoritesFilePath()
data, err := json.MarshalIndent(favorites, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
// handleFavorites route les requêtes /api/favorites/*
func (h *Handler) handleFavorites(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.handleGetFavorites(w, r)
case http.MethodPost:
h.handleAddFavorite(w, r)
case http.MethodDelete:
h.handleRemoveFavorite(w, r)
case http.MethodPut:
h.handleReorderFavorites(w, r)
default:
http.Error(w, "Méthode non autorisée", http.StatusMethodNotAllowed)
}
}
// handleGetFavorites retourne la liste des favoris (HTML)
func (h *Handler) handleGetFavorites(w http.ResponseWriter, r *http.Request) {
// Pas de redirection ici car cet endpoint est utilisé par HTMX ET par fetch()
// depuis le JavaScript pour mettre à jour la liste après ajout/suppression
h.renderFavoritesList(w)
}
// renderFavoritesList rend le template des favoris (méthode interne)
func (h *Handler) renderFavoritesList(w http.ResponseWriter) {
favorites, err := h.loadFavorites()
if err != nil {
h.logger.Printf("Erreur chargement favoris: %v", err)
// En cas d'erreur, retourner une liste vide plutôt qu'une erreur 500
favorites = &FavoritesData{Items: []Favorite{}}
}
// Enrichir avec les informations des fichiers
enrichedFavorites := []map[string]interface{}{}
for _, fav := range favorites.Items {
absPath := filepath.Join(h.notesDir, fav.Path)
// Vérifier si le fichier/dossier existe toujours
if _, err := os.Stat(absPath); os.IsNotExist(err) {
continue // Skip les favoris qui n'existent plus
}
item := map[string]interface{}{
"Path": fav.Path,
"IsDir": fav.IsDir,
"Title": fav.Title,
"Icon": getIcon(fav.IsDir, fav.Path),
}
enrichedFavorites = append(enrichedFavorites, item)
}
data := map[string]interface{}{
"Favorites": enrichedFavorites,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := h.templates.ExecuteTemplate(w, "favorites.html", data); err != nil {
h.logger.Printf("Erreur template favoris: %v", err)
http.Error(w, "Erreur de rendu", http.StatusInternalServerError)
}
}
// handleAddFavorite ajoute un élément aux favoris
func (h *Handler) handleAddFavorite(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
h.logger.Printf("Erreur ParseForm: %v", err)
http.Error(w, "Formulaire invalide", http.StatusBadRequest)
return
}
path := r.FormValue("path")
isDir := r.FormValue("is_dir") == "true"
title := r.FormValue("title")
h.logger.Printf("handleAddFavorite: path='%s', is_dir='%s', title='%s'", path, r.FormValue("is_dir"), title)
if path == "" {
h.logger.Printf("Erreur: chemin vide")
http.Error(w, "Chemin requis", http.StatusBadRequest)
return
}
// Valider que le fichier/dossier existe
absPath := filepath.Join(h.notesDir, path)
if _, err := os.Stat(absPath); os.IsNotExist(err) {
http.Error(w, "Fichier/dossier introuvable", http.StatusNotFound)
return
}
// Si pas de titre, utiliser le nom du fichier
if title == "" {
title = filepath.Base(path)
if !isDir && filepath.Ext(title) == ".md" {
title = title[:len(title)-3]
}
}
favorites, err := h.loadFavorites()
if err != nil {
h.logger.Printf("Erreur chargement favoris: %v", err)
http.Error(w, "Erreur de chargement", http.StatusInternalServerError)
return
}
// Vérifier si déjà en favoris
for _, fav := range favorites.Items {
if fav.Path == path {
http.Error(w, "Déjà en favoris", http.StatusConflict)
return
}
}
// Ajouter le nouveau favori
newFavorite := Favorite{
Path: path,
IsDir: isDir,
Title: title,
AddedAt: time.Now(),
Order: len(favorites.Items),
}
favorites.Items = append(favorites.Items, newFavorite)
if err := h.saveFavorites(favorites); err != nil {
h.logger.Printf("Erreur sauvegarde favoris: %v", err)
http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
return
}
// Retourner la liste mise à jour
h.renderFavoritesList(w)
}
// handleRemoveFavorite retire un élément des favoris
func (h *Handler) handleRemoveFavorite(w http.ResponseWriter, r *http.Request) {
// Pour DELETE, il faut toujours lire le body manuellement
// car ParseForm() ne lit pas le body pour les méthodes DELETE
body, err := io.ReadAll(r.Body)
if err != nil {
h.logger.Printf("Erreur lecture body: %v", err)
http.Error(w, "Erreur lecture requête", http.StatusBadRequest)
return
}
r.Body.Close()
values, err := url.ParseQuery(string(body))
if err != nil {
h.logger.Printf("Erreur parsing query: %v", err)
http.Error(w, "Erreur parsing requête", http.StatusBadRequest)
return
}
path := values.Get("path")
if path == "" {
h.logger.Printf("Chemin requis manquant dans la requête")
http.Error(w, "Chemin requis", http.StatusBadRequest)
return
}
favorites, err := h.loadFavorites()
if err != nil {
h.logger.Printf("Erreur chargement favoris: %v", err)
http.Error(w, "Erreur de chargement", http.StatusInternalServerError)
return
}
// Retirer le favori
newItems := []Favorite{}
found := false
for _, fav := range favorites.Items {
if fav.Path != path {
newItems = append(newItems, fav)
} else {
found = true
}
}
if !found {
http.Error(w, "Favori introuvable", http.StatusNotFound)
return
}
// Réorganiser les ordres
for i := range newItems {
newItems[i].Order = i
}
favorites.Items = newItems
if err := h.saveFavorites(favorites); err != nil {
h.logger.Printf("Erreur sauvegarde favoris: %v", err)
http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
return
}
// Retourner la liste mise à jour
h.renderFavoritesList(w)
}
// handleReorderFavorites réorganise l'ordre des favoris
func (h *Handler) handleReorderFavorites(w http.ResponseWriter, r *http.Request) {
var order []string
if err := json.NewDecoder(r.Body).Decode(&order); err != nil {
http.Error(w, "JSON invalide", http.StatusBadRequest)
return
}
favorites, err := h.loadFavorites()
if err != nil {
h.logger.Printf("Erreur chargement favoris: %v", err)
http.Error(w, "Erreur de chargement", http.StatusInternalServerError)
return
}
// Créer un map pour retrouver les favoris rapidement
favMap := make(map[string]*Favorite)
for i := range favorites.Items {
favMap[favorites.Items[i].Path] = &favorites.Items[i]
}
// Réorganiser selon le nouvel ordre
newItems := []Favorite{}
for i, path := range order {
if fav, ok := favMap[path]; ok {
fav.Order = i
newItems = append(newItems, *fav)
}
}
favorites.Items = newItems
if err := h.saveFavorites(favorites); err != nil {
h.logger.Printf("Erreur sauvegarde favoris: %v", err)
http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
// getIcon retourne l'icône appropriée pour un fichier/dossier
func getIcon(isDir bool, path string) string {
if isDir {
return "📁"
}
return "📄"
}