api

package
v0.1.2-alpha Latest Latest
Warning

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

Go to latest
Published: Mar 17, 2026 License: MIT Imports: 56 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNFONotFound     = errors.New("nfo file not found")
	ErrNFOAccessDenied = errors.New("access denied: path is outside allowed directories")
	ErrNFOInvalidPath  = errors.New("invalid file path")
	ErrNFOIsDirectory  = errors.New("path is a directory, not a file")
)

Sentinel errors for NFO validation

Functions

func LogServerInfo

func LogServerInfo(cfg *config.Config)

LogServerInfo logs information about the running server

func NewServer

func NewServer(deps *ServerDependencies) *gin.Engine

NewServer creates and configures the Gin router with all API endpoints

Types

type AvailableScrapersResponse

type AvailableScrapersResponse struct {
	Scrapers []ScraperInfo `json:"scrapers"`
}

AvailableScrapersResponse represents the list of available scrapers

type BatchFileResult

type BatchFileResult struct {
	FilePath       string            `json:"file_path"`
	MovieID        string            `json:"movie_id"`
	Status         string            `json:"status"`
	Error          string            `json:"error,omitempty"`
	FieldSources   map[string]string `json:"field_sources,omitempty"`   // Field-level source by scraper/NFO
	ActressSources map[string]string `json:"actress_sources,omitempty"` // Actress-level source by scraper/NFO
	Data           interface{}       `json:"data,omitempty"`            // Movie data
	StartedAt      string            `json:"started_at"`
	EndedAt        *string           `json:"ended_at,omitempty"`
	IsMultiPart    bool              `json:"is_multi_part,omitempty"`
	PartNumber     int               `json:"part_number,omitempty"`
	PartSuffix     string            `json:"part_suffix,omitempty"`
}

BatchFileResult wraps worker.FileResult with additional API-specific fields

type BatchJobResponse

type BatchJobResponse struct {
	ID          string                      `json:"id"`
	Status      string                      `json:"status"`
	TotalFiles  int                         `json:"total_files"`
	Completed   int                         `json:"completed"`
	Failed      int                         `json:"failed"`
	Excluded    map[string]bool             `json:"excluded"` // Files excluded from organization
	Progress    float64                     `json:"progress"`
	Results     map[string]*BatchFileResult `json:"results"`
	StartedAt   string                      `json:"started_at"`
	CompletedAt *string                     `json:"completed_at,omitempty"`
}

BatchJobResponse represents a batch job status

type BatchRescrapeRequest

type BatchRescrapeRequest struct {
	Force             bool     `json:"force" example:"false"`
	SelectedScrapers  []string `json:"selected_scrapers,omitempty" example:"r18dev,dmm"`
	ManualSearchInput string   `json:"manual_search_input,omitempty" example:"IPX-535"`
	Preset            string   `json:"preset,omitempty" example:"conservative"`        // Merge strategy preset: conservative, gap-fill, aggressive (overrides scalar/array strategies)
	ScalarStrategy    string   `json:"scalar_strategy,omitempty" example:"prefer-nfo"` // For Update mode: prefer-nfo, prefer-scraper, preserve-existing, fill-missing-only
	ArrayStrategy     string   `json:"array_strategy,omitempty" example:"merge"`       // For Update mode: merge, replace
}

BatchRescrapeRequest represents a batch rescrape request for manual search/rescraping

type BatchRescrapeResponse

type BatchRescrapeResponse struct {
	Movie          *models.Movie     `json:"movie"`
	FieldSources   map[string]string `json:"field_sources,omitempty"`
	ActressSources map[string]string `json:"actress_sources,omitempty"`
}

BatchRescrapeResponse represents a batch rescrape response with movie

type BatchScrapeRequest

type BatchScrapeRequest struct {
	Files            []string `json:"files" binding:"required"`
	Strict           bool     `json:"strict" example:"false"`
	Force            bool     `json:"force" example:"false"`
	Destination      string   `json:"destination,omitempty" example:"/path/to/output"`
	Update           bool     `json:"update" example:"false"` // Update mode: only create/update metadata files without moving video files
	SelectedScrapers []string `json:"selected_scrapers,omitempty" example:"r18dev,dmm"`
	Preset           string   `json:"preset,omitempty" example:"conservative"`        // Merge strategy preset: conservative, gap-fill, aggressive (overrides scalar/array strategies)
	ScalarStrategy   string   `json:"scalar_strategy,omitempty" example:"prefer-nfo"` // For Update mode: prefer-nfo, prefer-scraper, preserve-existing, fill-missing-only
	ArrayStrategy    string   `json:"array_strategy,omitempty" example:"merge"`       // For Update mode: merge, replace
}

