Skip to content

Conversation

@Oluwaseyi89
Copy link

Description
This PR implements a robust Badge awarding system, including the underlying database schema, business logic services, and API endpoints. It also resolves a critical type-mismatch issue where user identifiers were inconsistently handled between string and int.

Key Changes
Database Schema: * Added BadgeDefinitionModel to store badge metadata (names, descriptions, and criteria).

Added UserBadgeModel as a join table to track earned badges, aligned with UserProfileModel using int foreign keys.

Data Layer (Processors):

Implemented BadgeProcessor for efficient CRUD operations and relationship inclusion (_db.UserBadges.Add(userBadge);).

Service Layer:

Implemented BadgeAwardService to handle business logic for reputation-based and action-specific badge awarding.

API & ViewModels:

Created BadgeController with endpoints for fetching user badges and badge definitions.

Implemented BadgeViewModel and AutoMapper profiles to ensure the API returns flattened, serializable data, preventing circular reference errors in Swagger.

Data Seeding:

Added initial badge definitions (Early Adopter, Reputation milestones) via EF Core HasData seeding.

Technical Notes
Type Safety: Refactored all badge-related methods to use int userProfileId to maintain consistency with the existing UserProfileModel.Id.

Encoding: Confirmed UTF-8 support for emoji-based icons in badge definitions.

Swagger Fix: Replaced raw Entity models with ViewModels in controller responses to resolve schema generation crashes.

How to Test
Run dotnet ef database update to apply the new schema and seed data.

Navigate to /api/v1/users/{id}/badges.

Verify that the response returns an array of badges (or an empty array if none are awarded yet).

Or check all available badge definition Navigate to /api/v1/users/test-definitions

closes #91

@Oluwaseyi89
Copy link
Author

@SurfingBowser @0xGeorgii Please, review and give me your feedback.

@Oluwaseyi89
Copy link
Author

@0xGeorgii @SurfingBowser please review and give me your feedback.

@0xGeorgii 0xGeorgii requested a review from Copilot January 30, 2026 02:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a badge awarding system to gamify user engagement by tracking achievements through database models, business logic services, and API endpoints. It also addresses type inconsistencies in user identification and refactors dependency injection for better scope management.

Changes:

  • Database schema additions for BadgeDefinitionModel and UserBadgeModel with seeded badge data
  • Service layer implementation with BadgeAwardService for reputation-based and action-specific badge awarding
  • API endpoints via BadgeController for retrieving user badges and badge definitions
  • Refactored dependency injection to use IServiceScopeFactory for proper scoping in hosted services
  • Downgraded Swashbuckle packages and replaced deprecated security APIs

Reviewed changes

Copilot reviewed 20 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Startup.cs Refactored JWT configuration to use options pattern, added badge services registration, and improved dependency injection
SorobanSecurityPortalApi.csproj Downgraded Swashbuckle packages and added Microsoft.OpenApi reference
InstanceSyncHostedService.cs Refactored to use IServiceScopeFactory for proper dependency scope management
BadgeAwardService.cs New service implementing business logic for badge awarding based on reputation and actions
BackgroundWorkingHostedService.cs Refactored to use IServiceScopeFactory and improved error handling
ReportService.cs Added SupportedOSPlatform attributes to platform-specific methods
BadgeViewModel.cs New view model for badge data transfer
BadgeMappingProfile.cs AutoMapper profile for mapping UserBadgeModel to BadgeViewModel
UserBadgeModel.cs New database model for tracking user-badge associations
BadgeDefinitionModel.cs New database model for badge metadata
DbModelSnapshot.cs Updated EF Core snapshot with badge tables and moderation log
UpdateUserBadgeForeignKeys.cs Migration to fix foreign key from string user_id to int user_profile_id
AddBadgeSystem.cs Initial migration creating badge_definitions and user_badges tables
IBadgeProcessor.cs Interface for badge data access operations
BadgeProcessor.cs Implementation of badge CRUD operations
BadgeController.cs API controller exposing badge endpoints
Extensions.cs Replaced deprecated SHA256Managed with SHA256.Create()
ExtendedConfig.cs Refactored to use IServiceScopeFactory for database access
BadgeCategory.cs New enum defining badge categories
Db.cs Added BadgeDefinitions and UserBadges DbSets with seed data
Files not reviewed (2)
  • Backend/SorobanSecurityPortalApi/Migrations/20260128231425_AddBadgeSystem.Designer.cs: Language not supported
  • Backend/SorobanSecurityPortalApi/Migrations/20260129011841_UpdateUserBadgeForeignKeys.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 119 to 125
