api

package
v1.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: GPL-3.0 Imports: 69 Imported by: 0

Documentation

Overview

Package api: handlers that mutate the artist row outside the field-level edit flow. The directory-rename handler in this file is intentionally separate from handleFieldUpdate ("name") because renaming an artist's on-disk directory has filesystem and platform-mapping consequences that must be opt-in, not a side-effect of saving a name change. See issue #1077: editing the Name field used to indirectly trigger an on-disk rename via the directory_name_mismatch rule fixer, which broke Emby/Jellyfin item-to-path mappings. The fix decouples the two: name edits go through PATCH /api/v1/artists/{id}/fields/name and stay in the DB+NFO; the directory rename below is a deliberate, user-driven action.

Package api implements the HTTP router, handlers, middleware, and static asset management for the REST API and HTMX web interface.

Index

Constants

View Source
const (
	BulkActionRunRules       = "run_rules"
	BulkActionReIdentify     = "re_identify"      // legacy alias for re_identify_auto
	BulkActionReIdentifyAuto = "re_identify_auto" // silent auto-link + queue path
	BulkActionScan           = "scan"
	BulkActionFetchImages    = "fetch_images"
)

Allowed bulk action types.

BulkActionReIdentify is the legacy alias retained so existing callers keep working; it is normalized to BulkActionReIdentifyAuto on entry. The review variant (re_identify_review string in the UI) is dispatched separately through the wizard endpoints in handlers_reidentify_wizard.go and never flows through this handler, so no Go constant is defined for it here.

View Source
const (
	PrefTheme               = "theme"
	PrefSidebarState        = "sidebar_state"
	PrefContentWidth        = "content_width"
	PrefFontFamily          = "font_family"
	PrefFontSize            = "font_size"
	PrefLetterSpacing       = "letter_spacing"
	PrefThumbnailSize       = "thumbnail_size"
	PrefReducedMotion       = "reduced_motion"
	PrefLiteMode            = "lite_mode"
	PrefLanguage            = "language"
	PrefMetadataLanguages   = "metadata_languages"
	PrefNotificationEnabled = "notification_enabled"
	PrefAutoFetchImages     = "auto_fetch_images"
	PrefBgOpacity           = "bg_opacity"
	PrefPageSize            = "page_size"

	// PrefSuppressConfirmPrefix is the prefix for per-action confirm suppression
	// preferences. Keys have the form "suppress_confirm_{action}" and accept
	// "true" or "false". These are not listed in preferenceDefaults because they
	// are created dynamically by the UI as the user opts out of specific dialogs.
	PrefSuppressConfirmPrefix = "suppress_confirm_"

	// PageSizeDefault is the default number of items per page.
	// PageSizeMin and PageSizeMax define the allowed range for the page_size preference.
	PageSizeDefault = 50
	PageSizeMin     = 10
	PageSizeMax     = 500
)

Preference key constants. Use these instead of raw strings when referencing preference keys in Go code to catch typos at compile time.

View Source
const (
	BgOpacityDefault = 65
	BgOpacityMin     = 20
	BgOpacityMax     = 100
)

BgOpacityDefault is the default background opacity percentage. BgOpacityMin and BgOpacityMax define the allowed range for the bg_opacity preference.

View Source
const MaxBulkActionIDs = artist.MaxListIDs

MaxBulkActionIDs caps the number of artist IDs accepted in a single bulk action request. This bounds both memory and run time so a single request cannot monopolize the singleton bulk-action slot indefinitely. Sourced from artist.MaxListIDs so the API request cap and the domain-layer IDs-filter cap stay in lockstep without a comment-only contract.

View Source
const MetadataLanguagesDefault = langpref.DefaultJSON

MetadataLanguagesDefault is the default value for the metadata_languages preference when no user preference is stored.

Deprecated: use langpref.DefaultJSON in new code.

View Source
const MetadataLanguagesMaxEntries = langpref.MaxEntries

MetadataLanguagesMaxEntries limits how many language tags a user can store.

Deprecated: use langpref.MaxEntries in new code.

Variables

View Source
var (
	ErrConflictPeerRejected = errors.New("peer rejected stillwater-managed change")
	ErrConflictLocalPersist = errors.New("persisting stillwater-managed state failed")
)

Sentinel errors classify failures from applyStillwaterManaged / clearStillwaterManaged so the HTTP handler can map them to the right status code. ErrConflictPeerRejected => 502 (peer-side: snapshot read, disable, restore). ErrConflictLocalPersist => 500 (Stillwater-side: SetPreStillwaterConfig, SetManageServerFiles). Local persistence failures returned a 502 in the original implementation, which sent callers toward the wrong remediation path.

Functions

func DecodeJSON added in v0.9.5

func DecodeJSON(w http.ResponseWriter, req *http.Request, target any) bool

DecodeJSON decodes the JSON request body into target. If decoding fails, it writes a 400 error and returns false. Callers must return immediately when the return value is false.

func RequirePathParam added in v0.9.5

func RequirePathParam(w http.ResponseWriter, req *http.Request, name string) (string, bool)

RequirePathParam extracts a named path parameter from the request. If the value is empty, it writes a 400 error and returns ("", false). Callers must return immediately when the second return value is false.

Types

type BulkActionProgress added in v0.9.6

type BulkActionProgress struct {
	Action      string `json:"action"`
	Status      string `json:"status"` // "running", "completed", "failed", "canceled"
	Total       int    `json:"total"`
	Processed   int    `json:"processed"`
	Succeeded   int    `json:"succeeded"`
	Skipped     int    `json:"skipped"`
	Failed      int    `json:"failed"`
	CurrentName string `json:"current_name"`
	// Re-identify-specific breakdown. These remain zero for other actions,
	// so the bulk-completion toast can detect the re_identify_auto path by
	// checking whether any of them are non-zero. Populated in addition to
	// the generic succeeded/skipped/failed counters so existing consumers
	// keep working unchanged.
	AutoLinked  int `json:"auto_linked"`
	Queued      int `json:"queued"`
	NoMatch     int `json:"no_match"`
	StartedAt   time.Time
	CompletedAt time.Time
	// contains filtered or unexported fields
}

BulkActionProgress tracks the state of an in-flight bulk action. It is mutex-protected and shared between the request handler and the background goroutine processing the IDs.

type ClobberCheckResponse added in v0.9.5

type ClobberCheckResponse struct {
	HasRisk bool          `json:"has_risk"`
	Risks   []ClobberRisk `json:"risks"`
}

ClobberCheckResponse is the response for GET /api/v1/connections/clobber-check.

type ClobberRisk added in v0.9.5

type ClobberRisk struct {
	ConnectionID   string `json:"connection_id"`
	ConnectionName string `json:"connection_name"`
	ConnectionType string `json:"connection_type"`
	NFOWriter      bool   `json:"nfo_writer"`
	LibraryName    string `json:"library_name,omitempty"`
	Error          string `json:"error,omitempty"`
}

ClobberRisk describes whether a specific connection may overwrite NFO/image files.

type FileRemover added in v0.9.5

type FileRemover interface {
	Remove(name string) error
}

FileRemover abstracts file removal for testability. Production code uses osRemover (the default); tests can inject a stub that returns errors on demand to exercise error paths that are otherwise unreachable as root or on Windows. Implementations must return an error wrapping os.ErrNotExist when the target file does not exist, since callers use errors.Is to distinguish missing files from genuine failures.

type FixAllProgress added in v0.9.5

type FixAllProgress struct {
	Status    string `json:"status"`
	Total     int    `json:"total"`
	Processed int    `json:"processed"`
	Fixed     int    `json:"fixed"`
	Skipped   int    `json:"skipped"`
	Failed    int    `json:"failed"`
	// contains filtered or unexported fields
}

FixAllProgress tracks the state of an async fix-all operation.

type IdentifyCandidate added in v0.9.5

type IdentifyCandidate struct {
	ArtistID   string            `json:"artist_id"`
	ArtistName string            `json:"artist_name"`
	ArtistPath string            `json:"artist_path"`
	Tier       string            `json:"tier"` // "connection", "album", "name"
	Candidates []ScoredCandidate `json:"candidates"`
}

IdentifyCandidate represents an artist that needs manual review for linking.

type IdentifyProgress added in v0.9.5

