✨ Activable badge
This commit is contained in:
		| @@ -65,6 +65,8 @@ public class Profile : ModelBase | ||||
|     public Instant? Birthday { get; set; } | ||||
|     public Instant? LastSeenAt { get; set; } | ||||
|  | ||||
|     [Column(TypeName = "jsonb")] public BadgeReferenceObject? ActiveBadge { get; set; } = null!; | ||||
|  | ||||
|     public int Experience { get; set; } = 0; | ||||
|     [NotMapped] public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1; | ||||
|  | ||||
|   | ||||
| @@ -47,7 +47,6 @@ public class AccountController( | ||||
|         return account is null ? NotFound() : account.Badges.ToList(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public class AccountCreateRequest | ||||
|     { | ||||
|         [Required] | ||||
|   | ||||
| @@ -644,7 +644,7 @@ public class AccountCurrentController( | ||||
|     public async Task<ActionResult<AccountContact>> DeleteContact(Guid id) | ||||
|     { | ||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); | ||||
|          | ||||
|  | ||||
|         var contact = await db.AccountContacts | ||||
|             .Where(c => c.AccountId == currentUser.Id && c.Id == id) | ||||
|             .FirstOrDefaultAsync(); | ||||
| @@ -660,4 +660,34 @@ public class AccountCurrentController( | ||||
|             return BadRequest(ex.Message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [HttpGet("badges")] | ||||
|     [ProducesResponseType<List<Badge>>(StatusCodes.Status200OK)] | ||||
|     [Authorize] | ||||
|     public async Task<ActionResult<List<Badge>>> GetBadges() | ||||
|     { | ||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); | ||||
|          | ||||
|         var badges = await db.Badges | ||||
|             .Where(b => b.AccountId == currentUser.Id) | ||||
|             .ToListAsync(); | ||||
|         return Ok(badges); | ||||
|     } | ||||
|  | ||||
|     [HttpPost("badges/{id:guid}/active")] | ||||
|     [Authorize] | ||||
|     public async Task<ActionResult<Badge>> ActivateBadge(Guid id) | ||||
|     { | ||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             await accounts.ActiveBadge(currentUser, id); | ||||
|             return Ok(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return BadRequest(ex.Message); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -438,7 +438,78 @@ public class AccountService( | ||||
|         await db.SaveChangesAsync(); | ||||
|     } | ||||
|  | ||||
|     /// Maintenance methods for server administrator | ||||
|     /// <summary> | ||||
|     /// This method will grant a badge to the account. | ||||
|     /// Shouldn't be exposed to normal user and the user itself. | ||||
|     /// </summary> | ||||
|     public async Task<Badge> GrantBadge(Account account, Badge badge) | ||||
|     { | ||||
|         badge.AccountId = account.Id; | ||||
|         db.Badges.Add(badge); | ||||
|         await db.SaveChangesAsync(); | ||||
|         return badge; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// This method will revoke a badge from the account. | ||||
|     /// Shouldn't be exposed to normal user and the user itself. | ||||
|     /// </summary> | ||||
|     public async Task RevokeBadge(Account account, Guid badgeId) | ||||
|     { | ||||
|         var badge = await db.Badges | ||||
|             .Where(b => b.AccountId == account.Id && b.Id == badgeId) | ||||
|             .OrderByDescending(b => b.CreatedAt) | ||||
|             .FirstOrDefaultAsync(); | ||||
|         if (badge is null) throw new InvalidOperationException("Badge was not found."); | ||||
|  | ||||
|         var profile = await db.AccountProfiles | ||||
|             .Where(p => p.AccountId == account.Id) | ||||
|             .FirstOrDefaultAsync(); | ||||
|         if (profile?.ActiveBadge is not null && profile.ActiveBadge.Id == badge.Id) | ||||
|             profile.ActiveBadge = null; | ||||
|  | ||||
|         db.Remove(badge); | ||||
|         await db.SaveChangesAsync(); | ||||
|     } | ||||
|  | ||||
|     public async Task ActiveBadge(Account account, Guid badgeId) | ||||
|     { | ||||
|         await using var transaction = await db.Database.BeginTransactionAsync(); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             var badge = await db.Badges | ||||
|                 .Where(b => b.AccountId == account.Id && b.Id == badgeId) | ||||
|                 .OrderByDescending(b => b.CreatedAt) | ||||
|                 .FirstOrDefaultAsync(); | ||||
|             if (badge is null) throw new InvalidOperationException("Badge was not found."); | ||||
|  | ||||
|             await db.Badges | ||||
|                 .Where(b => b.AccountId == account.Id && b.Id != badgeId) | ||||
|                 .ExecuteUpdateAsync(s => s.SetProperty(p => p.ActivatedAt, p => null)); | ||||
|  | ||||
|             badge.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); | ||||
|             db.Update(badge); | ||||
|             await db.SaveChangesAsync(); | ||||
|  | ||||
|             await db.AccountProfiles | ||||
|                 .Where(p => p.AccountId == account.Id) | ||||
|                 .ExecuteUpdateAsync(s => s.SetProperty(p => p.ActiveBadge, badge.ToReference())); | ||||
|             await PurgeAccountCache(account); | ||||
|  | ||||
|             await transaction.CommitAsync(); | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|             await transaction.RollbackAsync(); | ||||
|             throw; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// The maintenance method for server administrator. | ||||
|     /// To check every user has an account profile and to create them if it isn't having one. | ||||
|     /// </summary> | ||||
|     public async Task EnsureAccountProfileCreated() | ||||
|     { | ||||
|         var accountsId = await db.Accounts.Select(a => a.Id).ToListAsync(); | ||||
|   | ||||
| @@ -12,8 +12,36 @@ public class Badge : ModelBase | ||||
|     [MaxLength(1024)] public string? Label { get; set; } | ||||
|     [MaxLength(4096)] public string? Caption { get; set; } | ||||
|     [Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new(); | ||||
|     public Instant? ActivatedAt { get; set; } | ||||
|     public Instant? ExpiredAt { get; set; } | ||||
|  | ||||
|     public Guid AccountId { get; set; } | ||||
|     [JsonIgnore] public Account Account { get; set; } = null!; | ||||
|  | ||||
|     public BadgeReferenceObject ToReference() | ||||
|     { | ||||
|         return new BadgeReferenceObject | ||||
|         { | ||||
|             Id = Id, | ||||
|             Type = Type, | ||||
|             Label = Label, | ||||
|             Caption = Caption, | ||||
|             Meta = Meta, | ||||
|             ActivatedAt = ActivatedAt, | ||||
|             ExpiredAt = ExpiredAt, | ||||
|             AccountId = AccountId | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| public class BadgeReferenceObject : ModelBase | ||||
| { | ||||
|     public Guid Id { get; set; } | ||||
|     public string Type { get; set; } = null!; | ||||
|     public string? Label { get; set; } | ||||
|     public string? Caption { get; set; } | ||||
|     public Dictionary<string, object>? Meta { get; set; } | ||||
|     public Instant? ActivatedAt { get; set; } | ||||
|     public Instant? ExpiredAt { get; set; } | ||||
|     public Guid AccountId { get; set; } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user