Repository navigation
feat(bot): birthday role auto-grant + /birthday role - #743
Conversation
Completes the birthday feature with the remaining Nekotina-style perk.
Schema:
- GuildSettings.birthdayRoleId (String?) — new migration. Null by default.
Command:
- /birthday role <role> — ManageGuild-gated, upserts GuildSettings.
Empty role disables the grant entirely.
Scheduler:
- On each tick, for every guild with matches today: fetch role + grant
to celebrators who don't already have it.
- For guilds with birthdayRoleId set but NO matches today, iterate the
role's current members and revoke. This means:
- 24-hour auto-expiry happens naturally (next UTC day tick finds no
match for the previous day's celebrators, revokes cleanly).
- No separate tracking table / timer state needed.
- Admin reconfiguration is idempotent (stale roleIds simply get logged
and skipped).
Scoped API calls — uses guild.roles.fetch(roleId).members iteration
rather than fetching every guild member. No pagination, no rate-limit
scaling risk at Lucky's current scale.
Tests: 9 total (3 new role cases on top of the 6 announcement cases):
- Grants the role to today's celebrators
- Revokes from stale holders when no matches today
- Skips re-grant when member already has the role
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
8b55ba2
into
feature/birthday-scheduler
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Promotes ~/.claude/plans/lucky-next-phases-ultraplan.md into a committed spec under docs/specs/2026-04-20-lucky-next-phases/ via the spec-new skill. Includes a rewritten tasks.md with Phase 0/1 marked shipped (PRs #728-#743), Phase 2 status (PRs #746/#747/#749/#750 shipped, real Stripe SDK + PR 4 dashboard remaining), and an audit inventory of premium gates across packages/*/src to guide PR 4 wiring. Key audit findings: - Only PremiumService + replenisher.ts reference isPremium today - Collaborative playlist service already exists — natural P2 gate - embedColor palette is the cheapest premium UX gate - Leaderboard row-limit is frontend-paginated, not backend-enforced
* docs(spec): promote lucky-next-phases ultraplan to docs/specs Promotes ~/.claude/plans/lucky-next-phases-ultraplan.md into a committed spec under docs/specs/2026-04-20-lucky-next-phases/ via the spec-new skill. Includes a rewritten tasks.md with Phase 0/1 marked shipped (PRs #728-#743), Phase 2 status (PRs #746/#747/#749/#750 shipped, real Stripe SDK + PR 4 dashboard remaining), and an audit inventory of premium gates across packages/*/src to guide PR 4 wiring. Key audit findings: - Only PremiumService + replenisher.ts reference isPremium today - Collaborative playlist service already exists — natural P2 gate - embedColor palette is the cheapest premium UX gate - Leaderboard row-limit is frontend-paginated, not backend-enforced * docs(spec): mark Phase 2 (Stripe/premium billing) as deferred Per 2026-04-20 decision: removing Stripe features for now. The existing premiumService.isPremium() check serves as the feature toggle (returns false without subscription rows, so all premium-adjacent paths behave as if off). Closed PRs: #745 (spec), #747 (env), #749 (routes) Kept: #746 (schema, harmless no-op on main), #750 (autoplay gate = the live toggle)
* feat(bot): birthday scheduler + /birthday channel Completes the birthdays feature on top of the schema + /birthday set|clear shipped in #738. Schema change: - GuildSettings.birthdayChannelId (String?) — null by default so the scheduler safely skips guilds that haven't opted in. New migration. Command: - /birthday channel <channel> — ManageGuild-gated, upserts GuildSettings. Empty channel disables announcements (same field set to null). Scheduler (packages/bot/src/utils/general/birthdayScheduler.ts): - Runs once at startup + every hour (BIRTHDAY_TICK_INTERVAL_MS override) - Queries MemberBirthday by today's UTC (month, day) via the existing @@index([guildId, month, day]) - Groups by guild, reads GuildSettings.birthdayChannelId, posts a celebration embed mentioning each user - Idempotent per UTC day (in-memory lastAnnouncedPerGuild map) so hourly reruns are no-ops; posts again when the date rolls over - Wired into clientHandler/service.ts alongside modDigestScheduler - Failures are logged and swallowed (won't crash the bot) Tests: 14 total across birthday.ts and birthdayScheduler.ts — covering empty results, multi-user guild, no-config skip, day-rollover idempotency, and db-error resilience. * feat(bot): birthday role auto-grant + /birthday role (#743) Completes the birthday feature with the remaining Nekotina-style perk. Schema: - GuildSettings.birthdayRoleId (String?) — new migration. Null by default. Command: - /birthday role <role> — ManageGuild-gated, upserts GuildSettings. Empty role disables the grant entirely. Scheduler: - On each tick, for every guild with matches today: fetch role + grant to celebrators who don't already have it. - For guilds with birthdayRoleId set but NO matches today, iterate the role's current members and revoke. This means: - 24-hour auto-expiry happens naturally (next UTC day tick finds no match for the previous day's celebrators, revokes cleanly). - No separate tracking table / timer state needed. - Admin reconfiguration is idempotent (stale roleIds simply get logged and skipped). Scoped API calls — uses guild.roles.fetch(roleId).members iteration rather than fetching every guild member. No pagination, no rate-limit scaling risk at Lucky's current scale. Tests: 9 total (3 new role cases on top of the 6 announcement cases): - Grants the role to today's celebrators - Revokes from stale holders when no matches today - Skips re-grant when member already has the role * test(bot): cover birthday scheduler + /birthday channel/role commands (unblocks #740 Sonar) * fix(bot): remove stray merge conflict marker in birthday.ts Leftover <<<<<<< HEAD from the earlier merge resolution — no corresponding ======= / >>>>>>> pair, so the merged content is correct, just had a stray opening marker on line 203 that broke TypeScript compile.
* docs(spec): promote lucky-next-phases ultraplan to docs/specs Promotes ~/.claude/plans/lucky-next-phases-ultraplan.md into a committed spec under docs/specs/2026-04-20-lucky-next-phases/ via the spec-new skill. Includes a rewritten tasks.md with Phase 0/1 marked shipped (PRs #728-#743), Phase 2 status (PRs #746/#747/#749/#750 shipped, real Stripe SDK + PR 4 dashboard remaining), and an audit inventory of premium gates across packages/*/src to guide PR 4 wiring. Key audit findings: - Only PremiumService + replenisher.ts reference isPremium today - Collaborative playlist service already exists — natural P2 gate - embedColor palette is the cheapest premium UX gate - Leaderboard row-limit is frontend-paginated, not backend-enforced * docs(spec): mark Phase 2 (Stripe/premium billing) as deferred Per 2026-04-20 decision: removing Stripe features for now. The existing premiumService.isPremium() check serves as the feature toggle (returns false without subscription rows, so all premium-adjacent paths behave as if off). Closed PRs: #745 (spec), #747 (env), #749 (routes) Kept: #746 (schema, harmless no-op on main), #750 (autoplay gate = the live toggle)
* feat(bot): birthday scheduler + /birthday channel Completes the birthdays feature on top of the schema + /birthday set|clear shipped in #738. Schema change: - GuildSettings.birthdayChannelId (String?) — null by default so the scheduler safely skips guilds that haven't opted in. New migration. Command: - /birthday channel <channel> — ManageGuild-gated, upserts GuildSettings. Empty channel disables announcements (same field set to null). Scheduler (packages/bot/src/utils/general/birthdayScheduler.ts): - Runs once at startup + every hour (BIRTHDAY_TICK_INTERVAL_MS override) - Queries MemberBirthday by today's UTC (month, day) via the existing @@index([guildId, month, day]) - Groups by guild, reads GuildSettings.birthdayChannelId, posts a celebration embed mentioning each user - Idempotent per UTC day (in-memory lastAnnouncedPerGuild map) so hourly reruns are no-ops; posts again when the date rolls over - Wired into clientHandler/service.ts alongside modDigestScheduler - Failures are logged and swallowed (won't crash the bot) Tests: 14 total across birthday.ts and birthdayScheduler.ts — covering empty results, multi-user guild, no-config skip, day-rollover idempotency, and db-error resilience. * feat(bot): birthday role auto-grant + /birthday role (#743) Completes the birthday feature with the remaining Nekotina-style perk. Schema: - GuildSettings.birthdayRoleId (String?) — new migration. Null by default. Command: - /birthday role <role> — ManageGuild-gated, upserts GuildSettings. Empty role disables the grant entirely. Scheduler: - On each tick, for every guild with matches today: fetch role + grant to celebrators who don't already have it. - For guilds with birthdayRoleId set but NO matches today, iterate the role's current members and revoke. This means: - 24-hour auto-expiry happens naturally (next UTC day tick finds no match for the previous day's celebrators, revokes cleanly). - No separate tracking table / timer state needed. - Admin reconfiguration is idempotent (stale roleIds simply get logged and skipped). Scoped API calls — uses guild.roles.fetch(roleId).members iteration rather than fetching every guild member. No pagination, no rate-limit scaling risk at Lucky's current scale. Tests: 9 total (3 new role cases on top of the 6 announcement cases): - Grants the role to today's celebrators - Revokes from stale holders when no matches today - Skips re-grant when member already has the role * test(bot): cover birthday scheduler + /birthday channel/role commands (unblocks #740 Sonar) * fix(bot): remove stray merge conflict marker in birthday.ts Leftover <<<<<<< HEAD from the earlier merge resolution — no corresponding ======= / >>>>>>> pair, so the merged content is correct, just had a stray opening marker on line 203 that broke TypeScript compile.
Summary
Completes the birthday feature with the remaining Nekotina-style perk. Stacked on #740.
Schema
Command
Scheduler (auto-grant/revoke)
Failure modes
Test plan
Stacking
Base: `feature/birthday-scheduler` (#740). Will auto-retarget to `main` when #740 lands.