BatchScrapeRequest represents a batch scrape request

type BatchScrapeResponse

type BatchScrapeResponse struct {
	JobID string `json:"job_id" example:"550e8400-e29b-41d4-a716-446655440000"`
}

BatchScrapeResponse represents batch scrape response

type BrowseRequest

type BrowseRequest struct {
	Path string `json:"path" example:"/path/to/directory"`
}

BrowseRequest represents a browse request

type BrowseResponse

type BrowseResponse struct {
	CurrentPath string     `json:"current_path" example:"/path/to/directory"`
	ParentPath  string     `json:"parent_path,omitempty" example:"/path/to"`
	Items       []FileInfo `json:"items"`
}

BrowseResponse represents browse results

type DataSource

type DataSource struct {
	Source      string  `json:"source" example:"nfo"`                                  // "scraper" or "nfo"
	Confidence  float64 `json:"confidence" example:"0.9"`                              // Confidence score (0.0-1.0)
	LastUpdated *string `json:"last_updated,omitempty" example:"2024-01-15T10:30:00Z"` // ISO 8601 timestamp
}

DataSource represents the source of a metadata field

type DeleteHistoryBulkResponse

type DeleteHistoryBulkResponse struct {
	Deleted int64 `json:"deleted"`
}

DeleteHistoryBulkResponse is the response for bulk deletion

type ErrorResponse

type ErrorResponse struct {
	Error  string   `json:"error" example:"Movie not found"`
	Errors []string `json:"errors,omitempty"`
}

ErrorResponse represents an error response

type FieldDifference

type FieldDifference struct {
	Field        string      `json:"field" example:"title"`
	NFOValue     interface{} `json:"nfo_value,omitempty" example:"Beautiful Woman"`
	ScrapedValue interface{} `json:"scraped_value,omitempty" example:"Pretty Lady"`
	MergedValue  interface{} `json:"merged_value,omitempty" example:"Beautiful Woman"`
	Reason       string      `json:"reason,omitempty" example:"NFO preferred by merge strategy"`
}

FieldDifference represents a difference between NFO and scraped data

type FileInfo

type FileInfo struct {
	Name        string `json:"name" example:"video.mp4"`
	Path        string `json:"path" example:"/path/to/video.mp4"`
	IsDir       bool   `json:"is_dir" example:"false"`
	Size        int64  `json:"size" example:"1024000000"`
	ModTime     string `json:"mod_time" example:"2024-01-15T10:30:00Z"`
	MovieID     string `json:"movie_id,omitempty" example:"IPX-535"`
	Matched     bool   `json:"matched" example:"true"`
	IsMultiPart bool   `json:"is_multi_part,omitempty" example:"true"`
	PartNumber  int    `json:"part_number,omitempty" example:"1"`
	PartSuffix  string `json:"part_suffix,omitempty" example:"-pt1"`
}

FileInfo represents file or directory information

type HealthResponse

type HealthResponse struct {
	Status    string   `json:"status" example:"ok"`
	Scrapers  []string `json:"scrapers" example:"r18dev,dmm"`
	Version   string   `json:"version" example:"v1.2.3"`
	Commit    string   `json:"commit" example:"abc123def456"`
	BuildDate string   `json:"build_date" example:"2026-02-23T00:00:00Z"`
}

HealthResponse represents the health check response

type HistoryListResponse

type HistoryListResponse struct {
	Records []HistoryRecord `json:"records"`
	Total   int64           `json:"total"`
	Limit   int             `json:"limit"`
	Offset  int             `json:"offset"`
}

HistoryListResponse is the response for listing history records

type HistoryRecord

