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/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 } 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) + } + }) + } +}