Repository navigation
Implement Achievement/Badge System with Admin Commands - #344
Conversation
Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com>
|
/deploy_dev |
|
🚀 Starting deployment of |
|
@copilot A few modifications:
|
Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com>
I've implemented all the requested modifications to the badge system: ✅ Users can now view the list of badges - Removed admin requirement from Key Changes:
The system is backward compatible and will automatically add the |
|
@copilot resolve the merge conflicts in this pull request |
# Conflicts: # DiscordBot/Services/DatabaseService.cs Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com>
Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com>
Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com>
Resolved. I merged |
| { | ||
| try | ||
| { | ||
| var c = new NpgsqlConnection(ConnectionString); |
| /// </summary> | ||
| public bool IsUserAdmin(SocketGuildUser user) | ||
| { | ||
| return user?.GuildPermissions.Administrator == true; |
| catch (Exception e) | ||
| { | ||
| await _logging.Log(LogBehaviour.ConsoleChannelAndFile, | ||
| $"Error creating badge '{title}': {e}", ExtendedLogSeverity.Error); | ||
| return null; | ||
| } |
| catch (Exception e) | ||
| { | ||
| await _logging.Log(LogBehaviour.ConsoleChannelAndFile, | ||
| $"Error updating badge ID {badgeId}: {e}", ExtendedLogSeverity.Error); | ||
| return null; | ||
| } |
| catch (Exception e) | ||
| { | ||
| await _logging.Log(LogBehaviour.ConsoleChannelAndFile, | ||
| $"Error retrieving badges: {e}", ExtendedLogSeverity.Error); | ||
| return new List<Badge>(); | ||
| } |
| catch (Exception e) | ||
| { | ||
| await _logging.Log(LogBehaviour.ConsoleChannelAndFile, | ||
| $"Error retrieving badge {badgeId}: {e}", ExtendedLogSeverity.Error); | ||
| return null; | ||
| } |
| catch (Exception e) | ||
| { | ||
| await _logging.Log(LogBehaviour.ConsoleChannelAndFile, | ||
| $"Error retrieving badge '{title}': {e}", ExtendedLogSeverity.Error); | ||
| return null; | ||
| } |
|
@copilot I want a way to add a "leaderboard" for some badges. For example I'll add one badge for each UDC Jam we did, and award it to the past winners of those jams. So "UDC Jam #1 Winner", "UDC Jam #2 Winner", etc... And it would be nice to have a leaderboard of badges. A global leaderboard is nice (who has the most badge overall), but also a leaderboard only for "UDC Jam" badges. So we need a way to group/tag badges, so we can do something like /badge leaderboard group:udcjam for eample. |
Proposed approach:
If you validate this direction, I’ll implement it with the minimal schema/service/command changes. |
…thod; add unit test for badge details retrieval Co-authored-by: Copilot <copilot@github.com>
|
✅ PR deployed to dev! Image: |
… and navigation components for badge listing Co-authored-by: Copilot <copilot@github.com>
| catch (Exception e) | ||
| { | ||
| await _logging.Log(LogBehaviour.ConsoleChannelAndFile, | ||
| $"Error removing badge '{badge.Title}' from user {user.GetPreferredAndUsername()}: {e}", | ||
| ExtendedLogSeverity.Error); | ||
| return false; | ||
| } |
|
✅ PR deployed to dev! Image: |
|
✅ PR deployed to dev! Image: |
Allow admin badge commands to resolve badges by ID or title with autocomplete hints. Make /admin badge edit fields optional and no-op safely when no changes are provided. Add badge deletion service support used by admin delete flow.
…prove user feedback Co-authored-by: Copilot <copilot@github.com>
| catch (Exception e) | ||
| { | ||
| await _logging.Log(LogBehaviour.ConsoleChannelAndFile, | ||
| $"Error deleting badge ID {badgeId}: {e}", ExtendedLogSeverity.Error); | ||
| return null; | ||
| } |
|
✅ PR deployed to dev! Image: |
There was a problem hiding this comment.
🟡 Changes recommended
Nullable-contract mismatches (non-nullable returns that return null) and documented UX/DB discrepancies need resolving to avoid build warnings/runtime confusion.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a new badge/achievement subsystem to UDC-Bot, including PostgreSQL-backed persistence and new Discord slash-command surfaces for users and admins.
Changes:
- Introduces badge persistence (entities + Insight.Database repository) and startup-time table initialization/migrations.
- Adds BadgeService plus new slash commands for viewing/listing/leaderboards (
/badge) and admin management (/admin badge ...). - Includes several small cleanup/typo fixes in existing modules/services.
File summaries
| File | Description |
|---|---|
| DiscordBot/Services/Profiles/BirthdayAnnouncementService.cs | Minor logging/initialization tweaks in birthday announcements. |
| DiscordBot/Services/DatabaseService.cs | Adds badge repo accessor and badge table initialization/creation on startup. |
| DiscordBot/Services/BadgeService.cs | New service layer for creating/updating/deleting/assigning/querying badges. |
| DiscordBot/Program.cs | Registers BadgeService in DI. |
| DiscordBot/Modules/Utils/ConvertModule.cs | Fixes command summary text and avoids redundant ToLower calls. |
| DiscordBot/Modules/Server/ServerSlashModule.cs | Corrects help title/module key usage for server commands. |
| DiscordBot/Modules/Profiles/RankModule.cs | Fixes typo in summary text. |
| DiscordBot/Modules/Profiles/ProfileModule.cs | Removes unused variable. |
| DiscordBot/Modules/Fun/FunModule.cs | Adds clarifying comment for legacy behavior. |
| DiscordBot/Modules/BadgeSlashModule.cs | New public /badge commands (list/view/leaderboard + paging components). |
| DiscordBot/Modules/AdminSlashModule.cs | Adds /admin badge admin commands + small adjustments in existing admin flows. |
| DiscordBot/Extensions/BadgeRepository.cs | New badge/user_badge models, props, and SQL repo interface for PostgreSQL. |
| DiscordBot.Tests/Extensions/BadgeRepositoryTests.cs | Adds unit test coverage for EnsureBadgeDetails hydration behavior. |
Review details
Suppressed comments (3)
DiscordBot/Services/BadgeService.cs:80
- UpdateBadge() returns null when the repo is unavailable, the badge is missing, a title conflicts, or on exceptions, but the return type is Task (non-nullable). Update the signature to Task<Badge?> to match the actual contract under nullable reference types.
public async Task<Badge> UpdateBadge(int badgeId, string title, string description, bool isPublic, string? groupKey = null, bool updateGroup = false)
{
try
{
var badgeQuery = _databaseService.BadgeQuery;
if (badgeQuery == null)
return null;
DiscordBot/Services/BadgeService.cs:192
- GetBadge() can return null (e.g., when BadgeQuery is null or an exception occurs), but it is declared as Task. With nullable enabled, the signature should be Task<Badge?> so callers are forced to handle the not-found/unavailable cases safely.
public async Task<Badge> GetBadge(int badgeId)
{
try
{
var badgeQuery = _databaseService.BadgeQuery;
return badgeQuery == null ? null : await badgeQuery.GetBadge(badgeId);
}
DiscordBot/Extensions/BadgeRepository.cs:55
- BadgeLeaderboardEntry.UserId is non-nullable but not initialized, which triggers CS8618 with nullable enabled. Give it a default value (or make it nullable if needed).
public string UserId { get; set; }
public long BadgeCount { get; set; }
- Files reviewed: 13/13 changed files
- Comments generated: 9
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| public string Title { get; set; } | ||
| public string Description { get; set; } |
| [Sql($"SELECT * FROM {BadgeProps.TableName} WHERE {BadgeProps.Id} = @badgeId")] | ||
| Task<Badge> GetBadge(int badgeId); | ||
|
|
||
| [Sql($"SELECT * FROM {BadgeProps.TableName} WHERE {BadgeProps.Title} = @title")] | ||
| Task<Badge> GetBadgeByTitle(string title); |
| /// <summary> | ||
| /// Creates a new badge with the specified title and description. | ||
| /// </summary> | ||
| public async Task<Badge> CreateBadge(string title, string description, bool isPublic = true, string? groupKey = null) |
| catch | ||
| { | ||
| await _logging.LogAction($"DatabaseService: Badge tables do not exist, attempting to generate tables.", | ||
| ExtendedLogSeverity.LowWarning); |
| [Group("admin", "Administrator-only commands")] | ||
| [DefaultMemberPermissions(GuildPermission.Administrator)] | ||
| public class AdminSlashModule : InteractionModuleBase | ||
| { | ||
| [Group("badge", "Badge administration commands")] | ||
| public class BadgeAdminCommands : InteractionModuleBase<SocketInteractionContext> | ||
| { |
| var success = await BadgeService.AssignBadgeToUser(user, badge, Context.User as SocketGuildUser); | ||
|
|
||
| if (success) | ||
| { | ||
| var embed = new EmbedBuilder() | ||
| .WithTitle("🏆 Badge Assigned Successfully") | ||
| .WithDescription($"Assigned **{badge.Title}** to **{user.DisplayName}**") | ||
| .AddField("Badge Description", badge.Description) | ||
| .AddField("Assigned By", Context.User.Mention) | ||
| .AddField("Assigned At", DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC")) | ||
| .WithColor(Color.Gold) | ||
| .WithTimestamp(DateTimeOffset.UtcNow) | ||
| .Build(); | ||
|
|
||
| await Context.Interaction.FollowupAsync(embed: embed, ephemeral: true); | ||
| } |
| } | ||
| else | ||
| { | ||
| var guildUser = await Context.Guild.GetUserAsync(userId); | ||
| var guildUser = Context.Guild.GetUser(userId); | ||
| displayName = guildUser?.DisplayName ?? $"Unknown User ({entry.UserID})"; | ||
| } | ||
|
|
| public BadgeService BadgeService { get; set; } | ||
| public ILoggingService LoggingService { get; set; } | ||
|
|
| private IBadgeRepo? CreateBadgeQuery() | ||
| { | ||
| try | ||
| { | ||
| var c = new NpgsqlConnection(ConnectionString); | ||
| return c.As<IBadgeRepo>(); | ||
| } |
This PR implements a comprehensive badge/achievement system for the UDC Bot, allowing administrators to create, assign, and manage user badges through Discord slash commands.
Features Implemented
Database Layer
Admin Slash Commands
All commands require Discord Administrator permissions and use the
/badgecommand group:/badge create [title] [description]- Create new badges with validation/badge assign [user] [badge]- Assign badges to users with duplicate prevention/badge remove [user] [badge]- Remove badges from users/badge list- Display all available badges/badge view [user]- Show a user's earned badgesUser Experience
Database Schema
The system creates two new tables:
Usage Examples
Technical Details
The system is production-ready and will be immediately available to administrators once deployed.
Fixes #341.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.