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) { 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.handleGetFavorites(w, r) } // handleRemoveFavorite retire un élément des favoris func (h *Handler) handleRemoveFavorite(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { // Pour DELETE, il faut lire le body manuellement body, _ := io.ReadAll(r.Body) r.Body.Close() values, _ := url.ParseQuery(string(body)) r.Form = values } path := r.FormValue("path") if path == "" { 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.handleGetFavorites(w, r) } // 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 "📄" }