From e4031478b5b8261ee2a2a951c20a6b517ece738f Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 2 May 2025 01:06:59 +0800 Subject: [PATCH] :sparkles: Realms --- DysonNetwork.Sphere/Realm/Realm.cs | 7 + DysonNetwork.Sphere/Realm/RealmController.cs | 181 ++++++++++++++----- DysonNetwork.Sphere/Realm/RealmService.cs | 1 - 3 files changed, 139 insertions(+), 50 deletions(-) diff --git a/DysonNetwork.Sphere/Realm/Realm.cs b/DysonNetwork.Sphere/Realm/Realm.cs index 39d75bf..7e3995d 100644 --- a/DysonNetwork.Sphere/Realm/Realm.cs +++ b/DysonNetwork.Sphere/Realm/Realm.cs @@ -1,15 +1,22 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using DysonNetwork.Sphere.Storage; +using Microsoft.EntityFrameworkCore; using NodaTime; namespace DysonNetwork.Sphere.Realm; +[Index(nameof(Slug), IsUnique = true)] public class Realm : ModelBase { public long Id { get; set; } + [MaxLength(1024)] public string Slug { 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? VerifiedAs { get; set; } + public Instant? VerifiedAt { get; set; } + public bool IsCommunity { get; set; } + public bool IsPublic { get; set; } public CloudFile? Picture { get; set; } public CloudFile? Background { get; set; } diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index 239bda9..1d46028 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; @@ -9,26 +8,15 @@ namespace DysonNetwork.Sphere.Realm; [ApiController] [Route("/realms")] -public class RealmController : Controller +public class RealmController(AppDatabase db, RealmService rs, FileService fs) : Controller { - private readonly AppDatabase _db; - private readonly RealmService _realmService; - private readonly FileService _fileService; - - public RealmController(AppDatabase db, RealmService rs, FileService fs) - { - _db = db; - _realmService = rs; - _fileService = fs; - } - - [HttpGet("{name}")] - public async Task> GetRealm(string name) + [HttpGet("{slug}")] + public async Task> GetRealm(string slug) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - var realm = await _db.Realms - .Where(e => e.Name == name) + var realm = await db.Realms + .Where(e => e.Slug == slug) .Include(e => e.Picture) .Include(e => e.Background) .FirstOrDefaultAsync(); @@ -44,7 +32,7 @@ public class RealmController : Controller if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var userId = currentUser.Id; - var members = await _db.RealmMembers + var members = await db.RealmMembers .Where(m => m.AccountId == userId) .Where(m => m.JoinedAt != null) .Include(e => e.Realm) @@ -62,7 +50,7 @@ public class RealmController : Controller if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var userId = currentUser.Id; - var members = await _db.RealmMembers + var members = await db.RealmMembers .Where(m => m.AccountId == userId) .Where(m => m.JoinedAt == null) .Include(e => e.Realm) @@ -79,25 +67,25 @@ public class RealmController : Controller [Required] public RealmMemberRole Role { get; set; } } - [HttpPost("invites/{name}")] + [HttpPost("invites/{slug}")] [Authorize] - public async Task> InviteMember(string name, + public async Task> InviteMember(string slug, [FromBody] RealmMemberRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var userId = currentUser.Id; - var relatedUser = await _db.Accounts.FindAsync(request.RelatedUserId); + var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); if (relatedUser is null) return BadRequest("Related user was not found"); - var realm = await _db.Realms - .Where(p => p.Name == name) + var realm = await db.Realms + .Where(p => p.Slug == slug) .Include(publisher => publisher.Picture) .Include(publisher => publisher.Background) .FirstOrDefaultAsync(); if (realm is null) return NotFound(); - var member = await _db.RealmMembers + var member = await db.RealmMembers .Where(m => m.AccountId == userId) .Where(m => m.RealmId == realm.Id) .FirstOrDefaultAsync(); @@ -115,84 +103,179 @@ public class RealmController : Controller Role = request.Role, }; - _db.RealmMembers.Add(newMember); - await _db.SaveChangesAsync(); + db.RealmMembers.Add(newMember); + await db.SaveChangesAsync(); return Ok(newMember); } - [HttpPost("invites/{name}/accept")] + [HttpPost("invites/{slug}/accept")] [Authorize] - public async Task> AcceptMemberInvite(string name) + public async Task> AcceptMemberInvite(string slug) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var userId = currentUser.Id; - var member = await _db.RealmMembers + var member = await db.RealmMembers .Where(m => m.AccountId == userId) - .Where(m => m.Realm.Name == name) + .Where(m => m.Realm.Slug == slug) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); if (member is null) return NotFound(); member.JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow); - _db.Update(member); - await _db.SaveChangesAsync(); + db.Update(member); + await db.SaveChangesAsync(); return Ok(member); } - [HttpPost("invites/{name}/decline")] + [HttpPost("invites/{slug}/decline")] [Authorize] - public async Task DeclineMemberInvite(string name) + public async Task DeclineMemberInvite(string slug) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var userId = currentUser.Id; - var member = await _db.RealmMembers + var member = await db.RealmMembers .Where(m => m.AccountId == userId) - .Where(m => m.Realm.Name == name) + .Where(m => m.Realm.Slug == slug) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); if (member is null) return NotFound(); - _db.RealmMembers.Remove(member); - await _db.SaveChangesAsync(); + db.RealmMembers.Remove(member); + await db.SaveChangesAsync(); return NoContent(); } public class RealmRequest { + [MaxLength(1024)] public string? Slug { get; set; } [MaxLength(1024)] public string? Name { get; set; } [MaxLength(4096)] public string? Description { get; set; } public string? PictureId { get; set; } public string? BackgroundId { get; set; } + public bool? IsCommunity { get; set; } + public bool? IsPublic { get; set; } } [HttpPost] [Authorize] public async Task> CreateRealm(RealmRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (request.Name is null) return BadRequest("You cannot create a realm without a name."); + if (request.Slug is null) return BadRequest("You cannot create a realm without a slug."); - var realm = new Realm(); - realm.Name = request.Name!; - realm.Description = request.Description!; + var slugExists = await db.Realms.AnyAsync(r => r.Slug == request.Slug); + if (slugExists) return BadRequest("Realm with this slug already exists."); + + var realm = new Realm + { + Name = request.Name!, + Slug = request.Slug!, + Description = request.Description!, + AccountId = currentUser.Id, + IsCommunity = request.IsCommunity ?? false, + IsPublic = request.IsPublic ?? false + }; if (request.PictureId is not null) { - realm.Picture = await _db.Files.FindAsync(request.PictureId); + realm.Picture = await db.Files.FindAsync(request.PictureId); + if (realm.Picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); } if (request.BackgroundId is not null) { - realm.Background = await _db.Files.FindAsync(request.BackgroundId); + realm.Background = await db.Files.FindAsync(request.BackgroundId); + if (realm.Background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); } - - var result = await _realmService.CreateRealmAsync(realm, currentUser); - return Ok(result); + db.Realms.Add(realm); + await db.SaveChangesAsync(); + + if (realm.Picture is not null) await fs.MarkUsageAsync(realm.Picture, 1); + if (realm.Background is not null) await fs.MarkUsageAsync(realm.Background, 1); + + return Ok(realm); } -} + [HttpPut("{slug}")] + [Authorize] + public async Task> Update(string slug, [FromBody] RealmRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var realm = await db.Realms + .Where(r => r.Slug == slug) + .Include(r => r.Picture) + .Include(r => r.Background) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + var member = await db.RealmMembers + .Where(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id && m.JoinedAt != null) + .FirstOrDefaultAsync(); + if (member is null || member.Role < RealmMemberRole.Moderator) + return StatusCode(403, "You do not have permission to update this realm."); + + if (request.Slug is not null && request.Slug != realm.Slug) + { + var slugExists = await db.Realms.AnyAsync(r => r.Slug == request.Slug); + if (slugExists) return BadRequest("Realm with this slug already exists."); + realm.Slug = request.Slug; + } + + if (request.Name is not null) + realm.Name = request.Name; + if (request.Description is not null) + realm.Description = request.Description; + if (request.IsCommunity is not null) + realm.IsCommunity = request.IsCommunity.Value; + if (request.IsPublic is not null) + realm.IsPublic = request.IsPublic.Value; + + if (request.PictureId is not null) + { + var picture = await db.Files.FindAsync(request.PictureId); + if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + realm.Picture = picture; + } + + if (request.BackgroundId is not null) + { + var background = await db.Files.FindAsync(request.BackgroundId); + if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + realm.Background = background; + } + + db.Realms.Update(realm); + await db.SaveChangesAsync(); + return Ok(realm); + } + + [HttpDelete("{slug}")] + [Authorize] + public async Task Delete(string slug) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var realm = await db.Realms + .Where(r => r.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + var member = await db.RealmMembers + .Where(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id && m.JoinedAt != null) + .FirstOrDefaultAsync(); + if (member is null || member.Role < RealmMemberRole.Owner) + return StatusCode(403, "Only the owner can delete this realm."); + + db.Realms.Remove(realm); + await db.SaveChangesAsync(); + return NoContent(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Realm/RealmService.cs b/DysonNetwork.Sphere/Realm/RealmService.cs index 4079abf..3b204b8 100644 --- a/DysonNetwork.Sphere/Realm/RealmService.cs +++ b/DysonNetwork.Sphere/Realm/RealmService.cs @@ -2,5 +2,4 @@ namespace DysonNetwork.Sphere.Realm; public class RealmService(AppDatabase db) { - } \ No newline at end of file