* docs: design spec for multi-instance Sonarr/Radarr request routing Seerr-style multi-instance arr management inside Silo's request system: many instances per kind, HD/4K default routing, entitlement-driven dual-quality fan-out, per-instance anime overrides (keyword 210024), and a one-to-many media_request_targets model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for multi-instance arr request routing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(requests): migration for multi-instance arr routing Adds migration 169 to convert request_integrations from a one-row-per-kind table keyed on `kind` to a multi-instance table keyed on `id`, with HD/4K defaults, anime overrides, and a new one-to-many media_request_targets table for per-quality fulfillment tracking. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(requests): instance, target, and dual-quality types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(requests): id-based integration CRUD Replace upsert-by-kind (UpsertIntegration/UpsertIntegrations) with GetIntegration, CreateIntegration, UpdateIntegration, DeleteIntegration, and ClearDefault. Rewrites scanIntegration and integrationColumns to cover all new multi-instance columns (id, name, is_4k, is_default, is_default_4k, anime_* fields). Updates the Store interface accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(requests): target persistence and aggregate status * feat(tmdb): expose keyword ids and original language on detail * feat(requests): Seerr-exact anime detection (keyword 210024) * feat(requests): quality/anime routing engine Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(requests): force_dual_quality setting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(requests): multi-target fulfillment, reconcile, retry, and instance CRUD Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(api): request integration CRUD endpoints, targets in responses, entitlement wiring Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): multi-instance request integration types and CRUD hooks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(web): multi-instance arr manager, dual-quality toggle, per-target queue Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): UX review fixes for arr manager (delete confirm, switch hints, test feedback, dirty + target status) * fix(requests): address code-review findings (test-connection by id, HD-only default ceiling, retryable partial failure, idempotent submit, transactional defaults, presence/target reconcile, auto-approve gate) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review (anime override fallback, non-null slices, save gate, a11y, DeleteTarget not-found) - routing: anime fields only override standard root/profile/tags when set, so enabling anime with blank fields reuses standard values instead of clearing them into an invalid submission - api: normalize nil Tags/AnimeTags to [] so they serialize as arrays not null - web: require an API key before saving a NEW instance; add aria-expanded/ aria-controls to the anime-overrides disclosure toggle - repo: DeleteTarget returns ErrNotFound when no row was deleted Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(requests): address PR review findings --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
59 lines
2.8 KiB
Go
59 lines
2.8 KiB
Go
package requests
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
type Store interface {
|
|
GetSettings(ctx context.Context) (Settings, error)
|
|
UpdateSettings(ctx context.Context, settings Settings) (Settings, error)
|
|
GetUserLimit(ctx context.Context, userID int) (*UserLimit, error)
|
|
UpsertUserLimit(ctx context.Context, limit UserLimit) (*UserLimit, error)
|
|
CountUserRequestsSince(ctx context.Context, userID int, since time.Time) (int, error)
|
|
ListActiveByTMDB(ctx context.Context, mediaType MediaType, tmdbIDs []int) (map[int]*Request, error)
|
|
// DeleteFailedByTMDB removes prior failed requests for a given media so a
|
|
// re-request does not leave behind stale rows in user/admin lists.
|
|
DeleteFailedByTMDB(ctx context.Context, mediaType MediaType, tmdbID int) (int, error)
|
|
CreateRequest(ctx context.Context, input CreateRequestRecord) (*Request, error)
|
|
GetRequest(ctx context.Context, id string) (*Request, error)
|
|
ListReconciliationCandidates(ctx context.Context, limit int) ([]*Request, error)
|
|
ListMine(ctx context.Context, userID int, filter ListFilter) ([]*Request, error)
|
|
ListAdmin(ctx context.Context, filter ListFilter) ([]*Request, error)
|
|
SetStatus(ctx context.Context, id string, status Status, actor Viewer) (*Request, error)
|
|
SetOutcome(ctx context.Context, id string, outcome Outcome, actor Viewer, message string) (*Request, error)
|
|
ListTargets(ctx context.Context, requestID string) ([]Target, error)
|
|
CreateTarget(ctx context.Context, target Target) (Target, error)
|
|
DeleteTarget(ctx context.Context, id int64) error
|
|
UpdateTargetStatus(ctx context.Context, targetID int64, status Status, externalID, externalStatus, lastErr string, actor Viewer) (*Request, error)
|
|
ListIntegrations(ctx context.Context) ([]Integration, error)
|
|
GetIntegration(ctx context.Context, id string) (*Integration, error)
|
|
CreateIntegration(ctx context.Context, integration Integration) (*Integration, error)
|
|
UpdateIntegration(ctx context.Context, integration Integration) (*Integration, error)
|
|
// SaveIntegrationWithDefaults clears the conflicting kind default(s) and
|
|
// creates (isCreate) or updates the instance atomically in one transaction.
|
|
SaveIntegrationWithDefaults(ctx context.Context, integration Integration, isCreate bool) (*Integration, error)
|
|
DeleteIntegration(ctx context.Context, id string) error
|
|
}
|
|
|
|
type CreateRequestRecord struct {
|
|
ID string
|
|
Input CreateRequestInput
|
|
Status Status
|
|
Outcome Outcome
|
|
IsAnime bool
|
|
Requester Viewer
|
|
Now time.Time
|
|
// Quota, when non-nil, instructs the store to atomically verify the
|
|
// requester is below their per-user limit before inserting. The check
|
|
// runs inside the same transaction as the insert with a per-user
|
|
// advisory lock so concurrent submissions cannot both exceed the limit.
|
|
Quota *QuotaCheck
|
|
}
|
|
|
|
type QuotaCheck struct {
|
|
UserID int
|
|
WindowStart time.Time
|
|
MaxRequests int
|
|
}
|