type HistoryRecord struct {
	ID           uint   `json:"id"`
	MovieID      string `json:"movie_id"`
	Operation    string `json:"operation"`
	OriginalPath string `json:"original_path"`
	NewPath      string `json:"new_path"`
	Status       string `json:"status"`
	ErrorMessage string `json:"error_message"`
	Metadata     string `json:"metadata"`
	DryRun       bool   `json:"dry_run"`
	CreatedAt    string `json:"created_at"`
}

HistoryRecord represents a single history record in API responses

type HistoryStats

type HistoryStats struct {
	Total       int64            `json:"total"`
	Success     int64            `json:"success"`
	Failed      int64            `json:"failed"`
	Reverted    int64            `json:"reverted"`
	ByOperation map[string]int64 `json:"by_operation"`
}

HistoryStats represents aggregated history statistics

type MergeStatistics

type MergeStatistics struct {
	TotalFields       int `json:"total_fields" example:"15"`
	FromScraper       int `json:"from_scraper" example:"10"`
	FromNFO           int `json:"from_nfo" example:"3"`
	MergedArrays      int `json:"merged_arrays" example:"2"`
	ConflictsResolved int `json:"conflicts_resolved" example:"5"`
	EmptyFields       int `json:"empty_fields" example:"2"`
}

MergeStatistics represents statistics about a merge operation

type MovieResponse

type MovieResponse struct {
	Movie      *models.Movie         `json:"movie"`
	Provenance map[string]DataSource `json:"provenance,omitempty"`  // Field-level data source tracking
	MergeStats *MergeStatistics      `json:"merge_stats,omitempty"` // Merge statistics when NFO merging occurred
}

MovieResponse represents a movie response

type MoviesResponse

type MoviesResponse struct {
	Movies []models.Movie `json:"movies"`
	Count  int            `json:"count" example:"20"`
}

MoviesResponse represents a list of movies response

type NFOComparisonRequest

type NFOComparisonRequest struct {
	NFOPath          string   `json:"nfo_path,omitempty" example:"/path/to/movie.nfo"`   // Optional: explicit NFO path
	MergeStrategy    string   `json:"merge_strategy,omitempty" example:"prefer-scraper"` // Deprecated: prefer-scraper, prefer-nfo, merge-arrays (use preset or scalar/array strategies instead)
	Preset           string   `json:"preset,omitempty" example:"conservative"`           // Merge strategy preset: conservative, gap-fill, or aggressive (overrides scalar/array strategies)
	ScalarStrategy   string   `json:"scalar_strategy,omitempty" example:"prefer-nfo"`    // Scalar field merge strategy: prefer-nfo, prefer-scraper, preserve-existing, or fill-missing-only
	ArrayStrategy    string   `json:"array_strategy,omitempty" example:"merge"`          // Array field merge strategy: merge or replace
	SelectedScrapers []string `json:"selected_scrapers,omitempty" example:"r18dev,dmm"`  // Optional: custom scrapers for comparison
}

NFOComparisonRequest represents a request to compare NFO with scraped data

type NFOComparisonResponse

type NFOComparisonResponse struct {
	MovieID     string                `json:"movie_id" example:"IPX-535"`
	NFOExists   bool                  `json:"nfo_exists" example:"true"`
	NFOPath     string                `json:"nfo_path,omitempty" example:"movie.nfo"` // Returns filename only for security
	NFOData     *models.Movie         `json:"nfo_data,omitempty"`                     // Data from NFO file
	ScrapedData *models.Movie         `json:"scraped_data,omitempty"`                 // Fresh scraped data
	MergedData  *models.Movie         `json:"merged_data,omitempty"`                  // Result of merging
	Provenance  map[string]DataSource `json:"provenance,omitempty"`                   // Field-level provenance
	MergeStats  *MergeStatistics      `json:"merge_stats,omitempty"`                  // Merge statistics
	Differences []FieldDifference     `json:"differences,omitempty"`                  // List of fields that differ
}

NFOComparisonResponse represents the result of comparing NFO with scraped data

type OrganizePreviewRequest

type OrganizePreviewRequest struct {
	Destination string `json:"destination" binding:"required" example:"/path/to/output"`
	CopyOnly    bool   `json:"copy_only" example:"false"`
	LinkMode    string `json:"link_mode,omitempty" binding:"omitempty,oneof=hard soft" example:"hard"`
}

OrganizePreviewRequest represents a preview request

type OrganizePreviewResponse

type OrganizePreviewResponse struct {
	FolderName      string   `json:"folder_name" example:"IPX-535 [IdeaPocket] - Beautiful Woman (2021)"`
	FileName        string   `json:"file_name" example:"IPX-535"`
	FullPath        string   `json:"full_path" example:"/path/to/output/IPX-535 [IdeaPocket] - Beautiful Woman (2021)/IPX-535.mp4"`
	VideoFiles      []string `json:"video_files,omitempty"`                                                                        // For multi-part files: all video file paths
	NFOPath         string   `json:"nfo_path" example:"/path/to/output/IPX-535 [IdeaPocket] - Beautiful Woman (2021)/IPX-535.nfo"` // Single NFO (backward compatibility)
	NFOPaths        []string `json:"nfo_paths,omitempty"`                                                                          // For per_file=true multi-part: all NFO file paths
	PosterPath      string   `json:"poster_path" example:"/path/to/output/IPX-535 [IdeaPocket] - Beautiful Woman (2021)/IPX-535-poster.jpg"`
	FanartPath      string   `json:"fanart_path" example:"/path/to/output/IPX-535 [IdeaPocket] - Beautiful Woman (2021)/IPX-535-fanart.jpg"`
	ExtrafanartPath string   `json:"extrafanart_path" example:"/path/to/output/IPX-535 [IdeaPocket] - Beautiful Woman (2021)/extrafanart"`
	Screenshots     []string `json:"screenshots" example:"fanart1.jpg,fanart2.jpg,fanart3.jpg"`
}

OrganizePreviewResponse represents the expected output structure

type OrganizeRequest

type OrganizeRequest struct {
	Destination string `json:"destination" binding:"required" example:"/path/to/output"`
	CopyOnly    bool   `json:"copy_only" example:"false"`
	LinkMode    string `json:"link_mode,omitempty" binding:"omitempty,oneof=hard soft" example:"hard"`
}

OrganizeRequest represents an organize request

type PathAutocompleteRequest

type PathAutocompleteRequest struct {
	Path  string `json:"path" binding:"required" example:"/path/to/vid"`
	Limit int    `json:"limit,omitempty" example:"10"`
}

PathAutocompleteRequest represents a partial path autocomplete request.

type PathAutocompleteResponse

type PathAutocompleteResponse struct {
	InputPath   string                       `json:"input_path" example:"/path/to/vid"`
	BasePath    string                       `json:"base_path" example:"/path/to"`
	Suggestions []PathAutocompleteSuggestion `json:"suggestions"`
}

PathAutocompleteResponse represents directory suggestions for a partial path.

type PathAutocompleteSuggestion

type PathAutocompleteSuggestion struct {
	Name  string `json:"name" example:"videos"`
	Path  string `json:"path" example:"/path/to/videos"`
	IsDir bool   `json:"is_dir" example:"true"`
}

PathAutocompleteSuggestion represents a single autocomplete suggestion.

type PosterCropRequest

type PosterCropRequest struct {
	X      int `json:"x" binding:"min=0"`
	Y      int `json:"y" binding:"min=0"`
	Width  int `json:"width" binding:"min=1"`
	Height int `json:"height" binding:"min=1"`
}

PosterCropRequest represents manual poster crop coordinates in source-image pixels.

type PosterCropResponse

type PosterCropResponse struct {
	CroppedPosterURL string `json:"cropped_poster_url"`
}

PosterCropResponse returns the updated temp cropped poster URL.

type ProgressAdapter

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

ProgressAdapter bridges worker.ProgressTracker updates to WebSocket broadcasts for API batch jobs. It provides thread-safe mapping between task IDs and file paths for real-time progress tracking.

Usage Example:

// Create adapter for a batch job
adapter := NewProgressAdapter(job.ID, job, nil) // nil uses global wsHub

// Create progress tracker with adapter's channel
progressTracker := worker.NewProgressTracker(adapter.GetChannel())

// Start adapter (non-blocking)
adapter.Start()
defer adapter.Stop() // Safe to call multiple times

// Register each task before submitting to worker pool
for i, filePath := range job.Files {
    taskID := fmt.Sprintf("batch-scrape-%s-%d", job.ID, i)
    adapter.RegisterTask(taskID, i, filePath)

    task := NewBatchScrapeTask(taskID, filePath, progressTracker, ...)
    pool.Submit(task)
}