using (var sp = services.BuildServiceProvider())
{
var ec = sp.GetRequiredService<ExtendedConfig>();
var logger = sp.GetRequiredService<ILogger<Startup>>();
services.AddHttpClients(ec, logger);
}

Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building a service provider mid-configuration is an anti-pattern that can lead to incorrect dependency resolution and service lifetime issues. The built service provider here creates a separate container from the one that will be used at runtime, potentially causing configuration inconsistencies. Consider refactoring AddHttpClients to accept IServiceProvider as a parameter and call it during application startup, or defer this configuration until the Configure method where the final service provider is available.

Suggested change
using (var sp = services.BuildServiceProvider())
{
var ec = sp.GetRequiredService<ExtendedConfig>();
var logger = sp.GetRequiredService<ILogger<Startup>>();
services.AddHttpClients(ec, logger);
}

Copilot uses AI. Check for mistakes.
Comment on lines 62 to 63
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Swashbuckle packages are being downgraded from version 7.1.0 to 6.6.2. While this may resolve immediate issues, downgrading packages can introduce security vulnerabilities or miss important bug fixes. Consider investigating and resolving the root cause that necessitated the downgrade, or document the specific issue that requires this older version.

Suggested change
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="7.1.0" />

Copilot uses AI. Check for mistakes.
return Ok(result);
}

[HttpGet("test-definitions")]
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint name 'test-definitions' suggests this is a temporary testing endpoint. If this is meant for production use, rename it to simply 'definitions' or 'badge-definitions'. If it's truly for testing purposes only, it should be removed before production deployment or guarded with appropriate authorization checks.

Suggested change
[HttpGet("test-definitions")]
[HttpGet("definitions")]

Copilot uses AI. Check for mistakes.
@SurfingBowser
Copy link
Collaborator

@Oluwaseyi89 can take a look at the review please

@Oluwaseyi89
Copy link
Author

@Oluwaseyi89 can take a look at the review please

Note changes will be effected

@Oluwaseyi89
Copy link
Author

@0xGeorgii @SurfingBowser please review my latest commit.

@Oluwaseyi89
Copy link
Author

@0xGeorgii @SurfingBowser I am awaiting your review.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 24 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • Backend/SorobanSecurityPortalApi/Migrations/20260128231425_AddBadgeSystem.Designer.cs: Language not supported
  • Backend/SorobanSecurityPortalApi/Migrations/20260129011841_UpdateUserBadgeForeignKeys.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Oluwaseyi89
Copy link
Author

Oluwaseyi89 commented Feb 5, 2026

@0xGeorgii any feedback on this PR?


