Skip to content

Wire VipGift dispatch for vote rewards via template clone#48

Merged
AdaInTheLab merged 1 commit into
mainfrom
feature/vote-rewards-vipgift
Apr 30, 2026
Merged

Wire VipGift dispatch for vote rewards via template clone#48
AdaInTheLab merged 1 commit into
mainfrom
feature/vote-rewards-vipgift

Conversation

@AdaInTheLab
Copy link
Copy Markdown
Collaborator

Summary

The VipGift reward type now actually grants. In #46 it was a stub throwing `NotImplementedException`; this PR wires up the template-clone path so admins can pick "VIP Gift" in the Vote Rewards settings and have voters get an actual loot bundle.

Design: sentinel player_id, no schema change

A "template" is just a `vip_gifts` row whose `player_id` is the sentinel string `template`. Admins build templates via the existing VIP Gifts admin tab — type `template` as the player_id, give it a name, attach items + commands. VoteRewards finds it by name and clones it per voter.

Why this over an `is_template` column:

  • No migration needed
  • Templates don't show up in any real player's pending-gift list (`GetByPlayerId` is exact-match)
  • Admin sees the template clearly in the existing UI, bound to `template`
  • Only the VoteRewards feature needs to know about the convention

Flow

  1. `GetTemplateByName(name)` — finds the template (lowest id wins on dup names)
  2. Read template's items + commands before insert so a partial failure can't leave a half-built gift in the voter's pending list
  3. Insert a new `vip_gifts` row keyed to the voter's player_id
  4. Mirror the items + commands junctions via the existing `SetGiftItems` / `SetGiftCommands`
  5. Voter claims with `/vip` on next login

Files

File What
`Data/Repositories/VipGiftRepository.cs` `GetTemplateByName` + `VipGiftSentinels.TemplatePlayerId` constant
`Features/VoteRewardsFeature.cs` Real VipGift dispatch (was throwing) + `IVipGiftRepository` injection
`frontend/src/views/SettingsView.vue` Template-name field no longer `disabled`
`frontend/src/i18n/locales/*.ts` Dropdown label drops "(not yet implemented)" suffix; new hint string explains the `template` convention

What's left

CD Key dispatch still throws `NotImplementedException` — that's the next slot if we end up wanting it. The CD Key system already has key generation + redemption; the missing piece is generating a fresh single-use key from a template on grant.

Test plan

  • Open VIP Gifts admin tab, create a gift with `player_id = template`, give it a name like "vote-bundle", attach a couple of items + maybe a give-money command
  • In Settings → Vote Rewards, set 7daystodie-servers.com reward type to "VIP Gift" with template name "vote-bundle"
  • Vote on 7daystodie-servers.com from a logged-in Steam account
  • Within ~5 min, a new vip_gift row appears for the voter (visible in VIP Gifts admin tab filtered by their player_id)
  • Voter logs in, runs `/vip`, gets the bundled items + commands run
  • Audit table on Vote Rewards tab shows `reward_type=vip_gift` row
  • Voting again the same day is a no-op
  • Misconfigured template name (e.g. typo) → grant fails with a clear log line; vote_grants row still inserted (idempotency intact); next vote retries

🤖 Generated with Claude Code

The VipGift reward type now actually grants — earlier this was a stub
that threw NotImplementedException so admins selecting it got a clear
message rather than a silent no-op.

Approach: a "template" is just a vip_gift row whose player_id is the
sentinel "_template_". Admins build templates through the existing
VIP Gifts admin UI by typing that string as the player_id; the VoteRewards
feature looks them up by name and clones them per voter:

  1. GetTemplateByName(name) — finds the template (lowest id wins on dups)
  2. Read template's items + commands BEFORE insert so a partial failure
     doesn't leave a half-built gift in the voter's pending list
  3. Insert a new vip_gifts row keyed to the voter's player_id
  4. Mirror the items + commands junctions into the new row
  5. Voter claims with /vip on next login

Sentinel-prefix instead of an is_template column means no schema migration
and no junction-table scaffolding work — templates just don't show up in
any real player's pending-gift list because GetByPlayerId is exact-match.

Frontend changes:
- VIP gift template-name field is no longer disabled
- Dropdown label is plain "VIP Gift" (was "VIP Gift (not yet implemented)")
- New hint string explains the "_template_" sentinel convention so admins
  know exactly what to put in the player_id field when building the template

CD Key dispatch still throws NotImplementedException — that's the next slot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab AdaInTheLab merged commit 8dee0a1 into main Apr 30, 2026
2 checks passed
@AdaInTheLab AdaInTheLab deleted the feature/vote-rewards-vipgift branch April 30, 2026 15:15
AdaInTheLab added a commit that referenced this pull request Apr 30, 2026
Bug introduced (in spirit) by #46 and made very visible by #48: when
DispatchReward throws AFTER the audit/idempotency row was inserted, the
player is permanently locked out of ever claiming that vote. Future
sweeps see HasGrantForDate=true and short-circuit before retrying, even
though no reward was ever actually granted.

This always could have happened (e.g. SQLite glitch on AdjustPoints),
but #48's VipGift dispatch made it likely — a misspelled template name
in the Vote Rewards settings would silently lock out every voter on
that provider.

Fix: narrow try/catch around the DispatchReward call. On exception,
delete the audit row via the new IVoteGrantRepository.DeleteByKey so
the next sweep can retry once the admin fixes the misconfiguration.
The outer try/catch (network errors during GetClaimStatus / MarkClaimed)
is unaffected — those failure modes don't leave an orphaned row, so
they don't need rollback.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab AdaInTheLab mentioned this pull request Apr 30, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant