Compare commits
3 Commits
2fb29284fb
...
3db32caf7e
Author | SHA1 | Date | |
---|---|---|---|
|
3db32caf7e | ||
|
ee14c942f2 | ||
|
ee36ad41d0 |
@@ -65,6 +65,9 @@ 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 VerificationMark? Verification { get; set; }
|
||||||
|
[Column(TypeName = "jsonb")] public BadgeReferenceObject? ActiveBadge { get; set; }
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
@@ -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]
|
||||||
|
@@ -644,7 +644,7 @@ public class AccountCurrentController(
|
|||||||
public async Task<ActionResult<AccountContact>> DeleteContact(Guid id)
|
public async Task<ActionResult<AccountContact>> DeleteContact(Guid id)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
var contact = await db.AccountContacts
|
var contact = await db.AccountContacts
|
||||||
.Where(c => c.AccountId == currentUser.Id && c.Id == id)
|
.Where(c => c.AccountId == currentUser.Id && c.Id == id)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -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();
|
||||||
|
@@ -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; }
|
||||||
}
|
}
|
25
DysonNetwork.Sphere/Account/VerificationMark.cs
Normal file
25
DysonNetwork.Sphere/Account/VerificationMark.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Account;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The verification info of a resource
|
||||||
|
/// stands, for it is really an individual or organization or a company in the real world.
|
||||||
|
/// Besides, it can also be use for mark parody or fake.
|
||||||
|
/// </summary>
|
||||||
|
public class VerificationMark
|
||||||
|
{
|
||||||
|
public VerificationMarkType Type { get; set; }
|
||||||
|
[MaxLength(1024)] public string? Title { get; set; }
|
||||||
|
[MaxLength(8192)] public string? Description { get; set; }
|
||||||
|
[MaxLength(1024)] public string? VerifiedBy { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VerificationMarkType
|
||||||
|
{
|
||||||
|
Official,
|
||||||
|
Individual,
|
||||||
|
Organization,
|
||||||
|
Government,
|
||||||
|
Creator
|
||||||
|
}
|
3368
DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.Designer.cs
generated
Normal file
3368
DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
|||||||
|
using DysonNetwork.Sphere.Account;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ActiveBadgeAndVerification : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "verified_as",
|
||||||
|
table: "realms");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "verified_at",
|
||||||
|
table: "realms");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<VerificationMark>(
|
||||||
|
name: "verification",
|
||||||
|
table: "realms",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<VerificationMark>(
|
||||||
|
name: "verification",
|
||||||
|
table: "publishers",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Instant>(
|
||||||
|
name: "activated_at",
|
||||||
|
table: "badges",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<BadgeReferenceObject>(
|
||||||
|
name: "active_badge",
|
||||||
|
table: "account_profiles",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<VerificationMark>(
|
||||||
|
name: "verification",
|
||||||
|
table: "account_profiles",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "verification",
|
||||||
|
table: "realms");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "verification",
|
||||||
|
table: "publishers");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "activated_at",
|
||||||
|
table: "badges");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "active_badge",
|
||||||
|
table: "account_profiles");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "verification",
|
||||||
|
table: "account_profiles");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "verified_as",
|
||||||
|
table: "realms",
|
||||||
|
type: "character varying(4096)",
|
||||||
|
maxLength: 4096,
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Instant>(
|
||||||
|
name: "verified_at",
|
||||||
|
table: "realms",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -268,6 +268,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("account_id");
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ActivatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("activated_at");
|
||||||
|
|
||||||
b.Property<string>("Caption")
|
b.Property<string>("Caption")
|
||||||
.HasMaxLength(4096)
|
.HasMaxLength(4096)
|
||||||
.HasColumnType("character varying(4096)")
|
.HasColumnType("character varying(4096)")
|
||||||
@@ -554,6 +558,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("account_id");
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<BadgeReferenceObject>("ActiveBadge")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("active_badge");
|
||||||
|
|
||||||
b.Property<CloudFileReferenceObject>("Background")
|
b.Property<CloudFileReferenceObject>("Background")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("background");
|
.HasColumnName("background");
|
||||||
@@ -626,6 +634,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<VerificationMark>("Verification")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("verification");
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_account_profiles");
|
.HasName("pk_account_profiles");
|
||||||
|
|
||||||
@@ -1810,6 +1822,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<VerificationMark>("Verification")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("verification");
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_publishers");
|
.HasName("pk_publishers");
|
||||||
|
|
||||||
@@ -2021,14 +2037,9 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
b.Property<string>("VerifiedAs")
|
b.Property<VerificationMark>("Verification")
|
||||||
.HasMaxLength(4096)
|
.HasColumnType("jsonb")
|
||||||
.HasColumnType("character varying(4096)")
|
.HasColumnName("verification");
|
||||||
.HasColumnName("verified_as");
|
|
||||||
|
|
||||||
b.Property<Instant?>("VerifiedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("verified_at");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_realms");
|
.HasName("pk_realms");
|
||||||
|
@@ -29,6 +29,8 @@ public class Publisher : ModelBase, IIdentifiedResource
|
|||||||
|
|
||||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")] public Account.VerificationMark? Verification { get; set; }
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<Post.Post> Posts { get; set; } = new List<Post.Post>();
|
[JsonIgnore] public ICollection<Post.Post> Posts { get; set; } = new List<Post.Post>();
|
||||||
[JsonIgnore] public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
|
[JsonIgnore] public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
|
||||||
|
@@ -15,8 +15,6 @@ public class Realm : ModelBase, IIdentifiedResource
|
|||||||
[MaxLength(1024)] public string Slug { get; set; } = string.Empty;
|
[MaxLength(1024)] public string Slug { get; set; } = string.Empty;
|
||||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||||
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
|
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
|
||||||
[MaxLength(4096)] public string? VerifiedAs { get; set; }
|
|
||||||
public Instant? VerifiedAt { get; set; }
|
|
||||||
public bool IsCommunity { get; set; }
|
public bool IsCommunity { get; set; }
|
||||||
public bool IsPublic { get; set; }
|
public bool IsPublic { get; set; }
|
||||||
|
|
||||||
@@ -26,6 +24,8 @@ public class Realm : ModelBase, IIdentifiedResource
|
|||||||
|
|
||||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")] public Account.VerificationMark? Verification { get; set; }
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<RealmMember> Members { get; set; } = new List<RealmMember>();
|
[JsonIgnore] public ICollection<RealmMember> Members { get; set; } = new List<RealmMember>();
|
||||||
[JsonIgnore] public ICollection<ChatRoom> ChatRooms { get; set; } = new List<ChatRoom>();
|
[JsonIgnore] public ICollection<ChatRoom> ChatRooms { get; set; } = new List<ChatRoom>();
|
||||||
|
Reference in New Issue
Block a user