From ee36ad41d0e15c39dc5d77030b4c1b30a6108fa7 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 10 Jun 2025 23:04:46 +0800 Subject: [PATCH] :sparkles: Activable badge --- DysonNetwork.Sphere/Account/Account.cs | 2 + .../Account/AccountController.cs | 1 - .../Account/AccountCurrentController.cs | 32 +++++++- DysonNetwork.Sphere/Account/AccountService.cs | 73 ++++++++++++++++++- DysonNetwork.Sphere/Account/Badge.cs | 28 +++++++ 5 files changed, 133 insertions(+), 3 deletions(-) diff --git a/DysonNetwork.Sphere/Account/Account.cs b/DysonNetwork.Sphere/Account/Account.cs index 7f010ce..0a0c3b1 100644 --- a/DysonNetwork.Sphere/Account/Account.cs +++ b/DysonNetwork.Sphere/Account/Account.cs @@ -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; diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Sphere/Account/AccountController.cs index c421220..a9b38b9 100644 --- a/DysonNetwork.Sphere/Account/AccountController.cs +++ b/DysonNetwork.Sphere/Account/AccountController.cs @@ -47,7 +47,6 @@ public class AccountController( return account is null ? NotFound() : account.Badges.ToList(); } - public class AccountCreateRequest { [Required] diff --git a/DysonNetwork.Sphere/Account/AccountCurrentController.cs b/DysonNetwork.Sphere/Account/AccountCurrentController.cs index e8a921e..6e8c1b5 100644 --- a/DysonNetwork.Sphere/Account/AccountCurrentController.cs +++ b/DysonNetwork.Sphere/Account/AccountCurrentController.cs @@ -644,7 +644,7 @@ public class AccountCurrentController( public async Task> 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>(StatusCodes.Status200OK)] + [Authorize] + public async Task>> 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> 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); + } + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountService.cs b/DysonNetwork.Sphere/Account/AccountService.cs index a842b9f..6c6e3b3 100644 --- a/DysonNetwork.Sphere/Account/AccountService.cs +++ b/DysonNetwork.Sphere/Account/AccountService.cs @@ -438,7 +438,78 @@ public class AccountService( await db.SaveChangesAsync(); } - /// Maintenance methods for server administrator + /// + /// This method will grant a badge to the account. + /// Shouldn't be exposed to normal user and the user itself. + /// + public async Task GrantBadge(Account account, Badge badge) + { + badge.AccountId = account.Id; + db.Badges.Add(badge); + await db.SaveChangesAsync(); + return badge; + } + + /// + /// This method will revoke a badge from the account. + /// Shouldn't be exposed to normal user and the user itself. + /// + 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; + } + } + + /// + /// The maintenance method for server administrator. + /// To check every user has an account profile and to create them if it isn't having one. + /// public async Task EnsureAccountProfileCreated() { var accountsId = await db.Accounts.Select(a => a.Id).ToListAsync(); diff --git a/DysonNetwork.Sphere/Account/Badge.cs b/DysonNetwork.Sphere/Account/Badge.cs index 41b1d94..11368c4 100644 --- a/DysonNetwork.Sphere/Account/Badge.cs +++ b/DysonNetwork.Sphere/Account/Badge.cs @@ -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 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? Meta { get; set; } + public Instant? ActivatedAt { get; set; } + public Instant? ExpiredAt { get; set; } + public Guid AccountId { get; set; } } \ No newline at end of file