From a93b633e8418d438e5df80ffc9f8de69a0eec9e6 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 2 Oct 2025 17:09:11 +0800 Subject: [PATCH] :bug: Fixes member issue --- .../Wallet/SubscriptionGiftController.cs | 313 ++++++++++++++++++ DysonNetwork.Sphere/Chat/ChatController.cs | 9 +- .../Chat/ChatRoomController.cs | 39 ++- DysonNetwork.Sphere/Chat/ChatRoomService.cs | 9 +- DysonNetwork.Sphere/Chat/ChatService.cs | 4 +- .../Chat/RealtimeCallController.cs | 10 +- DysonNetwork.Sphere/Realm/RealmController.cs | 17 +- DysonNetwork.Sphere/Realm/RealmService.cs | 6 +- .../Startup/BroadcastEventHandler.cs | 2 +- 9 files changed, 371 insertions(+), 38 deletions(-) create mode 100644 DysonNetwork.Pass/Wallet/SubscriptionGiftController.cs diff --git a/DysonNetwork.Pass/Wallet/SubscriptionGiftController.cs b/DysonNetwork.Pass/Wallet/SubscriptionGiftController.cs new file mode 100644 index 0000000..754c7de --- /dev/null +++ b/DysonNetwork.Pass/Wallet/SubscriptionGiftController.cs @@ -0,0 +1,313 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Models; + +namespace DysonNetwork.Pass.Wallet; + +[ApiController] +[Route("/api/subscriptions/gifts")] +public class GiftController(SubscriptionService subscriptions, AppDatabase db) : ControllerBase +{ + /// + /// Lists gifts purchased by the current user. + /// + [HttpGet("sent")] + [Authorize] + public async Task>> ListSentGifts( + [FromQuery] int offset = 0, + [FromQuery] int take = 20 + ) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + var query = await subscriptions.GetGiftsByGifterAsync(currentUser.Id); + var totalCount = query.Count; + + var gifts = query + .Skip(offset) + .Take(take) + .ToList(); + + Response.Headers["X-Total"] = totalCount.ToString(); + + return gifts; + } + + /// + /// Lists gifts received by the current user (both direct and redeemed open gifts). + /// + [HttpGet("received")] + [Authorize] + public async Task>> ListReceivedGifts( + [FromQuery] int offset = 0, + [FromQuery] int take = 20 + ) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + var gifts = await subscriptions.GetGiftsByRecipientAsync(currentUser.Id); + var totalCount = gifts.Count; + + gifts = gifts + .Skip(offset) + .Take(take) + .ToList(); + + Response.Headers["X-Total"] = totalCount.ToString(); + + return gifts; + } + + /// + /// Gets a specific gift by ID (only if user is the gifter or recipient). + /// + [HttpGet("{giftId}")] + [Authorize] + public async Task> GetGift(Guid giftId) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + var gift = await db.WalletGifts + .Include(g => g.Gifter) + .Include(g => g.Recipient) + .Include(g => g.Redeemer) + .Include(g => g.Subscription) + .Include(g => g.Coupon) + .FirstOrDefaultAsync(g => g.Id == giftId); + + if (gift is null) return NotFound(); + if (gift.GifterId != currentUser.Id && gift.RecipientId != currentUser.Id && + !(gift.IsOpenGift && gift.RedeemerId == currentUser.Id)) + return NotFound(); + + return gift; + } + + /// + /// Checks if a gift code is valid and redeemable. + /// + [HttpGet("check/{giftCode}")] + [Authorize] + public async Task> CheckGiftCode(string giftCode) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + var gift = await subscriptions.GetGiftByCodeAsync(giftCode); + if (gift is null) return NotFound("Gift code not found."); + + var canRedeem = false; + var error = ""; + + if (gift.Status != DysonNetwork.Shared.Models.GiftStatus.Sent) + { + error = gift.Status switch + { + DysonNetwork.Shared.Models.GiftStatus.Created => "Gift has not been sent yet.", + DysonNetwork.Shared.Models.GiftStatus.Redeemed => "Gift has already been redeemed.", + DysonNetwork.Shared.Models.GiftStatus.Expired => "Gift has expired.", + DysonNetwork.Shared.Models.GiftStatus.Cancelled => "Gift has been cancelled.", + _ => "Gift is not redeemable." + }; + } + else if (gift.ExpiresAt < SystemClock.Instance.GetCurrentInstant()) + { + error = "Gift has expired."; + } + else if (!gift.IsOpenGift && gift.RecipientId != currentUser.Id) + { + error = "This gift is intended for someone else."; + } + else + { + // Check if user already has this subscription type + var subscriptionInfo = SubscriptionTypeData + .SubscriptionDict.TryGetValue(gift.SubscriptionIdentifier, out var template) + ? template + : null; + + if (subscriptionInfo != null) + { + var subscriptionsInGroup = subscriptionInfo.GroupIdentifier is not null + ? SubscriptionTypeData.SubscriptionDict + .Where(s => s.Value.GroupIdentifier == subscriptionInfo.GroupIdentifier) + .Select(s => s.Value.Identifier) + .ToArray() + : [gift.SubscriptionIdentifier]; + + var existingSubscription = await subscriptions.GetSubscriptionAsync(currentUser.Id, subscriptionsInGroup); + if (existingSubscription is not null) + { + error = "You already have an active subscription of this type."; + } + else if (subscriptionInfo.RequiredLevel > 0) + { + var profile = await db.AccountProfiles.FirstOrDefaultAsync(p => p.AccountId == currentUser.Id); + if (profile is null || profile.Level < subscriptionInfo.RequiredLevel) + { + error = $"Account level must be at least {subscriptionInfo.RequiredLevel} to redeem this gift."; + } + else + { + canRedeem = true; + } + } + else + { + canRedeem = true; + } + } + } + + return new GiftCheckResponse + { + GiftCode = giftCode, + SubscriptionIdentifier = gift.SubscriptionIdentifier, + CanRedeem = canRedeem, + Error = error, + Message = gift.Message + }; + } + + public class GiftCheckResponse + { + public string GiftCode { get; set; } = null!; + public string SubscriptionIdentifier { get; set; } = null!; + public bool CanRedeem { get; set; } + public string Error { get; set; } = null!; + public string? Message { get; set; } + } + + public class PurchaseGiftRequest + { + [Required] public string SubscriptionIdentifier { get; set; } = null!; + public Guid? RecipientId { get; set; } + [Required] public string PaymentMethod { get; set; } = null!; + [Required] public SnPaymentDetails PaymentDetails { get; set; } = null!; + public string? Message { get; set; } + public string? Coupon { get; set; } + public int? GiftDurationDays { get; set; } = 30; // Gift expires in 30 days by default + public int? SubscriptionDurationDays { get; set; } = 30; // Subscription lasts 30 days when redeemed + } + + /// + /// Purchases a gift subscription. + /// + [HttpPost("purchase")] + [Authorize] + public async Task> PurchaseGift([FromBody] PurchaseGiftRequest request) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + Duration? giftDuration = null; + if (request.GiftDurationDays.HasValue) + giftDuration = Duration.FromDays(request.GiftDurationDays.Value); + + Duration? subscriptionDuration = null; + if (request.SubscriptionDurationDays.HasValue) + subscriptionDuration = Duration.FromDays(request.SubscriptionDurationDays.Value); + + try + { + var gift = await subscriptions.PurchaseGiftAsync( + currentUser, + request.RecipientId, + request.SubscriptionIdentifier, + request.PaymentMethod, + request.PaymentDetails, + request.Message, + request.Coupon, + giftDuration, + subscriptionDuration + ); + + return gift; + } + catch (ArgumentOutOfRangeException ex) + { + return BadRequest(ex.Message); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + public class RedeemGiftRequest + { + [Required] public string GiftCode { get; set; } = null!; + } + + /// + /// Redeems a gift using its code, creating a subscription for the current user. + /// + [HttpPost("redeem")] + [Authorize] + public async Task> RedeemGift([FromBody] RedeemGiftRequest request) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + try + { + var (gift, subscription) = await subscriptions.RedeemGiftAsync(currentUser, request.GiftCode); + + return new RedeemGiftResponse + { + Gift = gift, + Subscription = subscription + }; + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + public class RedeemGiftResponse + { + public SnWalletGift Gift { get; set; } = null!; + public SnWalletSubscription Subscription { get; set; } = null!; + } + + /// + /// Marks a gift as sent (ready for redemption). + /// + [HttpPost("{giftId}/send")] + [Authorize] + public async Task> SendGift(Guid giftId) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + try + { + var gift = await subscriptions.MarkGiftAsSentAsync(giftId, currentUser.Id); + return gift; + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + /// + /// Cancels a gift before it's redeemed. + /// + [HttpPost("{giftId}/cancel")] + [Authorize] + public async Task> CancelGift(Guid giftId) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + + try + { + var gift = await subscriptions.CancelGiftAsync(giftId, currentUser.Id); + return gift; + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } +} diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs index 67db829..a6f30df 100644 --- a/DysonNetwork.Sphere/Chat/ChatController.cs +++ b/DysonNetwork.Sphere/Chat/ChatController.cs @@ -85,7 +85,7 @@ public partial class ChatController( var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) return StatusCode(403, "You are not a member of this chat room."); @@ -127,7 +127,7 @@ public partial class ChatController( var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) return StatusCode(403, "You are not a member of this chat room."); @@ -221,7 +221,8 @@ public partial class ChatController( .Select(a => Guid.Parse(a.Id)) .ToList(); var mentionedMembers = await db.ChatMembers - .Where(m => mentionedId.Contains(m.AccountId)) + .Where(m => m.ChatRoomId == roomId && mentionedId.Contains(m.AccountId)) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .Select(m => m.Id) .ToListAsync(); message.MembersMentioned = mentionedMembers; @@ -321,7 +322,7 @@ public partial class ChatController( var accountId = Guid.Parse(currentUser.Id); var isMember = await db.ChatMembers - .AnyAsync(m => m.AccountId == accountId && m.ChatRoomId == roomId); + .AnyAsync(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null); if (!isMember) return StatusCode(403, "You are not a member of this chat room."); diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs b/DysonNetwork.Sphere/Chat/ChatRoomController.cs index 54c5ec0..51e3b4a 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs @@ -56,8 +56,7 @@ public class ChatRoomController( var chatRooms = await db.ChatMembers .Where(m => m.AccountId == accountId) - .Where(m => m.JoinedAt != null) - .Where(m => m.LeaveAt == null) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .Include(m => m.ChatRoom) .Select(m => m.ChatRoom) .ToListAsync(); @@ -166,7 +165,7 @@ public class ChatRoomController( public class ChatRoomRequest { - [Required] [MaxLength(1024)] public string? Name { get; set; } + [Required][MaxLength(1024)] public string? Name { get; set; } [MaxLength(4096)] public string? Description { get; set; } [MaxLength(32)] public string? PictureId { get; set; } [MaxLength(32)] public string? BackgroundId { get; set; } @@ -475,6 +474,7 @@ public class ChatRoomController( var member = await db.ChatMembers .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member == null) @@ -496,13 +496,14 @@ public class ChatRoomController( { if (currentUser is null) return Unauthorized(); var member = await db.ChatMembers - .FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id)); + .Where(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id) && m.JoinedAt != null && m.LeaveAt == null) + .FirstOrDefaultAsync(); if (member is null) return StatusCode(403, "You need to be a member to see online count of private chat room."); } var members = await db.ChatMembers .Where(m => m.ChatRoomId == roomId) - .Where(m => m.LeaveAt == null) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .Select(m => m.AccountId) .ToListAsync(); @@ -530,13 +531,14 @@ public class ChatRoomController( { if (currentUser is null) return Unauthorized(); var member = await db.ChatMembers - .FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id)); + .Where(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id) && m.JoinedAt != null && m.LeaveAt == null) + .FirstOrDefaultAsync(); if (member is null) return StatusCode(403, "You need to be a member to see members of private chat room."); } var query = db.ChatMembers .Where(m => m.ChatRoomId == roomId) - .Where(m => m.LeaveAt == null); + .Where(m => m.JoinedAt != null && m.LeaveAt == null); if (withStatus) { @@ -633,6 +635,7 @@ public class ChatRoomController( var chatMember = await db.ChatMembers .Where(m => m.AccountId == accountId) .Where(m => m.ChatRoomId == roomId) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (chatMember is null) return StatusCode(403, "You are not even a member of the targeted chat room."); if (chatMember.Role < ChatMemberRole.Moderator) @@ -645,7 +648,7 @@ public class ChatRoomController( var hasExistingMember = await db.ChatMembers .Where(m => m.AccountId == request.RelatedUserId) .Where(m => m.ChatRoomId == roomId) - .Where(m => m.LeaveAt == null) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .AnyAsync(); if (hasExistingMember) return BadRequest("This user has been joined the chat cannot be invited again."); @@ -775,7 +778,7 @@ public class ChatRoomController( var accountId = Guid.Parse(currentUser.Id); var targetMember = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (targetMember is null) return BadRequest("You have not joined this chat room."); if (request.NotifyLevel is not null) @@ -816,7 +819,7 @@ public class ChatRoomController( else { var targetMember = await db.ChatMembers - .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (targetMember is null) return NotFound(); @@ -884,7 +887,7 @@ public class ChatRoomController( // Find the target member var member = await db.ChatMembers - .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member is null) return NotFound(); @@ -929,7 +932,17 @@ public class ChatRoomController( var existingMember = await db.ChatMembers .FirstOrDefaultAsync(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId); if (existingMember != null) + { + if (existingMember.LeaveAt == null) + { + existingMember.LeaveAt = null; + db.Update(existingMember); + await db.SaveChangesAsync(); + _ = crs.PurgeRoomMembersCache(roomId); + return Ok(existingMember); + } return BadRequest("You are already a member of this chat room."); + } var newMember = new SnChatMember { @@ -962,6 +975,7 @@ public class ChatRoomController( if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var member = await db.ChatMembers + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) .Where(m => m.ChatRoomId == roomId) .FirstOrDefaultAsync(); @@ -981,6 +995,7 @@ public class ChatRoomController( } member.LeaveAt = Instant.FromDateTimeUtc(DateTime.UtcNow); + db.Update(member); await db.SaveChangesAsync(); await crs.PurgeRoomMembersCache(roomId); @@ -1000,7 +1015,7 @@ public class ChatRoomController( { var account = await accounts.GetAccountAsync(new GetAccountRequest { Id = member.AccountId.ToString() }); CultureService.SetCultureInfo(account); - + string title = localizer["ChatInviteTitle"]; string body = member.ChatRoom.Type == ChatRoomType.DirectMessage diff --git a/DysonNetwork.Sphere/Chat/ChatRoomService.cs b/DysonNetwork.Sphere/Chat/ChatRoomService.cs index 8bf644e..af51e1f 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomService.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomService.cs @@ -45,7 +45,7 @@ public class ChatRoomService( if (member is not null) return member; member = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == chatRoomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == chatRoomId && m.JoinedAt != null && m.LeaveAt == null) .Include(m => m.ChatRoom) .FirstOrDefaultAsync(); @@ -95,7 +95,7 @@ public class ChatRoomService( ? await db.ChatMembers .Where(m => directRoomsId.Contains(m.ChatRoomId)) .Where(m => m.AccountId != userId) - .Where(m => m.LeaveAt == null) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .ToListAsync() : []; members = await LoadMemberAccounts(members); @@ -121,7 +121,7 @@ public class ChatRoomService( if (room.Type != ChatRoomType.DirectMessage) return room; var members = await db.ChatMembers .Where(m => m.ChatRoomId == room.Id && m.AccountId != userId) - .Where(m => m.LeaveAt == null) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .ToListAsync(); if (members.Count <= 0) return room; @@ -139,7 +139,8 @@ public class ChatRoomService( var maxRequiredRole = requiredRoles.Max(); var member = await db.ChatMembers - .FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == accountId); + .Where(m => m.ChatRoomId == roomId && m.AccountId == accountId && m.JoinedAt != null && m.LeaveAt == null) + .FirstOrDefaultAsync(); return member?.Role >= maxRequiredRole; } diff --git a/DysonNetwork.Sphere/Chat/ChatService.cs b/DysonNetwork.Sphere/Chat/ChatService.cs index 7561c3c..7ea9571 100644 --- a/DysonNetwork.Sphere/Chat/ChatService.cs +++ b/DysonNetwork.Sphere/Chat/ChatService.cs @@ -441,7 +441,7 @@ public partial class ChatService( public async Task ReadChatRoomAsync(Guid roomId, Guid userId) { var sender = await db.ChatMembers - .Where(m => m.AccountId == userId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == userId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (sender is null) throw new ArgumentException("User is not a member of the chat room."); @@ -452,7 +452,7 @@ public partial class ChatService( public async Task CountUnreadMessage(Guid userId, Guid chatRoomId) { var sender = await db.ChatMembers - .Where(m => m.AccountId == userId && m.ChatRoomId == chatRoomId) + .Where(m => m.AccountId == userId && m.ChatRoomId == chatRoomId && m.JoinedAt != null && m.LeaveAt == null) .Select(m => new { m.LastReadAt }) .FirstOrDefaultAsync(); if (sender?.LastReadAt is null) return 0; diff --git a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs index e8d44ef..960fc07 100644 --- a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs +++ b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs @@ -52,7 +52,7 @@ public class RealtimeCallController( var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) @@ -78,7 +78,7 @@ public class RealtimeCallController( // Check if the user is a member of the chat room var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) @@ -151,7 +151,7 @@ public class RealtimeCallController( var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .Include(m => m.ChatRoom) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) @@ -171,7 +171,7 @@ public class RealtimeCallController( var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) return StatusCode(403, "You need to be a normal member to end a call."); @@ -256,4 +256,4 @@ public class CallParticipant /// When the participant joined the call /// public DateTime JoinedAt { get; set; } -} \ No newline at end of file +} diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index 642167a..f376717 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -105,10 +105,10 @@ public class RealmController( var hasExistingMember = await db.RealmMembers .Where(m => m.AccountId == Guid.Parse(relatedUser.Id)) .Where(m => m.RealmId == realm.Id) - .Where(m => m.LeaveAt == null) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .AnyAsync(); if (hasExistingMember) - return BadRequest("This user has been joined the realm or leave cannot be invited again."); + return BadRequest("This user already in the realm cannot be invited again."); var member = new SnRealmMember { @@ -232,7 +232,7 @@ public class RealmController( var query = db.RealmMembers .Where(m => m.RealmId == realm.Id) - .Where(m => m.LeaveAt == null); + .Where(m => m.JoinedAt != null && m.LeaveAt == null); if (withStatus) { @@ -289,6 +289,7 @@ public class RealmController( var member = await db.RealmMembers .Where(m => m.AccountId == accountId) .Where(m => m.Realm.Slug == slug) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member is null) return NotFound(); @@ -305,7 +306,7 @@ public class RealmController( var member = await db.RealmMembers .Where(m => m.AccountId == accountId) .Where(m => m.Realm.Slug == slug) - .Where(m => m.JoinedAt != null) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member is null) return NotFound(); @@ -444,7 +445,7 @@ public class RealmController( var accountId = Guid.Parse(currentUser.Id); var member = await db.RealmMembers - .Where(m => m.AccountId == accountId && m.RealmId == realm.Id && m.JoinedAt != null) + .Where(m => m.AccountId == accountId && m.RealmId == realm.Id && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member is null || member.Role < RealmMemberRole.Moderator) return StatusCode(403, "You do not have permission to update this realm."); @@ -555,7 +556,7 @@ public class RealmController( return StatusCode(403, "Only community realms can be joined without invitation."); var existingMember = await db.RealmMembers - .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.RealmId == realm.Id) + .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.RealmId == realm.Id && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (existingMember is not null) return BadRequest("You are already a member of this realm."); @@ -600,7 +601,7 @@ public class RealmController( if (realm is null) return NotFound(); var member = await db.RealmMembers - .Where(m => m.AccountId == memberId && m.RealmId == realm.Id) + .Where(m => m.AccountId == memberId && m.RealmId == realm.Id && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member is null) return NotFound(); @@ -640,7 +641,7 @@ public class RealmController( if (realm is null) return NotFound(); var member = await db.RealmMembers - .Where(m => m.AccountId == memberId && m.RealmId == realm.Id) + .Where(m => m.AccountId == memberId && m.RealmId == realm.Id && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); if (member is null) return NotFound(); diff --git a/DysonNetwork.Sphere/Realm/RealmService.cs b/DysonNetwork.Sphere/Realm/RealmService.cs index 5aab438..495fa16 100644 --- a/DysonNetwork.Sphere/Realm/RealmService.cs +++ b/DysonNetwork.Sphere/Realm/RealmService.cs @@ -30,6 +30,7 @@ public class RealmService( var realms = await db.RealmMembers .Include(m => m.Realm) .Where(m => m.AccountId == accountId) + .Where(m => m.JoinedAt != null && m.LeaveAt == null) .Select(m => m.Realm!.Id) .ToListAsync(); @@ -67,7 +68,8 @@ public class RealmService( var maxRequiredRole = requiredRoles.Max(); var member = await db.RealmMembers - .FirstOrDefaultAsync(m => m.RealmId == realmId && m.AccountId == accountId); + .Where(m => m.RealmId == realmId && m.AccountId == accountId && m.JoinedAt != null && m.LeaveAt == null) + .FirstOrDefaultAsync(); return member?.Role >= maxRequiredRole; } @@ -90,4 +92,4 @@ public class RealmService( return m; }).ToList(); } -} \ No newline at end of file +} diff --git a/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs b/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs index 729e66e..644f470 100644 --- a/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs +++ b/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs @@ -360,7 +360,7 @@ public class BroadcastEventHandler( // Get user's joined chat rooms var userRooms = await db.ChatMembers - .Where(m => m.AccountId == evt.AccountId && m.LeaveAt == null) + .Where(m => m.AccountId == evt.AccountId && m.JoinedAt != null && m.LeaveAt == null) .Select(m => m.ChatRoomId) .ToListAsync(cancellationToken: stoppingToken);