From 173bd4ef1c88a80d81cdc796c054db7714c3a09d Mon Sep 17 00:00:00 2001 From: sigmanor Date: Tue, 8 Sep 2026 12:33:23 +0300 Subject: [PATCH 1/2] fix(store): keep the scheduled collect off the paused OssInsight source OssInsight stopped computing its star-based rankings, so a collect run with `resource = 'ossinsight'` fetches nothing. Reset the stored value to `github` during the collect_settings migration, so an existing deployment does not keep running a cron that quietly collects zero posts. The statement is idempotent and also guards against the value reaching the table by another route. Drop it together with re-enabling the option in content-sentinel. --- internal/store/sqlite_store.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/store/sqlite_store.go b/internal/store/sqlite_store.go index e9238b2..eb4811e 100644 --- a/internal/store/sqlite_store.go +++ b/internal/store/sqlite_store.go @@ -231,6 +231,13 @@ func migrateCollectSettingsSchema(db *sql.DB) error { } } + // The OssInsight trending metric is unavailable upstream (since 2026-03-01), + // so keep the scheduled collect on GitHub instead of silently fetching nothing. + // Drop this together with re-enabling the option in content-sentinel. + if _, err := db.Exec("UPDATE collect_settings SET resource = 'github' WHERE resource = 'ossinsight'"); err != nil { + return fmt.Errorf("failed to reset paused resource: %v", err) + } + return nil } From b113aac71a76d630ee843c80d6ba08c8308819a9 Mon Sep 17 00:00:00 2001 From: sigmanor Date: Tue, 8 Sep 2026 12:55:46 +0300 Subject: [PATCH 2/2] fix(api): reject a paused collect source when settings are saved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migration only resets the stored resource at start-up, so a direct API call in place of content-sentinel — where the option is merely disabled in the dropdown — could write 'ossinsight' back and leave the cron collecting zero posts again until the next restart. Validate the resource in the handler, following the ValidatePromptSettings pattern, with the paused sources named separately so the error says why the value is refused. The store keeps accepting any value, so the round-trip test there is unaffected. --- internal/server/api.go | 7 +++++++ internal/validation/collect.go | 30 +++++++++++++++++++++++++++++ internal/validation/collect_test.go | 28 +++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 internal/validation/collect.go create mode 100644 internal/validation/collect_test.go diff --git a/internal/server/api.go b/internal/server/api.go index 5d90118..5917f10 100644 --- a/internal/server/api.go +++ b/internal/server/api.go @@ -183,6 +183,13 @@ func (api *CronAPI) UpdateCollectSettings(w http.ResponseWriter, r *http.Request http.Error(w, "SpokenLanguageCode cannot be empty", http.StatusBadRequest) return } + if settings.Resource == "" { + settings.Resource = store.DefaultResource + } + if err := validation.ValidateCollectResource(settings.Resource); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } if err := api.store.UpdateCollectSettings(&settings); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/internal/validation/collect.go b/internal/validation/collect.go new file mode 100644 index 0000000..2291837 --- /dev/null +++ b/internal/validation/collect.go @@ -0,0 +1,30 @@ +package validation + +import ( + "errors" + "fmt" + "slices" + "strings" +) + +var validCollectResources = []string{"github"} + +// pausedCollectResources are sources whose upstream ranking is unavailable, so a +// collect run against them returns nothing. OssInsight paused its star-based +// rankings on 2026-03-01; move it back to validCollectResources once the metric +// returns. +var pausedCollectResources = []string{"ossinsight"} + +// ValidateCollectResource reports whether resource can be used for a collect run. +// An empty value is left to the caller to default. +func ValidateCollectResource(resource string) error { + if slices.Contains(pausedCollectResources, resource) { + return fmt.Errorf("resource %q is paused upstream and cannot be scheduled", resource) + } + + if !slices.Contains(validCollectResources, resource) { + return errors.New("invalid resource, allowed values: " + strings.Join(validCollectResources, ", ")) + } + + return nil +} diff --git a/internal/validation/collect_test.go b/internal/validation/collect_test.go new file mode 100644 index 0000000..66e0c81 --- /dev/null +++ b/internal/validation/collect_test.go @@ -0,0 +1,28 @@ +package validation + +import "testing" + +func TestValidateCollectResource(t *testing.T) { + tests := []struct { + name string + resource string + expectErr bool + }{ + {name: "github is supported", resource: "github", expectErr: false}, + {name: "paused source is rejected", resource: "ossinsight", expectErr: true}, + {name: "unknown source is rejected", resource: "gitlab", expectErr: true}, + {name: "empty source is rejected", resource: "", expectErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateCollectResource(tt.resource) + if tt.expectErr && err == nil { + t.Fatalf("expected an error for resource %q", tt.resource) + } + if !tt.expectErr && err != nil { + t.Fatalf("unexpected error for resource %q: %v", tt.resource, err) + } + }) + } +}