feat: indexers and download clients you add and configure (M502 #5518)
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
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
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>
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
// 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
|
||||
}
|
||||
@@ -195,6 +195,28 @@ type AcoustidSetting struct {
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type AcquisitionService struct {
|
||||
Code string
|
||||
Kind string
|
||||
Protocol string
|
||||
Label string
|
||||
Enabled bool
|
||||
BaseUrl string
|
||||
ApiKey string
|
||||
Username string
|
||||
Password string
|
||||
Category string
|
||||
TimeoutMs int32
|
||||
ServesSearch bool
|
||||
ServesDownloads bool
|
||||
ServesEnrichment bool
|
||||
LocalPath string
|
||||
RemotePath string
|
||||
SearchesPerHour int32
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
ID pgtype.UUID
|
||||
Title string
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS acquisition_services;
|
||||
@@ -0,0 +1,63 @@
|
||||
-- 0079_acquisition_services.up.sql — the indexers and download clients the
|
||||
-- operator adds in Admin → Integrations (Scribe milestone #502, #5518).
|
||||
--
|
||||
-- Minstrel replaces Lidarr, so it searches and downloads itself. Nothing is
|
||||
-- seeded and nothing is copied from Lidarr: a connection is a row the operator
|
||||
-- adds, from a list of what Minstrel can speak. Ported from Fabled
|
||||
-- Librarian's acquisition_services (its migrations 0044, 0055, 0062, 0064,
|
||||
-- 0070), which settled the shape:
|
||||
--
|
||||
-- kind which list the row appears in: search (Indexers) or download
|
||||
-- (Download clients).
|
||||
-- protocol which client is built: prowlarr (its native /api/v1/search,
|
||||
-- because Prowlarr has no aggregate Torznab feed), torznab (any
|
||||
-- single feed), qbittorrent (the WebUI v2), exchange.
|
||||
--
|
||||
-- FabledExchange is ONE row under Download clients with a switch per job:
|
||||
-- downloading, searching its DHT index, and describing a torrent's files and
|
||||
-- swarm before a grab. The serves_* switches mean something on an exchange
|
||||
-- row only; every other row does the one job its kind names.
|
||||
--
|
||||
-- Secrets (api_key, password) are write-only: no query the API answers with
|
||||
-- returns them, only whether one is set.
|
||||
CREATE TABLE acquisition_services (
|
||||
-- code is the stable identity: the protocol for the first of its kind,
|
||||
-- then protocol-2, protocol-3. Legible in a log, never typed.
|
||||
code text PRIMARY KEY,
|
||||
kind text NOT NULL
|
||||
CONSTRAINT acquisition_services_kind_check CHECK (kind IN ('search', 'download')),
|
||||
protocol text NOT NULL
|
||||
CONSTRAINT acquisition_services_protocol_check
|
||||
CHECK (protocol IN ('prowlarr', 'torznab', 'qbittorrent', 'exchange')),
|
||||
label text NOT NULL,
|
||||
-- A new row is off: it has no address yet.
|
||||
enabled boolean NOT NULL DEFAULT false,
|
||||
-- The address as the operator's browser opens it. For a Torznab feed it
|
||||
-- is the whole feed URL up to and including /api.
|
||||
base_url text NOT NULL DEFAULT '',
|
||||
api_key text NOT NULL DEFAULT '',
|
||||
-- qBittorrent only. Both may be empty: a WebUI with auth bypassed for
|
||||
-- local addresses takes neither.
|
||||
username text NOT NULL DEFAULT '',
|
||||
password text NOT NULL DEFAULT '',
|
||||
-- Download clients: everything Minstrel adds is filed under it.
|
||||
category text NOT NULL DEFAULT '',
|
||||
timeout_ms integer NOT NULL DEFAULT 30000
|
||||
CONSTRAINT acquisition_services_timeout_check CHECK (timeout_ms BETWEEN 1000 AND 300000),
|
||||
serves_search boolean NOT NULL DEFAULT true,
|
||||
serves_downloads boolean NOT NULL DEFAULT true,
|
||||
serves_enrichment boolean NOT NULL DEFAULT true,
|
||||
-- One downloads folder, two names: where Minstrel has it mounted, and
|
||||
-- where the client sees it (empty = the same path, nothing translated).
|
||||
local_path text NOT NULL DEFAULT '/downloads'
|
||||
CONSTRAINT acquisition_services_local_path_check CHECK (local_path LIKE '/%'),
|
||||
remote_path text NOT NULL DEFAULT ''
|
||||
CONSTRAINT acquisition_services_remote_path_check CHECK (remote_path = '' OR remote_path LIKE '/%'),
|
||||
-- The most searches the schedule sends this source an hour; 0 = unpaced.
|
||||
-- Prowlarr's indexers answer 429 when asked too often; an Exchange
|
||||
-- searches its own database and starts unpaced.
|
||||
searches_per_hour integer NOT NULL DEFAULT 120
|
||||
CONSTRAINT acquisition_services_searches_per_hour_check CHECK (searches_per_hour BETWEEN 0 AND 3600),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,76 @@
|
||||
-- Indexers and download clients (M502 #5518).
|
||||
|
||||
-- name: ListAcquisitionServices :many
|
||||
-- Returns no secret, only whether one is set: this feeds the settings page.
|
||||
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;
|
||||
|
||||
-- name: GetAcquisitionService :one
|
||||
-- The full row, credentials included. For building a client, never for a
|
||||
-- response.
|
||||
SELECT * FROM acquisition_services WHERE code = sqlc.arg(code);
|
||||
|
||||
-- name: ListUsableAcquisitionServices :many
|
||||
-- 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.
|
||||
SELECT * FROM acquisition_services
|
||||
WHERE enabled AND base_url <> ''
|
||||
AND CASE WHEN protocol = 'exchange' THEN
|
||||
CASE sqlc.arg(job)::text
|
||||
WHEN 'search' THEN serves_search
|
||||
WHEN 'download' THEN serves_downloads
|
||||
WHEN 'enrichment' THEN serves_enrichment
|
||||
ELSE false
|
||||
END
|
||||
ELSE kind = sqlc.arg(job)::text
|
||||
END
|
||||
ORDER BY code;
|
||||
|
||||
-- name: CreateAcquisitionService :one
|
||||
-- ON CONFLICT so the caller can try the next code when this one is taken.
|
||||
INSERT INTO acquisition_services
|
||||
(code, kind, protocol, label, category, timeout_ms, searches_per_hour)
|
||||
VALUES
|
||||
(sqlc.arg(code), sqlc.arg(kind), sqlc.arg(protocol), sqlc.arg(label),
|
||||
sqlc.arg(category), sqlc.arg(timeout_ms), sqlc.arg(searches_per_hour))
|
||||
ON CONFLICT (code) DO NOTHING
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateAcquisitionService :one
|
||||
-- Everything on the settings form except the credentials, which have their
|
||||
-- own write-only calls.
|
||||
UPDATE acquisition_services SET
|
||||
label = sqlc.arg(label),
|
||||
enabled = sqlc.arg(enabled),
|
||||
base_url = sqlc.arg(base_url),
|
||||
username = sqlc.arg(username),
|
||||
category = sqlc.arg(category),
|
||||
timeout_ms = sqlc.arg(timeout_ms),
|
||||
serves_search = sqlc.arg(serves_search),
|
||||
serves_downloads = sqlc.arg(serves_downloads),
|
||||
serves_enrichment = sqlc.arg(serves_enrichment),
|
||||
local_path = sqlc.arg(local_path),
|
||||
remote_path = sqlc.arg(remote_path),
|
||||
searches_per_hour = sqlc.arg(searches_per_hour),
|
||||
updated_at = now()
|
||||
WHERE code = sqlc.arg(code)
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteAcquisitionService :execrows
|
||||
DELETE FROM acquisition_services WHERE code = sqlc.arg(code);
|
||||
|
||||
-- name: SetAcquisitionServiceAPIKey :execrows
|
||||
UPDATE acquisition_services SET api_key = sqlc.arg(api_key), updated_at = now()
|
||||
WHERE code = sqlc.arg(code);
|
||||
|
||||
-- name: SetAcquisitionServicePassword :execrows
|
||||
UPDATE acquisition_services SET password = sqlc.arg(password), updated_at = now()
|
||||
WHERE code = sqlc.arg(code);
|
||||
Reference in New Issue
Block a user