Activable badge

This commit is contained in:
LittleSheep 2025-06-10 23:04:46 +08:00
parent 2fb29284fb
commit ee36ad41d0
5 changed files with 133 additions and 3 deletions

View File

@ -65,6 +65,8 @@ public class Profile : ModelBase
public Instant? Birthday { get; set; } public Instant? Birthday { get; set; }
public Instant? LastSeenAt { get; set; } public Instant? LastSeenAt { get; set; }
[Column(TypeName = "jsonb")] public BadgeReferenceObject? ActiveBadge { get; set; } = null!;
public int Experience { get; set; } = 0; public int Experience { get; set; } = 0;
[NotMapped] public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1; [NotMapped] public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1;

View File

@ -47,7 +47,6 @@ public class AccountController(
return account is null ? NotFound() : account.Badges.ToList(); return account is null ? NotFound() : account.Badges.ToList();
} }
public class AccountCreateRequest public class AccountCreateRequest
{ {
[Required] [Required]

View File

@ -660,4 +660,34 @@ public class AccountCurrentController(
return BadRequest(ex.Message); 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);
}
}
} }

View File

@ -438,7 +438,78 @@ public class AccountService(
await db.SaveChangesAsync(); 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() public async Task EnsureAccountProfileCreated()
{ {
var accountsId = await db.Accounts.Select(a => a.Id).ToListAsync(); var accountsId = await db.Accounts.Select(a => a.Id).ToListAsync();

View File

@ -12,8 +12,36 @@ public class Badge : ModelBase
[MaxLength(1024)] public string? Label { get; set; } [MaxLength(1024)] public string? Label { get; set; }
[MaxLength(4096)] public string? Caption { get; set; } [MaxLength(4096)] public string? Caption { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new(); [Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
public Instant? ActivatedAt { get; set; }
public Instant? ExpiredAt { get; set; } public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; } public Guid AccountId { get; set; }
[JsonIgnore] public Account Account { get; set; } = null!; [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; }
} }