feat(store,runner,web): channel unavailability notice (migration 013)
YouTube channels that 404 on playlist discovery (deleted/private) are now: 1. Wrapped in domain.ErrChannelUnavailable by the YouTube adapter (instead of a generic error), so the runner can identify them without string-matching. 2. Stored per-user in channel_errors (migration 013, RLS-guarded) via runner's new UpsertChannelError path — removed from the generic Errors counter, counted separately as ChannelUnavailable. 3. Shown on the account page under "Unavailable channels" with name, chip-warn badge, and first-seen date, so users know why some subscribed channels produce no videos.
This commit is contained in:
@@ -216,6 +216,9 @@ func (a *Adapter) NewVideos(ctx context.Context, sub domain.Subscription) ([]dom
|
||||
|
||||
var resp playlistItemListResponse
|
||||
if err := a.getJSON(ctx, client, "/playlistItems", q, &resp); err != nil {
|
||||
if isHTTP404(err) {
|
||||
return nil, &domain.ErrChannelUnavailable{ChannelID: sub.ChannelID, ChannelTitle: sub.ChannelTitle}
|
||||
}
|
||||
return nil, fmt.Errorf("new videos for channel %q: %w", sub.ChannelID, err)
|
||||
}
|
||||
|
||||
@@ -269,6 +272,12 @@ func (a *Adapter) resolveUploadsPlaylist(ctx context.Context, client *http.Clien
|
||||
return resp.Items[0].ContentDetails.RelatedPlaylists.Uploads, nil
|
||||
}
|
||||
|
||||
// isHTTP404 reports whether err came from a YouTube API call that returned HTTP 404.
|
||||
// getRaw encodes the status as "youtube api <path>: status 404: ...".
|
||||
func isHTTP404(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "status 404")
|
||||
}
|
||||
|
||||
// getJSON issues a GET and decodes a JSON body into out. A non-200 status is an
|
||||
// error carrying a bounded slice of the response body for diagnosis.
|
||||
func (a *Adapter) getJSON(ctx context.Context, client *http.Client, path string, q url.Values, out any) error {
|
||||
|
||||
Reference in New Issue
Block a user