feat(jobs/challenges): Phase 1 challenge processors (poll-based)#835
Open
raymondjacobson wants to merge 1 commit into
Open
feat(jobs/challenges): Phase 1 challenge processors (poll-based)#835raymondjacobson wants to merge 1 commit into
raymondjacobson wants to merge 1 commit into
Conversation
Adds the IndexChallengesJob + 11 challenge processors (in 7 files) that mirror apps' discovery-provider challenges system. Unlike apps' Redis event-bus design, processors here reconcile derived state from existing source tables on a tick — idempotent, restart-safe, no producer-side plumbing required (architecture decision; see processor.go package docs). Phase 1 challenges: p profile completion — 7 boolean steps from users/follows/saves/reposts u track upload — 3 public, non-stem tracks since starting_block fp first playlist — any non-deleted playlist v connect-verified — users.is_verified = true e listen streak — endless streak from plays (currently inactive) p1 play count 250 milestone — verified artists, 2025+ play sum p2 play count 1000 milestone — gated on p1 complete p3 play count 10000 milestone — gated on p2 complete tt trending track — top-10 of week, Fridays UTC, idempotent tut trending underground — same, UNDERGROUND_TRACKS type tp trending playlist — top-10 playlists, rank-tiered payout Each processor: * Implements challenges.Processor (ChallengeID + Reconcile). * Runs in its own pgx tx via the umbrella IndexChallengesJob. * Skips quietly when its catalog row is inactive or absent. The umbrella job runs every 30s and is wired into CoreIndexer.Start alongside the rest of the parity jobs in #834. Migration 0203 seeds the Phase 1 challenges catalog rows from challenges.json (mirrors apps' create_new_challenges with ON CONFLICT DO UPDATE so catalog stays in sync). Out of scope here (per the architecture discussion): * Trending notifications + tastemaker challenge — depend on the challenge-event mechanism we explicitly skipped. * Send-tip / audio-matching (Solana). * Mobile install / one-shot / referrals — need a signals endpoint (Phase 3). Tests: 12 DB-backed tests (one Friday-coupled trending test auto-skips on non-Fridays), all passing against test_jobs template DB.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #834 (api/parity-jobs).
Implements the discovery-provider "challenges" reward system in api/, replacing apps' Redis event-bus design with a poll-based reconciliation model (Option C from the earlier architecture discussion). Each Phase 1 challenge has its own `Processor` that reads source tables on a tick and writes derived state into `user_challenges` + per-challenge state tables.
Why poll-based
Phase 1 challenges (11 in 7 processor files)
Architecture
`api/jobs/challenges/processor.go`:
```go
type Processor interface {
ChallengeID() string
Reconcile(ctx context.Context, tx pgx.Tx) error // idempotent
}
```
`api/jobs/index_challenges.go` is the umbrella job:
Common helpers in `processor.go`:
Migration
`ddl/migrations/0203_seed_phase_1_challenges.sql` seeds the catalog rows from `challenges.json`. `ON CONFLICT DO UPDATE` matches apps' `create_new_challenges.py` behavior so the catalog stays aligned even if rows already exist.
Out of scope (called out)
Test plan
12 DB-backed tests against the `test_jobs` template DB:
All 12 pass; `go build ./...` clean; `go vet ./...` clean.
🤖 Generated with Claude Code