// Seed Initial Badge Definitions for Issue #91
builder.Entity<BadgeDefinitionModel>().HasData(
new BadgeDefinitionModel { Id = Guid.Parse("00000000-0000-0000-0000-000000000001"), Name = "First Comment", Description = "Posted first comment", Icon = "🎉", Category = BadgeCategory.Participation, Criteria = "first_comment" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not using just int constants?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xGeorgii I have refactored the Id type to use int and also all files concerned.

@0xGeorgii
Copy link
Contributor

@Oluwaseyi89 please resolve merge conflicts

@Oluwaseyi89
Copy link
Author

@0xGeorgii please review.

@0xGeorgii
Copy link
Contributor

@Oluwaseyi89, thank you for your contribution. Since it is a big feature, can you please record a video with a demo of how it works?

@0xGeorgii
Copy link
Contributor

@Oluwaseyi89, also fix the build and add more tests, please

@Oluwaseyi89
Copy link
Author

Oluwaseyi89 commented Feb 6, 2026

@0xGeorgii I have refactored the test to use BadgeDefinitionModel with int type for id instead of Guid


[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.0.0+e341b939fe (64-bit .NET 9.0.12)
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.0.0+e341b939fe (64-bit .NET 9.0.12)
[xUnit.net 00:00:00.20]   Discovering: SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.20]   Discovering: SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.34]   Discovered:  SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.34]   Discovered:  SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.42]   Starting:    SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.42]   Starting:    SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:01.04]   Finished:    SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:01.04]   Finished:    SorobanSecurityPortalApi.Tests
  Passed SorobanSecurityPortalApi.Tests.Services.BadgeAwardServiceTests.CheckAndAwardReputationBadges_ShouldHandleInvalidCriteriaGracefully [326 ms]
  Passed SorobanSecurityPortalApi.Tests.Services.BadgeAwardServiceTests.CheckAndAward_BoundaryTests(reputation: 399, shouldAward: False) [12 ms]
  Passed SorobanSecurityPortalApi.Tests.Services.BadgeAwardServiceTests.CheckAndAward_BoundaryTests(reputation: 400, shouldAward: True) [12 ms]
  Passed SorobanSecurityPortalApi.Tests.Services.BadgeAwardServiceTests.CheckAndAward_ShouldAwardMultipleBadges_WhenUserClearsMultipleThresholds [23 ms]
  Passed SorobanSecurityPortalApi.Tests.Services.BadgeAwardServiceTests.AwardSpecificBadge_ShouldBeCaseInsensitive [7 ms]
  Passed SorobanSecurityPortalApi.Tests.Services.BadgeAwardServiceTests.CheckAndAwardReputationBadges_ShouldAward_WhenReputationThresholdMet [9 ms]
  Passed SorobanSecurityPortalApi.Tests.Services.BadgeAwardServiceTests.AwardSpecificBadge_ShouldInvokeProcessor_WhenCriteriaMatches [7 ms]

Test Run Successful.
Total tests: 7
     Passed: 7
 Total time: 2.5446 Seconds
  SorobanSecurityPortalApi.Tests test net9.0 succeeded (4.2s)

Test summary: total: 7, failed: 0, succeeded: 7, skipped: 0, duration: 4.1s

[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.0.0+e341b939fe (64-bit .NET 9.0.12)
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.0.0+e341b939fe (64-bit .NET 9.0.12)
[xUnit.net 00:00:00.14]   Discovering: SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.14]   Discovering: SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.25]   Discovered:  SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.25]   Discovered:  SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.31]   Starting:    SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.31]   Starting:    SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.61]   Finished:    SorobanSecurityPortalApi.Tests
[xUnit.net 00:00:00.61]   Finished:    SorobanSecurityPortalApi.Tests
  Passed SorobanSecurityPortalApi.Tests.Controllers.BadgeControllerTests.GetUserBadges_ReturnsOk_WithMappedData [219 ms]

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 1.6807 Seconds
  SorobanSecurityPortalApi.Tests test net9.0 succeeded (2.1s)

Test summary: total: 1, failed: 0, succeeded: 1, skipped: 0, duration: 2.1s
Build succeeded in 6.2s

image --- image --- image

… files to enforce changing badge definition id type from guid to int
@Oluwaseyi89
Copy link
Author

@0xGeorgii please review.

@Oluwaseyi89
Copy link
Author

@0xGeorgii please, review.

@0xGeorgii
Copy link
Contributor

@Oluwaseyi89 can you please record a demo video of this feature?

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.

Create Badge System Database Schema

3 participants