fix(requests): clear prior failed rows on re-request
{"subject":"fix(requests): clear prior failed rows on re-request","body":""}
This commit is contained in:
@@ -182,6 +182,23 @@ func (r *Repository) ListActiveByTMDB(ctx context.Context, mediaType MediaType,
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteFailedByTMDB(ctx context.Context, mediaType MediaType, tmdbID int) (int, error) {
|
||||
if tmdbID <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
tag, err := r.pool.Exec(ctx, `
|
||||
DELETE FROM media_requests
|
||||
WHERE media_type = $1
|
||||
AND provider = 'tmdb'
|
||||
AND tmdb_id = $2
|
||||
AND outcome = 'failed'
|
||||
`, mediaType, tmdbID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("delete failed requests by tmdb: %w", err)
|
||||
}
|
||||
return int(tag.RowsAffected()), nil
|
||||
}
|
||||
|
||||
// quotaLockNamespace partitions advisory locks so request-quota locks do not
|
||||
// collide with advisory locks held elsewhere in the database. The value is
|
||||
// arbitrary; what matters is that it is stable.
|
||||
|
||||
@@ -278,6 +278,12 @@ func (s *Service) CreateRequest(ctx context.Context, viewer Viewer, input Create
|
||||
return nil, ErrAlreadyRequested
|
||||
}
|
||||
|
||||
// Re-requesting media that previously failed (e.g., transient integration
|
||||
// error) should not leave stale failed rows behind in user/admin lists.
|
||||
if _, err := s.store.DeleteFailedByTMDB(ctx, normalized.MediaType, normalized.TMDBID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policy, err := s.EffectivePolicy(ctx, viewer.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -349,6 +349,41 @@ func TestCreateRequestNoActiveDuplicateCreatesRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRequestClearsPriorFailedRequest(t *testing.T) {
|
||||
store := newFakeStore()
|
||||
store.settings.RequestsEnabled = true
|
||||
store.requests["req-prior-failed"] = &Request{
|
||||
ID: "req-prior-failed",
|
||||
MediaType: MediaTypeMovie,
|
||||
TMDBID: 550,
|
||||
Outcome: OutcomeFailed,
|
||||
Status: StatusApproved,
|
||||
LastError: "arr: decode response: json: cannot unmarshal object into Go value of type []radarr.movieResource",
|
||||
}
|
||||
store.requests["req-other-media-failed"] = &Request{
|
||||
ID: "req-other-media-failed",
|
||||
MediaType: MediaTypeMovie,
|
||||
TMDBID: 999,
|
||||
Outcome: OutcomeFailed,
|
||||
}
|
||||
service := newTestService(store)
|
||||
|
||||
_, err := service.CreateRequest(context.Background(), testViewer(1), CreateRequestInput{
|
||||
MediaType: MediaTypeMovie,
|
||||
TMDBID: 550,
|
||||
Title: "Fight Club",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateRequest returned error: %v", err)
|
||||
}
|
||||
if _, ok := store.requests["req-prior-failed"]; ok {
|
||||
t.Fatal("prior failed request was not cleared")
|
||||
}
|
||||
if _, ok := store.requests["req-other-media-failed"]; !ok {
|
||||
t.Fatal("failed request for different media should not be cleared")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchMarksSeriesAvailableByHydratedTVDBID(t *testing.T) {
|
||||
store := newFakeStore()
|
||||
store.settings.RequestsEnabled = true
|
||||
@@ -905,6 +940,19 @@ func (f *fakeStore) ListActiveByTMDB(_ context.Context, mediaType MediaType, ids
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) DeleteFailedByTMDB(_ context.Context, mediaType MediaType, tmdbID int) (int, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
deleted := 0
|
||||
for id, req := range f.requests {
|
||||
if req.MediaType == mediaType && req.TMDBID == tmdbID && req.Outcome == OutcomeFailed {
|
||||
delete(f.requests, id)
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) CreateRequest(_ context.Context, input CreateRequestRecord) (*Request, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
@@ -12,6 +12,9 @@ type Store interface {
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user