Skip to content

Commit d647c01

Browse files
committed
refactor(crossseed): remove legacy completion settings and related tests
This commit removes the CrossSeedCompletionSettings structure and its associated fields from various models, tests, and the database schema. The changes streamline the automation settings by eliminating deprecated completion configurations, ensuring a cleaner and more maintainable codebase. Additionally, a migration script is added to drop legacy completion columns from the database.
1 parent f8f65a8 commit d647c01

8 files changed

Lines changed: 95 additions & 302 deletions

File tree

internal/api/handlers/crossseed.go

Lines changed: 23 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -32,42 +32,32 @@ type automationSettingsRequest struct {
3232
Enabled bool `json:"enabled"`
3333
RunIntervalMinutes int `json:"runIntervalMinutes"`
3434
StartPaused bool `json:"startPaused"`
35-
Category *string `json:"category"`
36-
IgnorePatterns []string `json:"ignorePatterns"`
37-
TargetInstanceIDs []int `json:"targetInstanceIds"`
38-
TargetIndexerIDs []int `json:"targetIndexerIds"`
39-
MaxResultsPerRun int `json:"maxResultsPerRun"` // Deprecated: automation now processes full feeds and ignores this value
40-
FindIndividualEpisodes bool `json:"findIndividualEpisodes"`
41-
SizeMismatchTolerancePercent float64 `json:"sizeMismatchTolerancePercent"`
42-
UseCategoryFromIndexer bool `json:"useCategoryFromIndexer"`
43-
UseCrossCategorySuffix bool `json:"useCrossCategorySuffix"`
44-
RunExternalProgramID *int `json:"runExternalProgramId"`
45-
Completion *completionSettingsRequest `json:"completion"`
46-
}
47-
48-
type completionSettingsRequest struct {
49-
Enabled bool `json:"enabled"`
50-
Categories []string `json:"categories"`
51-
Tags []string `json:"tags"`
52-
ExcludeCategories []string `json:"excludeCategories"`
53-
ExcludeTags []string `json:"excludeTags"`
35+
Category *string `json:"category"`
36+
IgnorePatterns []string `json:"ignorePatterns"`
37+
TargetInstanceIDs []int `json:"targetInstanceIds"`
38+
TargetIndexerIDs []int `json:"targetIndexerIds"`
39+
MaxResultsPerRun int `json:"maxResultsPerRun"` // Deprecated: automation now processes full feeds and ignores this value
40+
FindIndividualEpisodes bool `json:"findIndividualEpisodes"`
41+
SizeMismatchTolerancePercent float64 `json:"sizeMismatchTolerancePercent"`
42+
UseCategoryFromIndexer bool `json:"useCategoryFromIndexer"`
43+
UseCrossCategorySuffix bool `json:"useCrossCategorySuffix"`
44+
RunExternalProgramID *int `json:"runExternalProgramId"`
5445
}
5546

5647
type automationSettingsPatchRequest struct {
57-
Enabled *bool `json:"enabled,omitempty"`
58-
RunIntervalMinutes *int `json:"runIntervalMinutes,omitempty"`
59-
StartPaused *bool `json:"startPaused,omitempty"`
60-
Category optionalString `json:"category"`
61-
IgnorePatterns *[]string `json:"ignorePatterns,omitempty"`
62-
TargetInstanceIDs *[]int `json:"targetInstanceIds,omitempty"`
63-
TargetIndexerIDs *[]int `json:"targetIndexerIds,omitempty"`
64-
MaxResultsPerRun *int `json:"maxResultsPerRun,omitempty"` // Deprecated: automation now processes full feeds and ignores this value
65-
FindIndividualEpisodes *bool `json:"findIndividualEpisodes,omitempty"`
66-
SizeMismatchTolerancePercent *float64 `json:"sizeMismatchTolerancePercent,omitempty"`
67-
UseCategoryFromIndexer *bool `json:"useCategoryFromIndexer,omitempty"`
68-
UseCrossCategorySuffix *bool `json:"useCrossCategorySuffix,omitempty"`
69-
RunExternalProgramID optionalInt `json:"runExternalProgramId"`
70-
Completion *completionSettingsPatchRequest `json:"completion,omitempty"`
48+
Enabled *bool `json:"enabled,omitempty"`
49+
RunIntervalMinutes *int `json:"runIntervalMinutes,omitempty"`
50+
StartPaused *bool `json:"startPaused,omitempty"`
51+
Category optionalString `json:"category"`
52+
IgnorePatterns *[]string `json:"ignorePatterns,omitempty"`
53+
TargetInstanceIDs *[]int `json:"targetInstanceIds,omitempty"`
54+
TargetIndexerIDs *[]int `json:"targetIndexerIds,omitempty"`
55+
MaxResultsPerRun *int `json:"maxResultsPerRun,omitempty"` // Deprecated: automation now processes full feeds and ignores this value
56+
FindIndividualEpisodes *bool `json:"findIndividualEpisodes,omitempty"`
57+
SizeMismatchTolerancePercent *float64 `json:"sizeMismatchTolerancePercent,omitempty"`
58+
UseCategoryFromIndexer *bool `json:"useCategoryFromIndexer,omitempty"`
59+
UseCrossCategorySuffix *bool `json:"useCrossCategorySuffix,omitempty"`
60+
RunExternalProgramID optionalInt `json:"runExternalProgramId"`
7161
// Source-specific tagging
7262
RSSAutomationTags *[]string `json:"rssAutomationTags,omitempty"`
7363
SeededSearchTags *[]string `json:"seededSearchTags,omitempty"`
@@ -76,14 +66,6 @@ type automationSettingsPatchRequest struct {
7666
InheritSourceTags *bool `json:"inheritSourceTags,omitempty"`
7767
}
7868

79-
type completionSettingsPatchRequest struct {
80-
Enabled *bool `json:"enabled,omitempty"`
81-
Categories *[]string `json:"categories,omitempty"`
82-
Tags *[]string `json:"tags,omitempty"`
83-
ExcludeCategories *[]string `json:"excludeCategories,omitempty"`
84-
ExcludeTags *[]string `json:"excludeTags,omitempty"`
85-
}
86-
8769
type optionalString struct {
8870
Set bool
8971
Value *string
@@ -154,22 +136,13 @@ func (r automationSettingsPatchRequest) isEmpty() bool {
154136
r.UseCategoryFromIndexer == nil &&
155137
r.UseCrossCategorySuffix == nil &&
156138
!r.RunExternalProgramID.Set &&
157-
(r.Completion == nil || r.Completion.isEmpty()) &&
158139
r.RSSAutomationTags == nil &&
159140
r.SeededSearchTags == nil &&
160141
r.CompletionSearchTags == nil &&
161142
r.WebhookTags == nil &&
162143
r.InheritSourceTags == nil
163144
}
164145

165-
func (r completionSettingsPatchRequest) isEmpty() bool {
166-
return r.Enabled == nil &&
167-
r.Categories == nil &&
168-
r.Tags == nil &&
169-
r.ExcludeCategories == nil &&
170-
r.ExcludeTags == nil
171-
}
172-
173146
func applyAutomationSettingsPatch(settings *models.CrossSeedAutomationSettings, patch automationSettingsPatchRequest) {
174147
if patch.Enabled != nil {
175148
settings.Enabled = *patch.Enabled
@@ -219,9 +192,6 @@ func applyAutomationSettingsPatch(settings *models.CrossSeedAutomationSettings,
219192
if patch.RunExternalProgramID.Set {
220193
settings.RunExternalProgramID = patch.RunExternalProgramID.Value
221194
}
222-
if patch.Completion != nil {
223-
applyCompletionSettingsPatch(&settings.Completion, patch.Completion)
224-
}
225195
// Source-specific tagging
226196
if patch.RSSAutomationTags != nil {
227197
settings.RSSAutomationTags = *patch.RSSAutomationTags
@@ -240,24 +210,6 @@ func applyAutomationSettingsPatch(settings *models.CrossSeedAutomationSettings,
240210
}
241211
}
242212

243-
func applyCompletionSettingsPatch(dest *models.CrossSeedCompletionSettings, patch *completionSettingsPatchRequest) {
244-
if patch.Enabled != nil {
245-
dest.Enabled = *patch.Enabled
246-
}
247-
if patch.Categories != nil {
248-
dest.Categories = *patch.Categories
249-
}
250-
if patch.Tags != nil {
251-
dest.Tags = *patch.Tags
252-
}
253-
if patch.ExcludeCategories != nil {
254-
dest.ExcludeCategories = *patch.ExcludeCategories
255-
}
256-
if patch.ExcludeTags != nil {
257-
dest.ExcludeTags = *patch.ExcludeTags
258-
}
259-
}
260-
261213
type automationRunRequest struct {
262214
DryRun bool `json:"dryRun"`
263215
}
@@ -614,17 +566,6 @@ func (h *CrossSeedHandler) UpdateAutomationSettings(w http.ResponseWriter, r *ht
614566
}
615567
}
616568

617-
completion := models.DefaultCrossSeedCompletionSettings()
618-
if req.Completion != nil {
619-
completion = models.CrossSeedCompletionSettings{
620-
Enabled: req.Completion.Enabled,
621-
Categories: req.Completion.Categories,
622-
Tags: req.Completion.Tags,
623-
ExcludeCategories: req.Completion.ExcludeCategories,
624-
ExcludeTags: req.Completion.ExcludeTags,
625-
}
626-
}
627-
628569
// Validate mutual exclusivity: cannot use both indexer category and .cross suffix
629570
if req.UseCategoryFromIndexer && req.UseCrossCategorySuffix {
630571
RespondError(w, http.StatusBadRequest, "Cannot enable both 'Use indexer name as category' and 'Add .cross category suffix'. These settings are mutually exclusive.")
@@ -645,7 +586,6 @@ func (h *CrossSeedHandler) UpdateAutomationSettings(w http.ResponseWriter, r *ht
645586
UseCategoryFromIndexer: req.UseCategoryFromIndexer,
646587
UseCrossCategorySuffix: req.UseCrossCategorySuffix,
647588
RunExternalProgramID: req.RunExternalProgramID,
648-
Completion: completion,
649589
}
650590

651591
updated, err := h.service.UpdateAutomationSettings(r.Context(), settings)

internal/api/handlers/crossseed_patch_test.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,6 @@ func TestApplyAutomationSettingsPatch_MergesFields(t *testing.T) {
2828
SizeMismatchTolerancePercent: 5.0,
2929
UseCategoryFromIndexer: false,
3030
RunExternalProgramID: ptrInt(42),
31-
Completion: models.CrossSeedCompletionSettings{
32-
Enabled: false,
33-
Categories: []string{"tv"},
34-
Tags: []string{"cross-seed"},
35-
ExcludeCategories: []string{"anime"},
36-
ExcludeTags: []string{"skip"},
37-
},
3831
}
3932

4033
newCategory := " movies "
@@ -53,13 +46,6 @@ func TestApplyAutomationSettingsPatch_MergesFields(t *testing.T) {
5346
SizeMismatchTolerancePercent: ptrFloat(12.5),
5447
UseCategoryFromIndexer: ptrBool(true),
5548
RunExternalProgramID: optionalInt{Set: true, Value: nil},
56-
Completion: &completionSettingsPatchRequest{
57-
Enabled: ptrBool(true),
58-
Categories: &[]string{"movies"},
59-
Tags: &[]string{"cross"},
60-
ExcludeCategories: &[]string{"music"},
61-
ExcludeTags: &[]string{"x265"},
62-
},
6349
}
6450

6551
applyAutomationSettingsPatch(&existing, patch)
@@ -113,20 +99,6 @@ func TestApplyAutomationSettingsPatch_MergesFields(t *testing.T) {
11399
if existing.RunExternalProgramID != nil {
114100
t.Fatalf("expected runExternalProgramID to be nil")
115101
}
116-
if existing.Completion.Enabled != true ||
117-
len(existing.Completion.Categories) != 1 ||
118-
existing.Completion.Categories[0] != "movies" {
119-
t.Fatalf("unexpected completion categories: %#v", existing.Completion)
120-
}
121-
if existing.Completion.Tags[0] != "cross" {
122-
t.Fatalf("unexpected completion tags: %#v", existing.Completion.Tags)
123-
}
124-
if existing.Completion.ExcludeCategories[0] != "music" {
125-
t.Fatalf("unexpected completion exclude categories: %#v", existing.Completion.ExcludeCategories)
126-
}
127-
if existing.Completion.ExcludeTags[0] != "x265" {
128-
t.Fatalf("unexpected completion exclude tags: %#v", existing.Completion.ExcludeTags)
129-
}
130102
}
131103

132104
func TestApplyAutomationSettingsPatch_PreservesUnspecifiedFields(t *testing.T) {
@@ -138,10 +110,6 @@ func TestApplyAutomationSettingsPatch_PreservesUnspecifiedFields(t *testing.T) {
138110
SeededSearchTags: []string{"keep-seeded"},
139111
CompletionSearchTags: []string{"keep-completion"},
140112
WebhookTags: []string{"keep-webhook"},
141-
Completion: models.CrossSeedCompletionSettings{
142-
Enabled: true,
143-
Tags: []string{"keep-tag"},
144-
},
145113
}
146114

147115
patch := automationSettingsPatchRequest{
@@ -166,9 +134,6 @@ func TestApplyAutomationSettingsPatch_PreservesUnspecifiedFields(t *testing.T) {
166134
if len(existing.SeededSearchTags) != 1 || existing.SeededSearchTags[0] != "keep-seeded" {
167135
t.Fatalf("expected seeded search tags to stay unchanged, got %#v", existing.SeededSearchTags)
168136
}
169-
if !existing.Completion.Enabled || existing.Completion.Tags[0] != "keep-tag" {
170-
t.Fatalf("expected completion to stay unchanged, got %#v", existing.Completion)
171-
}
172137
if existing.SizeMismatchTolerancePercent != 20 {
173138
t.Fatalf("expected updated tolerance to be 20, got %.2f", existing.SizeMismatchTolerancePercent)
174139
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
-- Copyright (c) 2025, s0up and the autobrr contributors.
2+
-- SPDX-License-Identifier: GPL-2.0-or-later
3+
4+
-- Remove legacy global completion columns from cross_seed_settings.
5+
-- These were replaced by per-instance settings in instance_crossseed_completion_settings (migration 029).
6+
7+
-- SQLite does not support DROP COLUMN directly, so we must recreate the table.
8+
-- Step 1: Create new table without the completion columns
9+
CREATE TABLE cross_seed_settings_new (
10+
id INTEGER PRIMARY KEY CHECK (id = 1),
11+
enabled BOOLEAN NOT NULL DEFAULT 0,
12+
run_interval_minutes INTEGER NOT NULL DEFAULT 120,
13+
start_paused BOOLEAN NOT NULL DEFAULT 1,
14+
category TEXT,
15+
ignore_patterns TEXT NOT NULL DEFAULT '[]',
16+
target_instance_ids TEXT NOT NULL DEFAULT '[]',
17+
target_indexer_ids TEXT NOT NULL DEFAULT '[]',
18+
max_results_per_run INTEGER NOT NULL DEFAULT 50,
19+
find_individual_episodes BOOLEAN NOT NULL DEFAULT 0,
20+
size_mismatch_tolerance_percent REAL NOT NULL DEFAULT 5.0,
21+
use_category_from_indexer BOOLEAN NOT NULL DEFAULT 0,
22+
run_external_program_id INTEGER REFERENCES external_programs(id) ON DELETE SET NULL,
23+
rss_automation_tags TEXT NOT NULL DEFAULT '["cross-seed"]',
24+
seeded_search_tags TEXT NOT NULL DEFAULT '["cross-seed"]',
25+
completion_search_tags TEXT NOT NULL DEFAULT '["cross-seed"]',
26+
webhook_tags TEXT NOT NULL DEFAULT '["cross-seed"]',
27+
inherit_source_tags BOOLEAN NOT NULL DEFAULT 0,
28+
use_cross_category_suffix BOOLEAN NOT NULL DEFAULT 1,
29+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
30+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
31+
);
32+
33+
-- Step 2: Copy data from old table (excluding dropped columns)
34+
INSERT INTO cross_seed_settings_new (
35+
id, enabled, run_interval_minutes, start_paused, category,
36+
ignore_patterns, target_instance_ids, target_indexer_ids,
37+
max_results_per_run, find_individual_episodes, size_mismatch_tolerance_percent,
38+
use_category_from_indexer, run_external_program_id,
39+
rss_automation_tags, seeded_search_tags, completion_search_tags,
40+
webhook_tags, inherit_source_tags, use_cross_category_suffix,
41+
created_at, updated_at
42+
)
43+
SELECT
44+
id, enabled, run_interval_minutes, start_paused, category,
45+
ignore_patterns, target_instance_ids, target_indexer_ids,
46+
max_results_per_run, find_individual_episodes, size_mismatch_tolerance_percent,
47+
use_category_from_indexer, run_external_program_id,
48+
rss_automation_tags, seeded_search_tags, completion_search_tags,
49+
webhook_tags, inherit_source_tags, use_cross_category_suffix,
50+
created_at, updated_at
51+
FROM cross_seed_settings;
52+
53+
-- Step 3: Drop old table
54+
DROP TABLE cross_seed_settings;
55+
56+
-- Step 4: Rename new table
57+
ALTER TABLE cross_seed_settings_new RENAME TO cross_seed_settings;
58+
59+
-- Step 5: Recreate the update trigger
60+
CREATE TRIGGER IF NOT EXISTS trg_cross_seed_settings_updated
61+
AFTER UPDATE ON cross_seed_settings
62+
BEGIN
63+
UPDATE cross_seed_settings
64+
SET updated_at = CURRENT_TIMESTAMP
65+
WHERE id = NEW.id;
66+
END;

0 commit comments

Comments
 (0)