coda/internal/web/library.go
Rodrigo Verdiani 708dc5fda9 fix: use context.Background() ao enfileirar jobs
O contexto da requisição HTTP era passado para o Enqueue, causando
'context canceled' quando a conexão encerrava antes do INSERT no
SQLite completar. Jobs devem ser persistidos independentemente do
ciclo de vida da requisição.
2026-07-07 08:43:20 -03:00

141 lines
3.3 KiB
Go

package web
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"path/filepath"
"strings"
"coda/internal/jobs"
"coda/internal/models"
)
func (h *Handler) Library(w http.ResponseWriter, r *http.Request) {
entries, err := h.scanner.Browse("")
if err != nil {
slog.Warn("library page browse failed, showing empty view", "error", err)
h.render.Render(w, "library.html", nil)
return
}
h.render.Render(w, "library.html", entries)
}
func (h *Handler) LibraryBrowse(w http.ResponseWriter, r *http.Request) {
relPath := r.URL.Query().Get("path")
entries, err := h.scanner.Browse(relPath)
if err != nil {
slog.Warn("library browse failed", "path", relPath, "error", err)
writeJSON(w, http.StatusOK, []any{})
return
}
writeJSON(w, http.StatusOK, entries)
}
func (h *Handler) LibraryScan(w http.ResponseWriter, r *http.Request) {
albums, err := h.scanner.Scan()
if err != nil {
slog.Error("library scan failed", "error", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if r.Header.Get("HX-Request") != "" {
h.render.Render(w, "scan_results.html", albums)
return
}
writeJSON(w, http.StatusOK, albums)
}
type convertRequest struct {
Paths []string `json:"paths"`
}
func (h *Handler) LibraryConvert(w http.ResponseWriter, r *http.Request) {
var paths []string
if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
var req convertRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
paths = req.Paths
} else {
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
paths = r.Form["paths"]
}
if len(paths) == 0 {
http.Error(w, "no paths provided", http.StatusBadRequest)
return
}
seen := map[string]struct{}{}
resolved := make([]string, 0, len(paths))
for _, relPath := range paths {
relPath = strings.TrimSpace(relPath)
if relPath == "" {
continue
}
if _, ok := seen[relPath]; ok {
continue
}
seen[relPath] = struct{}{}
absPath, err := h.scanner.Resolve(relPath)
if err != nil {
slog.Warn("library convert: invalid path", "path", relPath, "error", err)
http.Error(w, "invalid path: "+relPath, http.StatusBadRequest)
return
}
resolved = append(resolved, absPath)
}
if len(resolved) == 0 {
http.Error(w, "no paths provided", http.StatusBadRequest)
return
}
var jobIDs []string
for _, absPath := range resolved {
album := filepath.Base(absPath)
artist := filepath.Base(filepath.Dir(absPath))
id, err := h.enqueuer.Enqueue(context.Background(), jobs.EnqueueInput{
Artist: artist,
Album: album,
Path: absPath,
Source: models.SourceManual,
})
if err != nil {
slog.Error("library convert enqueue failed", "path", absPath, "error", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
jobIDs = append(jobIDs, id)
}
slog.Info("library convert completed", "jobs", jobIDs)
if r.Header.Get("HX-Request") != "" {
h.render.Render(w, "convert_result.html", len(jobIDs))
return
}
writeJSON(w, http.StatusAccepted, map[string]any{"job_ids": jobIDs})
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(v)
}