// Progress updates are automatically broadcast to WebSocket clients

func NewProgressAdapter

func NewProgressAdapter(jobID string, job *worker.BatchJob, broadcaster ProgressBroadcaster) *ProgressAdapter

NewProgressAdapter creates a new progress adapter for a batch job. The adapter listens to progress updates and broadcasts them via WebSocket. If broadcaster is nil, the global wsHub will be used.

func (*ProgressAdapter) GetChannel

func (a *ProgressAdapter) GetChannel() chan<- worker.ProgressUpdate

GetChannel returns a write-only channel for ProgressTracker to send updates. This channel should be passed to worker.NewProgressTracker().

func (*ProgressAdapter) GetRegisteredTaskCount

func (a *ProgressAdapter) GetRegisteredTaskCount() int

GetRegisteredTaskCount returns the number of currently registered tasks. Useful for debugging and monitoring.

func (*ProgressAdapter) RegisterTask

func (a *ProgressAdapter) RegisterTask(taskID string, fileIndex int, filePath string)

RegisterTask maps a task ID to its corresponding file index and path. This mapping is used to correlate progress updates with specific files. Thread-safe for concurrent registration during task submission.

func (*ProgressAdapter) Start

func (a *ProgressAdapter) Start()

Start launches the adapter's update processing goroutine. It listens for progress updates and converts them to WebSocket messages. This method is non-blocking; use Stop() to shut down gracefully.

func (*ProgressAdapter) Stop

func (a *ProgressAdapter) Stop()

Stop signals the adapter to shut down and waits for the processing goroutine to finish. This ensures graceful cleanup of resources. Stop is idempotent and safe to call multiple times.

func (*ProgressAdapter) UnregisterTask

func (a *ProgressAdapter) UnregisterTask(taskID string)

UnregisterTask removes a task from the mapping (optional cleanup). Not strictly necessary as the adapter is typically short-lived per job, but provided for completeness and memory efficiency in long-running jobs.

type ProgressBroadcaster

type ProgressBroadcaster interface {
	BroadcastProgress(msg *websocket.ProgressMessage) error
}

ProgressBroadcaster is an interface for broadcasting progress messages. This interface allows for dependency injection and testing with mocks.

type ProxyTestRequest

type ProxyTestRequest struct {
	Mode      string             `json:"mode" binding:"required,oneof=direct flaresolverr"` // direct or flaresolverr
	Proxy     config.ProxyConfig `json:"proxy"`
	TargetURL string             `json:"target_url,omitempty"` // Optional override target URL
}

ProxyTestRequest represents a proxy connectivity test request.

type ProxyTestResponse

type ProxyTestResponse struct {
	Success         bool   `json:"success"`
	Mode            string `json:"mode"`
	TargetURL       string `json:"target_url"`
	StatusCode      int    `json:"status_code,omitempty"`
	DurationMS      int64  `json:"duration_ms"`
	Message         string `json:"message"`
	ProxyURL        string `json:"proxy_url,omitempty"`        // Redacted proxy URL
	FlareSolverrURL string `json:"flaresolverr_url,omitempty"` // FlareSolverr endpoint used
}

ProxyTestResponse represents proxy connectivity test results.

type RescrapeRequest

type RescrapeRequest struct {
	SelectedScrapers []string `json:"selected_scrapers" binding:"required" example:"r18dev,dmm"`
	Force            bool     `json:"force" example:"false"`
}

RescrapeRequest represents a request to rescrape with specific scrapers

type ScanRequest

type ScanRequest struct {
	Path      string `json:"path" binding:"required" example:"/path/to/videos"`
	Recursive bool   `json:"recursive" example:"true"`
	Filter    string `json:"filter,omitempty" example:"STSK"` // Filter folder/file names (case-insensitive substring match)
}

ScanRequest represents a directory scan request

type ScanResponse

type ScanResponse struct {
	Files   []FileInfo `json:"files"`
	Count   int        `json:"count" example:"10"`
	Skipped []string   `json:"skipped,omitempty"`
}

ScanResponse represents scan results

type ScrapeRequest

type ScrapeRequest struct {
	ID               string   `json:"id" binding:"required" example:"IPX-535"`
	Force            bool     `json:"force" example:"false"`
	SelectedScrapers []string `json:"selected_scrapers,omitempty" example:"r18dev,dmm"`
}

ScrapeRequest represents the scrape request payload

type ScrapeResponse

type ScrapeResponse struct {
	Cached      bool          `json:"cached" example:"false"`
	Movie       *models.Movie `json:"movie"`
	SourcesUsed int           `json:"sources_used,omitempty" example:"2"`
	Errors      []string      `json:"errors,omitempty"`
}

ScrapeResponse represents the scrape response

type ScraperChoice

type ScraperChoice struct {
	Value string `json:"value" example:"en"`
	Label string `json:"label" example:"English"`
}

ScraperChoice represents a choice for a select-type scraper option

type ScraperInfo

type ScraperInfo struct {
	Name        string          `json:"name" example:"r18dev"`
	DisplayName string          `json:"display_name" example:"R18.dev"`
	Enabled     bool            `json:"enabled" example:"true"`
	Options     []ScraperOption `json:"options,omitempty"`
}

ScraperInfo represents information about a scraper

type ScraperOption

type ScraperOption struct {
	Key         string          `json:"key" example:"scrape_actress"`
	Label       string          `json:"label" example:"Scrape Actress Information"`
	Description string          `json:"description" example:"Enable detailed actress data scraping from DMM (may be slower)"`
	Type        string          `json:"type" example:"boolean"` // boolean, string, number, select
	Min         *int            `json:"min,omitempty" example:"5"`
	Max         *int            `json:"max,omitempty" example:"120"`
	Unit        string          `json:"unit,omitempty" example:"seconds"`
	Choices     []ScraperChoice `json:"choices,omitempty"` // For select type: available choices
}

ScraperOption represents a configurable option for a scraper

type ServerDependencies

type ServerDependencies struct {
	ConfigFile  string
	Registry    *models.ScraperRegistry
	DB          *database.DB
	Aggregator  *aggregator.Aggregator
	MovieRepo   *database.MovieRepository
	ActressRepo *database.ActressRepository
	HistoryRepo *database.HistoryRepository
	Matcher     *matcher.Matcher
	JobQueue    *worker.JobQueue
	// contains filtered or unexported fields
}

ServerDependencies holds all dependencies needed to create the API server Access to Config, Registry, Aggregator, and Matcher must be synchronized to prevent data races during config reload.

func (*ServerDependencies) GetAggregator

func (d *ServerDependencies) GetAggregator() *aggregator.Aggregator

GetAggregator returns the current aggregator (thread-safe)

func (*ServerDependencies) GetConfig

func (d *ServerDependencies) GetConfig() *config.Config

GetConfig returns the current configuration (thread-safe)

func (*ServerDependencies) GetMatcher

func (d *ServerDependencies) GetMatcher() *matcher.Matcher

GetMatcher returns the current matcher (thread-safe)

func (*ServerDependencies) GetRegistry

func (d *ServerDependencies) GetRegistry() *models.ScraperRegistry

GetRegistry returns the current scraper registry (thread-safe)

func (*ServerDependencies) SetConfig

func (d *ServerDependencies) SetConfig(cfg *config.Config)

SetConfig atomically sets the configuration (thread-safe)

func (*ServerDependencies) Shutdown

func (d *ServerDependencies) Shutdown()

Shutdown gracefully shuts down server resources, including the WebSocket hub

type TranslationModelsRequest

type TranslationModelsRequest struct {
	Provider string `json:"provider" binding:"required"` // openai (OpenAI-compatible only for now)
	BaseURL  string `json:"base_url" binding:"required"` // API base URL (e.g., https://api.openai.com/v1)
	APIKey   string `json:"api_key"`                     // Provider API key
}

TranslationModelsRequest represents a request to fetch available translation models.

type TranslationModelsResponse

type TranslationModelsResponse struct {
	Models []string `json:"models"`
}

TranslationModelsResponse represents the model discovery response.

type UpdateMovieRequest

type UpdateMovieRequest struct {
	Movie *models.Movie `json:"movie" binding:"required"`
}

UpdateMovieRequest represents the update movie request payload

Jump to

Keyboard shortcuts

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