type IdentifyProgress struct {
	Status      string              `json:"status"` // "running", "completed", "canceled"
	Total       int                 `json:"total"`
	Processed   int                 `json:"processed"`
	AutoLinked  int                 `json:"auto_linked"`
	Queued      int                 `json:"queued"`
	Unmatched   int                 `json:"unmatched"`
	Failed      int                 `json:"failed"`
	CurrentName string              `json:"current_name"`
	ReviewQueue []IdentifyCandidate `json:"review_queue,omitempty"`
	// contains filtered or unexported fields
}

IdentifyProgress tracks the state of a bulk-identify operation.

type LibraryOpResult added in v0.9.5

type LibraryOpResult struct {
	LibraryID   string     `json:"library_id"`
	LibraryName string     `json:"library_name"`
	Operation   string     `json:"operation"`
	Status      string     `json:"status"`
	Message     string     `json:"message,omitempty"`
	StartedAt   time.Time  `json:"started_at"`
	CompletedAt *time.Time `json:"completed_at,omitempty"`
}

LibraryOpResult tracks the state of an async library operation.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router sets up all HTTP routes for the application.

func NewRouter

func NewRouter(deps RouterDeps) *Router

NewRouter creates a new Router with all routes configured.

func (*Router) Handler

func (r *Router) Handler(ctx context.Context) http.Handler

Handler returns the fully configured HTTP handler with middleware applied. The provided context controls the lifecycle of background goroutines (e.g. rate limiter cleanup).

func (*Router) InvalidateHealthCache added in v0.9.5

func (r *Router) InvalidateHealthCache()

InvalidateHealthCache is a no-op retained for API compatibility with callers added by PR #700. Health scores are now read from stored per-artist values (updated via the event bus), so there is no in-memory cache to invalidate.

func (*Router) ServeError500 added in v0.9.5

func (r *Router) ServeError500(w http.ResponseWriter, req *http.Request)

ServeError500 renders the custom 500 error page. Other handlers can call this helper instead of http.Error when an unexpected internal error occurs and the request was a browser navigation (not a JSON API call). JSON API clients (Accept: application/json or /api/ path prefix) and HTMX partial requests (HX-Request: true) receive a JSON error body.

type RouterDeps added in v0.9.5

type RouterDeps struct {
	AuthService        *auth.Service
	AuthRegistry       *auth.Registry
	ArtistService      *artist.Service
	HistoryService     *artist.HistoryService
	ScannerService     *scanner.Service
	PlatformService    *platform.Service
	ProviderSettings   *provider.SettingsService
	ProviderRegistry   *provider.Registry
	WebSearchRegistry  *provider.WebSearchRegistry
	RateLimiters       *provider.RateLimiterMap
	Orchestrator       *provider.Orchestrator
	RuleService        *rule.Service
	RuleEngine         *rule.Engine
	Pipeline           rule.PipelineRunner
	BulkService        *rule.BulkService
	BulkExecutor       *rule.BulkExecutor
	RuleScheduler      *rule.Scheduler
	NFOSnapshotService *nfo.SnapshotService
	NFOSettingsService *nfo.NFOSettingsService
	ConnectionService  *connection.Service
	ScraperService     *scraper.Service
	LibraryService     *library.Service
	WebhookService     *webhook.Service
	WebhookDispatcher  *webhook.Dispatcher
	BackupService      *backup.Service
	LogManager         *logging.Manager
	MaintenanceService *maintenance.Service
	SettingsIOService  *settingsio.Service
	UpdaterService     *updater.Service
	ProbeCache         *watcher.ProbeCache
	ExpectedWrites     *watcher.ExpectedWrites
	EventBus           *event.Bus
	DB                 *sql.DB
	Logger             *slog.Logger
	BasePath           string
	BasePathFromEnv    bool
	StaticFS           fs.FS
	ImageCacheDir      string
	Publisher          *publish.Publisher
	SSEHub             *SSEHub
	I18nBundle         *i18n.Bundle
}

RouterDeps bundles all dependencies needed by the HTTP router.

type SSEClient added in v0.9.5

type SSEClient struct {
	// contains filtered or unexported fields
}

SSEClient represents a single connected SSE browser client.

type SSEEvent added in v0.9.5

