Files
minstrel/internal/db/dbq/acquisition.sql.go
T
bvandeusenandClaude Opus 5.5 a2e1890d4c
release / govulncheck (push) Successful in 34s
release / web (push) Successful in 1m13s
release / go (push) Successful in 2m12s
release / integration (push) Successful in 4m6s
release / Build signed APK (releases and dev) (push) Successful in 8m41s
release / android (push) Successful in 9m47s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 59s
release / Verify release artifacts (tag releases only) (push) Skipped
feat: indexers and download clients you add and configure (M502 #5518)
Admin → Integrations gains "Indexers and download clients": two lists of
connections the operator adds, Fabled Librarian's model. Nothing is seeded
or copied from Lidarr.

- Indexers: Prowlarr (its native /api/v1/search, which asks every indexer
  at once; Prowlarr has no aggregate Torznab feed) and any Torznab feed.
- Download clients: qBittorrent (WebUI v2, with re-login on an expired
  session) and FabledExchange, one entry with a switch per job:
  downloads (/api/downloads), search (/api/search) and release details
  (/api/torrents/{infohash}). All three start on; its key is optional.
- Each entry: enable, name, address, a write-only key or password, Test.
  A download client's Test lists its torrents and checks the downloads
  folder is visible and writable from Minstrel; an Exchange's Test
  reports each job switched on.
- Download clients carry a path mapping (Minstrel's /downloads vs the
  client's own name for it); search sources carry searches per hour.

Migration 0079 adds acquisition_services with its CHECKs. Ported from
Fabled Librarian: internal/indexer (Torznab, 429 Retry-After, magnet
redirects), internal/prowlarr, internal/exchange, internal/downloader,
and internal/acquire's one builder per kind, which Test already uses
so a passing Test vouches for the path a grab will take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:34:51 -04:00

363 lines
9.4 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: acquisition.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createAcquisitionService = `-- name: CreateAcquisitionService :one
INSERT INTO acquisition_services
(code, kind, protocol, label, category, timeout_ms, searches_per_hour)
VALUES
($1, $2, $3, $4,
$5, $6, $7)
ON CONFLICT (code) DO NOTHING
RETURNING code, kind, protocol, label, enabled, base_url, api_key, username, password, category, timeout_ms, serves_search, serves_downloads, serves_enrichment, local_path, remote_path, searches_per_hour, created_at, updated_at
`
type CreateAcquisitionServiceParams struct {
Code string
Kind string
Protocol string
Label string
Category string
TimeoutMs int32
SearchesPerHour int32
}
// ON CONFLICT so the caller can try the next code when this one is taken.
func (q *Queries) CreateAcquisitionService(ctx context.Context, arg CreateAcquisitionServiceParams) (AcquisitionService, error) {
row := q.db.QueryRow(ctx, createAcquisitionService,
arg.Code,
arg.Kind,
arg.Protocol,
arg.Label,
arg.Category,
arg.TimeoutMs,
arg.SearchesPerHour,
)
var i AcquisitionService
err := row.Scan(
&i.Code,
&i.Kind,
&i.Protocol,
&i.Label,
&i.Enabled,
&i.BaseUrl,
&i.ApiKey,
&i.Username,
&i.Password,
&i.Category,
&i.TimeoutMs,
&i.ServesSearch,
&i.ServesDownloads,
&i.ServesEnrichment,
&i.LocalPath,
&i.RemotePath,
&i.SearchesPerHour,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteAcquisitionService = `-- name: DeleteAcquisitionService :execrows
DELETE FROM acquisition_services WHERE code = $1
`
func (q *Queries) DeleteAcquisitionService(ctx context.Context, code string) (int64, error) {
result, err := q.db.Exec(ctx, deleteAcquisitionService, code)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const getAcquisitionService = `-- name: GetAcquisitionService :one
SELECT code, kind, protocol, label, enabled, base_url, api_key, username, password, category, timeout_ms, serves_search, serves_downloads, serves_enrichment, local_path, remote_path, searches_per_hour, created_at, updated_at FROM acquisition_services WHERE code = $1
`
// The full row, credentials included. For building a client, never for a
// response.
func (q *Queries) GetAcquisitionService(ctx context.Context, code string) (AcquisitionService, error) {
row := q.db.QueryRow(ctx, getAcquisitionService, code)
var i AcquisitionService
err := row.Scan(
&i.Code,
&i.Kind,
&i.Protocol,
&i.Label,
&i.Enabled,
&i.BaseUrl,
&i.ApiKey,
&i.Username,
&i.Password,
&i.Category,
&i.TimeoutMs,
&i.ServesSearch,
&i.ServesDownloads,
&i.ServesEnrichment,
&i.LocalPath,
&i.RemotePath,
&i.SearchesPerHour,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listAcquisitionServices = `-- name: ListAcquisitionServices :many
SELECT code, kind, protocol, label, enabled, base_url,
(api_key <> '')::boolean AS has_api_key,
(password <> '')::boolean AS has_password,
username, category, timeout_ms,
serves_search, serves_downloads, serves_enrichment,
local_path, remote_path, searches_per_hour,
created_at, updated_at
FROM acquisition_services
ORDER BY kind, label, code
`
type ListAcquisitionServicesRow struct {
Code string
Kind string
Protocol string
Label string
Enabled bool
BaseUrl string
HasApiKey bool
HasPassword bool
Username string
Category string
TimeoutMs int32
ServesSearch bool
ServesDownloads bool
ServesEnrichment bool
LocalPath string
RemotePath string
SearchesPerHour int32
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
}
// Indexers and download clients (M502 #5518).
// Returns no secret, only whether one is set: this feeds the settings page.
func (q *Queries) ListAcquisitionServices(ctx context.Context) ([]ListAcquisitionServicesRow, error) {
rows, err := q.db.Query(ctx, listAcquisitionServices)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAcquisitionServicesRow
for rows.Next() {
var i ListAcquisitionServicesRow
if err := rows.Scan(
&i.Code,
&i.Kind,
&i.Protocol,
&i.Label,
&i.Enabled,
&i.BaseUrl,
&i.HasApiKey,
&i.HasPassword,
&i.Username,
&i.Category,
&i.TimeoutMs,
&i.ServesSearch,
&i.ServesDownloads,
&i.ServesEnrichment,
&i.LocalPath,
&i.RemotePath,
&i.SearchesPerHour,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listUsableAcquisitionServices = `-- name: ListUsableAcquisitionServices :many
SELECT code, kind, protocol, label, enabled, base_url, api_key, username, password, category, timeout_ms, serves_search, serves_downloads, serves_enrichment, local_path, remote_path, searches_per_hour, created_at, updated_at FROM acquisition_services
WHERE enabled AND base_url <> ''
AND CASE WHEN protocol = 'exchange' THEN
CASE $1::text
WHEN 'search' THEN serves_search
WHEN 'download' THEN serves_downloads
WHEN 'enrichment' THEN serves_enrichment
ELSE false
END
ELSE kind = $1::text
END
ORDER BY code
`
// Every enabled, addressed service doing one job. An exchange row does a job
// when its switch for it is on; any other row does the job its kind names.
// Ordered by code so a fan-out is deterministic.
func (q *Queries) ListUsableAcquisitionServices(ctx context.Context, job string) ([]AcquisitionService, error) {
rows, err := q.db.Query(ctx, listUsableAcquisitionServices, job)
if err != nil {
return nil, err
}
defer rows.Close()
var items []AcquisitionService
for rows.Next() {
var i AcquisitionService
if err := rows.Scan(
&i.Code,
&i.Kind,
&i.Protocol,
&i.Label,
&i.Enabled,
&i.BaseUrl,
&i.ApiKey,
&i.Username,
&i.Password,
&i.Category,
&i.TimeoutMs,
&i.ServesSearch,
&i.ServesDownloads,
&i.ServesEnrichment,
&i.LocalPath,
&i.RemotePath,
&i.SearchesPerHour,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setAcquisitionServiceAPIKey = `-- name: SetAcquisitionServiceAPIKey :execrows
UPDATE acquisition_services SET api_key = $1, updated_at = now()
WHERE code = $2
`
type SetAcquisitionServiceAPIKeyParams struct {
ApiKey string
Code string
}
func (q *Queries) SetAcquisitionServiceAPIKey(ctx context.Context, arg SetAcquisitionServiceAPIKeyParams) (int64, error) {
result, err := q.db.Exec(ctx, setAcquisitionServiceAPIKey, arg.ApiKey, arg.Code)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const setAcquisitionServicePassword = `-- name: SetAcquisitionServicePassword :execrows
UPDATE acquisition_services SET password = $1, updated_at = now()
WHERE code = $2
`
type SetAcquisitionServicePasswordParams struct {
Password string
Code string
}
func (q *Queries) SetAcquisitionServicePassword(ctx context.Context, arg SetAcquisitionServicePasswordParams) (int64, error) {
result, err := q.db.Exec(ctx, setAcquisitionServicePassword, arg.Password, arg.Code)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const updateAcquisitionService = `-- name: UpdateAcquisitionService :one
UPDATE acquisition_services SET
label = $1,
enabled = $2,
base_url = $3,
username = $4,
category = $5,
timeout_ms = $6,
serves_search = $7,
serves_downloads = $8,
serves_enrichment = $9,
local_path = $10,
remote_path = $11,
searches_per_hour = $12,
updated_at = now()
WHERE code = $13
RETURNING code, kind, protocol, label, enabled, base_url, api_key, username, password, category, timeout_ms, serves_search, serves_downloads, serves_enrichment, local_path, remote_path, searches_per_hour, created_at, updated_at
`
type UpdateAcquisitionServiceParams struct {
Label string
Enabled bool
BaseUrl string
Username string
Category string
TimeoutMs int32
ServesSearch bool
ServesDownloads bool
ServesEnrichment bool
LocalPath string
RemotePath string
SearchesPerHour int32
Code string
}
// Everything on the settings form except the credentials, which have their
// own write-only calls.
func (q *Queries) UpdateAcquisitionService(ctx context.Context, arg UpdateAcquisitionServiceParams) (AcquisitionService, error) {
row := q.db.QueryRow(ctx, updateAcquisitionService,
arg.Label,
arg.Enabled,
arg.BaseUrl,
arg.Username,
arg.Category,
arg.TimeoutMs,
arg.ServesSearch,
arg.ServesDownloads,
arg.ServesEnrichment,
arg.LocalPath,
arg.RemotePath,
arg.SearchesPerHour,
arg.Code,
)
var i AcquisitionService
err := row.Scan(
&i.Code,
&i.Kind,
&i.Protocol,
&i.Label,
&i.Enabled,
&i.BaseUrl,
&i.ApiKey,
&i.Username,
&i.Password,
&i.Category,
&i.TimeoutMs,
&i.ServesSearch,
&i.ServesDownloads,
&i.ServesEnrichment,
&i.LocalPath,
&i.RemotePath,
&i.SearchesPerHour,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}