setName(e.target.value)}
spellCheck={false}
/>
-
setTemplate(e.target.value)}
- spellCheck={false}
- autoComplete="off"
- autoCorrect="off"
- autoCapitalize="off"
- />
+
+
setTemplate(e.target.value)}
+ spellCheck={false}
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ />
+
+
-
Preview
-
-
→
-
-
+
+ {TEMPLATE_VARS.map(({ name: varName, example, enriched }) => (
+ insertVariable(varName)}
+ className="flex items-baseline gap-1.5 bg-surface border border-ui-border rounded-[6px] px-2.5 py-1.5 text-[12px] text-white cursor-pointer transition-colors hover:border-accent hover:text-accent"
+ >
+ {varName}
+ {example}
+ {enriched && * }
+
+ ))}
-
-
Insert variable
-
- {TEMPLATE_VARS.map(([varName, example]) => (
-
insertVariable(varName)}
- className="flex items-baseline gap-1.5 bg-surface border border-ui-border rounded-[6px] px-2.5 py-1.5 text-[12px] text-white cursor-pointer transition-colors hover:border-accent hover:text-accent"
- >
- {varName}
- {example}
-
- ))}
+
+ Click to insert at cursor. Variables marked * need ENRICH_METADATA=true. Illegal chars (/ \ : * ? " < > |) are stripped.
+
-
-
- Click to insert at cursor. Illegal characters{' '}
- {['/', '\\', ':', '*', '?', '"', '<', '>', '|'].map(c => (
- {c}
- ))}{' '}
- in a value are stripped automatically.
-
{/* Footer */}
diff --git a/src/web/frontend/src/lib/utils.js b/src/web/frontend/src/lib/utils.js
index 7d5a429d..56bbd2f1 100644
--- a/src/web/frontend/src/lib/utils.js
+++ b/src/web/frontend/src/lib/utils.js
@@ -2,7 +2,9 @@ function escHtml(s) {
return s.replace(/&/g, '&').replace(//g, '>')
}
-export function highlightEnv(text) {
+const SENSITIVE_KEY_RE = /password|token|api_key|secret|accesstoken/i
+
+export function highlightEnv(text, masked = false) {
return text.split('\n').map(line => {
const trimmed = line.trim()
if (!trimmed) return ''
@@ -12,7 +14,8 @@ export function highlightEnv(text) {
const key = line.slice(0, eq)
const val = line.slice(eq + 1).trim()
if (!val) return `
${escHtml(line)} `
- return `
${escHtml(key)} = ${escHtml(line.slice(eq + 1))} `
+ const displayVal = (masked && SENSITIVE_KEY_RE.test(key)) ? '••••••••' : escHtml(line.slice(eq + 1))
+ return `
${escHtml(key)} = ${displayVal} `
}
return escHtml(line)
}).join('\n')
From f30f7f3ec0f9aa1731aaf77e4de82dd8d89e0988 Mon Sep 17 00:00:00 2001
From: dammitjeff <44111923+dammitjeff@users.noreply.github.com>
Date: Thu, 11 Jun 2026 12:59:53 -0700
Subject: [PATCH 4/5] - added new API endpoints for managing path template
presets (GET, POST, DELETE). - Added toggles for ENRICH_METADATA and
PATH_TEMPLATE
---
src/web/backend/defs.go | 2 +-
src/web/backend/path_templates.go | 112 ++++++++++++++++++
src/web/backend/server.go | 37 ++++++
src/web/frontend/src/components/Settings.jsx | 102 ++++++++++++++--
.../src/components/ui/PathTemplateModal.jsx | 37 +++---
src/web/frontend/src/components/ui/common.jsx | 2 +-
src/web/frontend/src/lib/api.js | 32 ++++-
7 files changed, 295 insertions(+), 29 deletions(-)
create mode 100644 src/web/backend/path_templates.go
diff --git a/src/web/backend/defs.go b/src/web/backend/defs.go
index 50b2292e..e5d769e8 100644
--- a/src/web/backend/defs.go
+++ b/src/web/backend/defs.go
@@ -156,7 +156,7 @@ var allConfigKeys = []string{
"ON_REPEAT_SCHEDULE", "ON_REPEAT_FLAGS",
"EXPLO_SYSTEM", "SYSTEM_URL", "API_KEY", "LIBRARY_NAME",
"SYSTEM_USERNAME", "SYSTEM_PASSWORD", "PLAYLIST_DIR", "SLEEP", "PUBLIC_PLAYLIST",
- "DOWNLOAD_DIR", "USE_SUBDIRECTORY", "PATH_TEMPLATE",
+ "DOWNLOAD_DIR", "USE_SUBDIRECTORY", "PATH_TEMPLATE", "ENRICH_METADATA",
"DOWNLOAD_SERVICES", "YOUTUBE_API_KEY", "TRACK_EXTENSION", "FILTER_LIST",
"SLSKD_URL", "SLSKD_API_KEY",
"WIZARD_COMPLETE", "MIGRATE_DOWNLOADS", "EXTENSIONS",
diff --git a/src/web/backend/path_templates.go b/src/web/backend/path_templates.go
new file mode 100644
index 00000000..0617a65b
--- /dev/null
+++ b/src/web/backend/path_templates.go
@@ -0,0 +1,112 @@
+package backend
+
+import (
+ "encoding/json"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// PathTemplatePreset is a named folder-structure template saved by the user.
+type PathTemplatePreset struct {
+ Name string `json:"name"`
+ Template string `json:"template"`
+}
+
+func pathTemplatesFilePath(cfgDir string) string {
+ return filepath.Join(cfgDir, "path-templates.json")
+}
+
+func loadPathTemplates(cfgDir string) []PathTemplatePreset {
+ data, err := os.ReadFile(pathTemplatesFilePath(cfgDir))
+ if err != nil {
+ return nil
+ }
+ var out []PathTemplatePreset
+ if err := json.Unmarshal(data, &out); err != nil {
+ slog.Warn("path-templates: failed to parse", "err", err)
+ return nil
+ }
+ return out
+}
+
+func savePathTemplates(cfgDir string, presets []PathTemplatePreset) error {
+ raw, err := json.MarshalIndent(presets, "", " ")
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(pathTemplatesFilePath(cfgDir), raw, 0644)
+}
+
+// handlePathTemplates handles GET and POST for /api/ui/path-templates.
+func (s *Server) handlePathTemplates(w http.ResponseWriter, r *http.Request) {
+ cfgDir := s.cfg.WebDataDir
+ switch r.Method {
+ case http.MethodGet:
+ presets := loadPathTemplates(cfgDir)
+ if presets == nil {
+ presets = []PathTemplatePreset{}
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(presets); err != nil {
+ slog.Error("failed encoding path templates", "err", err.Error())
+ }
+ case http.MethodPost:
+ var body PathTemplatePreset
+ if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+ http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+ if body.Name == "" || body.Template == "" {
+ http.Error(w, "name and template are required", http.StatusBadRequest)
+ return
+ }
+ presets := loadPathTemplates(cfgDir)
+ presets = append(presets, body)
+ if err := savePathTemplates(cfgDir, presets); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ if err := json.NewEncoder(w).Encode(body); err != nil {
+ slog.Error("failed encoding path template", "err", err.Error())
+ }
+ default:
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ }
+}
+
+// handleDeletePathTemplate handles DELETE /api/ui/path-templates/{name}.
+func (s *Server) handleDeletePathTemplate(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodDelete {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+ raw := strings.TrimPrefix(r.URL.Path, "/api/ui/path-templates/")
+ name, err := url.PathUnescape(raw)
+ if err != nil || name == "" {
+ http.Error(w, "invalid name", http.StatusBadRequest)
+ return
+ }
+ cfgDir := s.cfg.WebDataDir
+ presets := loadPathTemplates(cfgDir)
+ filtered := presets[:0]
+ for _, p := range presets {
+ if p.Name != name {
+ filtered = append(filtered, p)
+ }
+ }
+ if len(filtered) == len(presets) {
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ if err := savePathTemplates(cfgDir, filtered); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+}
diff --git a/src/web/backend/server.go b/src/web/backend/server.go
index 9ee548e5..336f505e 100644
--- a/src/web/backend/server.go
+++ b/src/web/backend/server.go
@@ -244,6 +244,19 @@ func (s *Server) registerRoutes() {
s.mux.Handle("/api/ui/config/reset", s.authStore.RequireAuth(http.HandlerFunc(s.handleResetConfig)))
s.mux.Handle("/api/ui/config/schedules", s.authStore.RequireAuth(http.HandlerFunc(s.handleSaveSchedule)))
s.mux.Handle("/api/ui/config/path-template", s.authStore.RequireAuth(http.HandlerFunc(s.handleSavePathTemplate)))
+ s.mux.Handle("/api/ui/config/enrich-metadata", s.authStore.RequireAuth(http.HandlerFunc(s.handleSaveEnrichMetadata)))
+
+ // Path template presets: GET list, POST add; DELETE per name under prefix
+ s.mux.HandleFunc("/api/ui/path-templates", func(w http.ResponseWriter, r *http.Request) {
+ s.authStore.RequireAuth(http.HandlerFunc(s.handlePathTemplates)).ServeHTTP(w, r)
+ })
+ s.mux.HandleFunc("/api/ui/path-templates/", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodDelete {
+ s.authStore.RequireAuth(http.HandlerFunc(s.handleDeletePathTemplate)).ServeHTTP(w, r)
+ return
+ }
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ })
// Wizard steps (POST) — require auth
s.mux.Handle("/api/ui/wizard/step1", s.authStore.RequireAuth(http.HandlerFunc(s.handleWizardStep1)))
@@ -587,6 +600,30 @@ func (s *Server) handleSavePathTemplate(w http.ResponseWriter, r *http.Request)
w.WriteHeader(http.StatusOK)
}
+// handleSaveEnrichMetadata writes ENRICH_METADATA=true/false to the .env file.
+func (s *Server) handleSaveEnrichMetadata(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+ var body struct {
+ Enabled bool `json:"enabled"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+ http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+ val := "false"
+ if body.Enabled {
+ val = "true"
+ }
+ if err := updateEnvKeys(s.cfg.WebEnvPath, map[string]string{"ENRICH_METADATA": val}, web.SampleEnv); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+}
+
// updateEnvKeys reads the env file (falling back to fallback if missing), updates the
// given key=value pairs in-place preserving comments, and writes the result back.
func updateEnvKeys(path string, updates map[string]string, fallback []byte) error {
diff --git a/src/web/frontend/src/components/Settings.jsx b/src/web/frontend/src/components/Settings.jsx
index 0785521c..822968eb 100644
--- a/src/web/frontend/src/components/Settings.jsx
+++ b/src/web/frontend/src/components/Settings.jsx
@@ -14,7 +14,8 @@ import { useState, useEffect, useCallback, useRef } from 'react'
import {
fetchConfig, fetchConfigRaw, saveConfig, resetConfig,
saveSchedule, startRun, stopRun, fetchRunStatus, fetchLogs,
- fetchCustomPlaylists, deleteCustomPlaylist, savePathTemplate,
+ fetchCustomPlaylists, deleteCustomPlaylist, savePathTemplate, saveEnrichMetadata,
+ fetchPathTemplatePresets, addPathTemplatePreset, deletePathTemplatePreset,
} from '../lib/api'
import { parseSlogLine, cronToFields, highlightEnv } from '../lib/utils'
import { fetchPlaylistTracks } from '../lib/listenbrainz'
@@ -503,26 +504,65 @@ function DownloadPathSection() {
const [saveStatus, setSaveStatus] = useState('')
const [showModal, setShowModal] = useState(false)
const [openMenuIdx, setOpenMenuIdx] = useState(null)
+ const [enrichEnabled, setEnrichEnabled] = useState(false)
+ const [templateEnabled, setTemplateEnabled] = useState(false)
useEffect(() => {
- fetchConfig().then(({ values }) => {
+ Promise.all([
+ fetchConfig(),
+ fetchPathTemplatePresets().catch(() => []),
+ ]).then(([{ values }, jsonPresets]) => {
+ const allProfiles = [
+ ...SEED_PRESETS.map(p => ({ ...p, seed: true })),
+ ...jsonPresets,
+ ]
+ setEnrichEnabled(values.ENRICH_METADATA === 'true')
const t = values.PATH_TEMPLATE || ''
if (t) {
- const idx = SEED_PRESETS.findIndex(p => p.template === t)
+ setTemplateEnabled(true)
+ const idx = allProfiles.findIndex(p => p.template === t)
if (idx >= 0) {
+ setProfiles(allProfiles)
setAppliedIdx(idx)
setSelectedIdx(idx)
} else {
- const customIdx = SEED_PRESETS.length
- setProfiles([...SEED_PRESETS.map(p => ({ ...p, seed: true })), { name: 'Custom', template: t }])
+ const customIdx = allProfiles.length
+ setProfiles([...allProfiles, { name: 'Custom', template: t }])
setAppliedIdx(customIdx)
setSelectedIdx(customIdx)
}
+ } else {
+ setProfiles(allProfiles)
}
setLoaded(true)
})
}, [])
+ const handleEnrichToggle = async () => {
+ const next = !enrichEnabled
+ setEnrichEnabled(next)
+ try { await saveEnrichMetadata(next) } catch { setEnrichEnabled(!next) }
+ }
+
+ const handleTemplateToggle = async () => {
+ if (templateEnabled) {
+ setTemplateEnabled(false)
+ setAppliedIdx(null)
+ setSelectedIdx(null)
+ try { await savePathTemplate('') } catch { setTemplateEnabled(true) }
+ } else {
+ setTemplateEnabled(true)
+ if (selectedIdx === null) setSelectedIdx(0)
+ }
+ }
+
+ useEffect(() => {
+ if (!showModal) return
+ const handle = e => { e.preventDefault(); e.returnValue = '' }
+ window.addEventListener('beforeunload', handle)
+ return () => window.removeEventListener('beforeunload', handle)
+ }, [showModal])
+
useEffect(() => {
if (openMenuIdx === null) return
const handle = () => setOpenMenuIdx(null)
@@ -534,7 +574,11 @@ function DownloadPathSection() {
if (!loaded) return null
- const handleDeleteProfile = i => {
+ const handleDeleteProfile = async (i) => {
+ const profile = profiles[i]
+ if (!profile.seed) {
+ try { await deletePathTemplatePreset(profile.name) } catch {}
+ }
const newApplied = appliedIdx === i ? null : appliedIdx !== null && appliedIdx > i ? appliedIdx - 1 : appliedIdx
const newSelected = selectedIdx === i ? newApplied : selectedIdx !== null && selectedIdx > i ? selectedIdx - 1 : selectedIdx
setProfiles(prev => prev.filter((_, j) => j !== i))
@@ -558,7 +602,8 @@ function DownloadPathSection() {
}
}
- const handleSavePreset = ({ name, template }) => {
+ const handleSavePreset = async ({ name, template }) => {
+ try { await addPathTemplatePreset(name, template) } catch {}
const newIdx = profiles.length
setProfiles(prev => [...prev, { name, template }])
setSelectedIdx(newIdx)
@@ -568,9 +613,41 @@ function DownloadPathSection() {
return (
Folder Structure
+ {/* ENRICH_METADATA toggle */}
+
+
+ Auto-tag songs
+ Looks up track numbers, year, genre & more from MusicBrainz and writes them to downloaded files.
+
+
+
+
+
+ {/* Organize into folders toggle */}
+
+
+ Organize into folders
+ Sort downloads into subfolders by artist, album, etc.
+
+
+
+
+
+
+ {templateEnabled && (<>
{/* Current / pending path readout */}
-
+
{dirty ? 'Preview:' : 'Active:'}
@@ -580,7 +657,7 @@ function DownloadPathSection() {
{/* Profile card grid */}
-
+
{profiles.map((profile, i) => {
const isSelected = i === selectedIdx
return (
@@ -606,7 +683,7 @@ function DownloadPathSection() {
···
{openMenuIdx === i && (
-
Cancel
-
+
Save folder structure
@@ -679,11 +756,14 @@ function DownloadPathSection() {
)}
+ >)}
+
{showModal && (
setShowModal(false)}
onSave={handleSavePreset}
+ enrichEnabled={enrichEnabled}
/>
)}
diff --git a/src/web/frontend/src/components/ui/PathTemplateModal.jsx b/src/web/frontend/src/components/ui/PathTemplateModal.jsx
index 6bce57a7..9693302f 100644
--- a/src/web/frontend/src/components/ui/PathTemplateModal.jsx
+++ b/src/web/frontend/src/components/ui/PathTemplateModal.jsx
@@ -50,9 +50,10 @@ export function PathLine({ template }) {
}
// Props:
-// onClose — called on cancel / backdrop / Escape
-// onSave — called with { name, template } when user saves the preset
-export function PathTemplateModal({ onClose, onSave }) {
+// onClose — called on cancel / backdrop / Escape
+// onSave — called with { name, template } when user saves the preset
+// enrichEnabled — whether rich metadata is on; gates enriched variable chips
+export function PathTemplateModal({ onClose, onSave, enrichEnabled = false }) {
const [name, setName] = useState('')
const [template, setTemplate] = useState(SEED_PRESETS[0].template)
const nameInputRef = useRef(null)
@@ -154,21 +155,27 @@ export function PathTemplateModal({ onClose, onSave }) {
- {TEMPLATE_VARS.map(({ name: varName, example, enriched }) => (
- insertVariable(varName)}
- className="flex items-baseline gap-1.5 bg-surface border border-ui-border rounded-[6px] px-2.5 py-1.5 text-[12px] text-white cursor-pointer transition-colors hover:border-accent hover:text-accent"
- >
- {varName}
- {example}
- {enriched && * }
-
- ))}
+ {TEMPLATE_VARS.map(({ name: varName, example, enriched }) => {
+ const locked = enriched && !enrichEnabled
+ return (
+ !locked && insertVariable(varName)}
+ title={locked ? 'Enable rich metadata in settings to use this variable' : undefined}
+ className={`flex items-baseline gap-1.5 bg-surface border rounded-[6px] px-2.5 py-1.5 text-[12px] transition-colors
+ ${locked
+ ? 'border-ui-border text-muted opacity-40 cursor-not-allowed'
+ : 'border-ui-border text-white cursor-pointer hover:border-accent hover:text-accent'}`}
+ >
+ {varName}
+ {example}
+
+ )
+ })}
- Click to insert at cursor. Variables marked * need ENRICH_METADATA=true. Illegal chars (/ \ : * ? " < > |) are stripped.
+ Click a variable to insert it at the cursor. Dimmed variables require Auto-tag to be enabled in settings. Illegal path characters are stripped automatically.
diff --git a/src/web/frontend/src/components/ui/common.jsx b/src/web/frontend/src/components/ui/common.jsx
index d155601b..0d90b879 100644
--- a/src/web/frontend/src/components/ui/common.jsx
+++ b/src/web/frontend/src/components/ui/common.jsx
@@ -5,7 +5,7 @@ import { forwardRef } from 'react'
export function Button({ children, className = '', ...props }) {
return (
{children}
diff --git a/src/web/frontend/src/lib/api.js b/src/web/frontend/src/lib/api.js
index 632f58a4..4920bf27 100644
--- a/src/web/frontend/src/lib/api.js
+++ b/src/web/frontend/src/lib/api.js
@@ -199,7 +199,37 @@ export async function savePathTemplate(template) {
body: JSON.stringify({ template }),
})
if (!res.ok) throw new Error(await res.text())
-}
+}
+
+export async function fetchPathTemplatePresets() {
+ const res = await apiFetch('/api/ui/path-templates')
+ if (!res.ok) throw new Error(await res.text())
+ return res.json()
+}
+
+export async function addPathTemplatePreset(name, template) {
+ const res = await apiFetch('/api/ui/path-templates', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name, template }),
+ })
+ if (!res.ok) throw new Error(await res.text())
+ return res.json()
+}
+
+export async function deletePathTemplatePreset(name) {
+ const res = await apiFetch(`/api/ui/path-templates/${encodeURIComponent(name)}`, { method: 'DELETE' })
+ if (!res.ok) throw new Error(await res.text())
+}
+
+export async function saveEnrichMetadata(enabled) {
+ const res = await apiFetch('/api/ui/config/enrich-metadata', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ enabled }),
+ })
+ if (!res.ok) throw new Error(await res.text())
+}
export async function fetchBackgroundArt() {
try {
From 8bd085419da6e6a561460284e44e24cbdfde2f22 Mon Sep 17 00:00:00 2001
From: dammitjeff <44111923+dammitjeff@users.noreply.github.com>
Date: Fri, 12 Jun 2026 11:44:48 -0700
Subject: [PATCH 5/5] Fix broken ENRICH_TRACK_METADATA mentions in defs.go and
server.go
---
src/web/backend/defs.go | 2 +-
src/web/backend/server.go | 17 ++++++++++-------
src/web/frontend/src/components/Settings.jsx | 2 +-
3 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/web/backend/defs.go b/src/web/backend/defs.go
index e5d769e8..8a22deea 100644
--- a/src/web/backend/defs.go
+++ b/src/web/backend/defs.go
@@ -156,7 +156,7 @@ var allConfigKeys = []string{
"ON_REPEAT_SCHEDULE", "ON_REPEAT_FLAGS",
"EXPLO_SYSTEM", "SYSTEM_URL", "API_KEY", "LIBRARY_NAME",
"SYSTEM_USERNAME", "SYSTEM_PASSWORD", "PLAYLIST_DIR", "SLEEP", "PUBLIC_PLAYLIST",
- "DOWNLOAD_DIR", "USE_SUBDIRECTORY", "PATH_TEMPLATE", "ENRICH_METADATA",
+ "DOWNLOAD_DIR", "USE_SUBDIRECTORY", "PATH_TEMPLATE", "ENRICH_TRACK_METADATA",
"DOWNLOAD_SERVICES", "YOUTUBE_API_KEY", "TRACK_EXTENSION", "FILTER_LIST",
"SLSKD_URL", "SLSKD_API_KEY",
"WIZARD_COMPLETE", "MIGRATE_DOWNLOADS", "EXTENSIONS",
diff --git a/src/web/backend/server.go b/src/web/backend/server.go
index 336f505e..b00431e2 100644
--- a/src/web/backend/server.go
+++ b/src/web/backend/server.go
@@ -286,16 +286,19 @@ func (s *Server) registerRoutes() {
}
})
// ID-specific routes: DELETE /api/ui/custom-playlists/{id} and POST .../{id}/refresh
- s.mux.HandleFunc("/api/ui/custom-playlists/", func(w http.ResponseWriter, r *http.Request) {
- if r.Method == http.MethodDelete {
- s.authStore.RequireAuth(http.HandlerFunc(s.handleDeleteCustomPlaylist)).ServeHTTP(w, r)
+ s.mux.HandleFunc("/api/ui/custom-playlists/{id}/refresh", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
- if r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/refresh") {
- s.authStore.RequireAuth(http.HandlerFunc(s.handleRefreshCustomPlaylist)).ServeHTTP(w, r)
+ s.authStore.RequireAuth(http.HandlerFunc(s.handleRefreshCustomPlaylist)).ServeHTTP(w, r)
+ })
+ s.mux.HandleFunc("/api/ui/custom-playlists/{id}", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodDelete {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ s.authStore.RequireAuth(http.HandlerFunc(s.handleDeleteCustomPlaylist)).ServeHTTP(w, r)
})
s.mux.Handle("/api/ui/logout", s.authStore.RequireAuth(http.HandlerFunc(s.handleLogout)))
@@ -617,7 +620,7 @@ func (s *Server) handleSaveEnrichMetadata(w http.ResponseWriter, r *http.Request
if body.Enabled {
val = "true"
}
- if err := updateEnvKeys(s.cfg.WebEnvPath, map[string]string{"ENRICH_METADATA": val}, web.SampleEnv); err != nil {
+ if err := updateEnvKeys(s.cfg.WebEnvPath, map[string]string{"ENRICH_TRACK_METADATA": val}, web.SampleEnv); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
diff --git a/src/web/frontend/src/components/Settings.jsx b/src/web/frontend/src/components/Settings.jsx
index 822968eb..61c1757f 100644
--- a/src/web/frontend/src/components/Settings.jsx
+++ b/src/web/frontend/src/components/Settings.jsx
@@ -516,7 +516,7 @@ function DownloadPathSection() {
...SEED_PRESETS.map(p => ({ ...p, seed: true })),
...jsonPresets,
]
- setEnrichEnabled(values.ENRICH_METADATA === 'true')
+ setEnrichEnabled(values.ENRICH_TRACK_METADATA === 'true')
const t = values.PATH_TEMPLATE || ''
if (t) {
setTemplateEnabled(true)