type SSEEvent struct {
	// Type is the SSE event name (e.g. "scan.completed", "rule.violation").
	Type string `json:"type"`
	// Title is a short human-readable summary for toast/notification display.
	Title string `json:"title"`
	// Message is the full notification body text.
	Message string `json:"message"`
	// Timestamp is when the event occurred.
	Timestamp time.Time `json:"timestamp"`
	// Data carries optional structured metadata about the event.
	Data map[string]any `json:"data,omitempty"`
}

SSEEvent is a single server-sent event payload delivered to browser clients.

type SSEHub added in v0.9.5

type SSEHub struct {
	// contains filtered or unexported fields
}

SSEHub manages connected SSE clients and fans out events from the event bus. It is safe for concurrent access from multiple goroutines.

func NewSSEHub added in v0.9.5

func NewSSEHub(logger *slog.Logger) *SSEHub

NewSSEHub creates a new SSE hub.

func (*SSEHub) Broadcast added in v0.9.5

func (h *SSEHub) Broadcast(evt SSEEvent)

Broadcast sends an event to all connected clients. If a client's buffer is full the event is dropped for that client (non-blocking).

func (*SSEHub) ClientCount added in v0.9.5

func (h *SSEHub) ClientCount() int

ClientCount returns the number of connected SSE clients.

func (*SSEHub) Register added in v0.9.5

func (h *SSEHub) Register(userID string) *SSEClient

Register adds a client to the hub and returns it. The caller must call Unregister when the client disconnects.

func (*SSEHub) SubscribeToEventBus added in v0.9.5

func (h *SSEHub) SubscribeToEventBus(bus *event.Bus)

SubscribeToEventBus registers event bus handlers that convert internal events into SSE events and broadcast them to all connected clients.

func (*SSEHub) Unregister added in v0.9.5

func (h *SSEHub) Unregister(c *SSEClient)

Unregister removes a client from the hub and closes its channel.

type ScoredCandidate added in v0.9.5

type ScoredCandidate struct {
	provider.ArtistSearchResult
	AlbumComparison *artist.AlbumComparison `json:"album_comparison,omitempty"`
	Confidence      float64                 `json:"confidence"`
	Reason          string                  `json:"reason"`
}

ScoredCandidate wraps a provider search result with confidence scoring.

type SharedFilesystemEntry added in v0.9.5

type SharedFilesystemEntry struct {
	LibraryID   string `json:"library_id"`
	LibraryName string `json:"library_name"`
	Path        string `json:"path"`
	OverlapWith string `json:"overlap_with"`
}

SharedFilesystemEntry describes one library with a shared-filesystem overlap.

type SharedFilesystemStatus added in v0.9.5

type SharedFilesystemStatus struct {
	HasOverlaps          bool                             `json:"has_overlaps"`
	Libraries            []SharedFilesystemEntry          `json:"libraries"`
	Dismissed            bool                             `json:"dismissed"`                        // user chose "don't show again"
	ImageFetcherWarnings []connection.ImageFetcherWarning `json:"image_fetcher_warnings,omitempty"` // platform image fetcher conflicts
}

SharedFilesystemStatus holds the current shared-filesystem detection state returned by the status endpoint and consumed by the notification bar template.

type StaticAssets

type StaticAssets struct {
	// contains filtered or unexported fields
}

StaticAssets manages static file serving with content-hash cache busting. Files are served with a version query parameter (e.g., /static/css/styles.css?v=abc123). When the hash matches, responses include immutable cache headers for aggressive browser caching. When files change, the hash changes, forcing browsers to fetch the new version.

func NewStaticAssets

func NewStaticAssets(fsys fs.FS, logger *slog.Logger) *StaticAssets

NewStaticAssets creates a StaticAssets manager that scans the given filesystem.

func (*StaticAssets) Handler

func (sa *StaticAssets) Handler(basePath string) http.Handler

Handler returns an HTTP handler that serves static files with appropriate cache headers.

func (*StaticAssets) Path

func (sa *StaticAssets) Path(filePath string) string

Path returns a cache-busted URL for a static file. Example: Path("/css/styles.css") returns "/static/css/styles.css?v=a1b2c3d4"

func (*StaticAssets) Rescan

func (sa *StaticAssets) Rescan(logger *slog.Logger)

Rescan rescans the static filesystem and updates hashes. Useful if files change at runtime (e.g., during development with os.DirFS).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL