feat(artwork): read a single queue row by item

The explain CLI must report whether an item is queued, at what priority and when it
retries; the queue repository could only be drained in eligibility batches, which
cannot see a row that is still backing off.
This commit is contained in:
Deluan 2026-08-14 13:15:24 -04:00
commit 5ba8a9fcb5
4 changed files with 41 additions and 0 deletions

View file

@ -119,6 +119,8 @@ type ArtworkRepository interface {
}
type ArtworkQueueRepository interface {
// Get returns the pending row for an item, or ErrNotFound when it is not queued.
Get(kind Kind, id, imageType string) (*ArtworkQueueItem, error)
// Enqueue upserts; an existing row keeps the higher priority and has its retry_at reset.
Enqueue(items ...ArtworkQueueItem) error
// EnqueuePreservingBackoff upserts like Enqueue but preserves an existing row's retry_at, so a

View file

@ -31,6 +31,16 @@ func NewArtworkQueueRepository(ctx context.Context, db dbx.Builder) model.Artwor
return r
}
func (r *artworkQueueRepository) Get(kind model.Kind, id, imageType string) (*model.ArtworkQueueItem, error) {
var res model.ArtworkQueueItem
err := r.queryOne(Select("*").From(r.tableName).
Where(Eq{"item_kind": kind.Prefix(), "item_id": id, "image_type": imageType}), &res)
if err != nil {
return nil, err
}
return &res, nil
}
// Enqueue also resets enqueued_at, so a fresh request does not inherit an old row's spent retry budget.
func (r *artworkQueueRepository) Enqueue(items ...model.ArtworkQueueItem) error {
return r.enqueue(`ON CONFLICT (item_kind, item_id, image_type) DO UPDATE SET

View file

@ -54,6 +54,22 @@ var _ = Describe("ArtworkQueueRepository", func() {
Expect(got[0].ItemID).To(Equal("high"))
})
It("Get returns a queued row, including one still backing off", func() {
Expect(repo.Enqueue(item("ar", "g1", model.ArtworkPriorityScan))).To(Succeed())
backOff("ar", "g1", time.Now().Add(time.Hour))
got, err := repo.Get(model.KindArtistArtwork, "g1", model.ImageTypePrimary)
Expect(err).ToNot(HaveOccurred())
Expect(got.Priority).To(Equal(model.ArtworkPriorityScan))
Expect(got.Attempts).To(Equal(1))
Expect(got.RetryAt).To(BeTemporally(">", time.Now()))
})
It("Get reports ErrNotFound when the item is not queued", func() {
_, err := repo.Get(model.KindArtistArtwork, "nope", model.ImageTypePrimary)
Expect(err).To(MatchError(model.ErrNotFound))
})
It("keeps the higher priority on duplicate enqueue", func() {
Expect(repo.Enqueue(item("al", "a1", model.ArtworkPriorityBump))).To(Succeed())
Expect(repo.Enqueue(item("al", "a1", model.ArtworkPriorityBackfill))).To(Succeed())

View file

@ -25,6 +25,19 @@ func CreateMockArtworkQueueRepo() *MockArtworkQueueRepo {
return &MockArtworkQueueRepo{Data: map[string]model.ArtworkQueueItem{}}
}
func (m *MockArtworkQueueRepo) Get(kind model.Kind, id, imageType string) (*model.ArtworkQueueItem, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.Err != nil {
return nil, m.Err
}
it, ok := m.Data[iaKey(kind.Prefix(), id, imageType)]
if !ok {
return nil, model.ErrNotFound
}
return &it, nil
}
func (m *MockArtworkQueueRepo) Enqueue(items ...model.ArtworkQueueItem) error {
m.mu.Lock()
defer m.mu.Unlock()