diff --git a/DysonNetwork.Shared/Models/ChatRoom.cs b/DysonNetwork.Shared/Models/ChatRoom.cs index 963bd62..5ab27b4 100644 --- a/DysonNetwork.Shared/Models/ChatRoom.cs +++ b/DysonNetwork.Shared/Models/ChatRoom.cs @@ -38,13 +38,6 @@ public class SnChatRoom : ModelBase, IIdentifiedResource public string ResourceIdentifier => $"chatroom:{Id}"; } -public abstract class ChatMemberRole -{ - public const int Owner = 100; - public const int Moderator = 50; - public const int Member = 0; -} - public enum ChatMemberNotify { All, @@ -75,12 +68,16 @@ public class SnChatMember : ModelBase [MaxLength(1024)] public string? Nick { get; set; } - public int Role { get; set; } = ChatMemberRole.Member; public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All; public Instant? LastReadAt { get; set; } public Instant? JoinedAt { get; set; } public Instant? LeaveAt { get; set; } - public bool IsBot { get; set; } = false; + + public Guid? InvitedById { get; set; } + public SnChatMember? InvitedBy { get; set; } + + // Backwards support field + [NotMapped] public int Role = 0; /// /// The break time is the user doesn't receive any message from this member for a while. @@ -107,15 +104,19 @@ public class ChatMemberTransmissionObject : ModelBase [MaxLength(1024)] public string? Nick { get; set; } - public int Role { get; set; } = ChatMemberRole.Member; public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All; public Instant? JoinedAt { get; set; } public Instant? LeaveAt { get; set; } - public bool IsBot { get; set; } = false; + + public Guid? InvitedById { get; set; } + public SnChatMember? InvitedBy { get; set; } public Instant? BreakUntil { get; set; } public Instant? TimeoutUntil { get; set; } public ChatTimeoutCause? TimeoutCause { get; set; } + + // Backwards support field + [NotMapped] public int Role = 0; public static ChatMemberTransmissionObject FromEntity(SnChatMember member) { @@ -126,14 +127,14 @@ public class ChatMemberTransmissionObject : ModelBase AccountId = member.AccountId, Account = member.Account!, Nick = member.Nick, - Role = member.Role, Notify = member.Notify, JoinedAt = member.JoinedAt, LeaveAt = member.LeaveAt, - IsBot = member.IsBot, BreakUntil = member.BreakUntil, TimeoutUntil = member.TimeoutUntil, TimeoutCause = member.TimeoutCause, + InvitedById = member.InvitedById, + InvitedBy = member.InvitedBy, CreatedAt = member.CreatedAt, UpdatedAt = member.UpdatedAt, DeletedAt = member.DeletedAt diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs index f61e233..c5bd144 100644 --- a/DysonNetwork.Sphere/Chat/ChatController.cs +++ b/DysonNetwork.Sphere/Chat/ChatController.cs @@ -112,7 +112,7 @@ public partial class ChatController( .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); - if (member == null || member.Role < ChatMemberRole.Member) + if (member == null) return StatusCode(403, "You are not a member of this chat room."); } @@ -155,7 +155,7 @@ public partial class ChatController( .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); - if (member == null || member.Role < ChatMemberRole.Member) + if (member == null) return StatusCode(403, "You are not a member of this chat room."); } @@ -255,8 +255,8 @@ public partial class ChatController( return BadRequest("You cannot send an empty message."); var member = await crs.GetRoomMember(Guid.Parse(currentUser.Id), roomId); - if (member == null || member.Role < ChatMemberRole.Member) - return StatusCode(403, "You need to be a normal member to send messages here."); + if (member == null) + return StatusCode(403, "You need to be a member to send messages here."); // Validate fund if provided if (request.FundId.HasValue) diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs b/DysonNetwork.Sphere/Chat/ChatRoomController.cs index 850c21f..0a058e5 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs @@ -6,7 +6,6 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Registry; using DysonNetwork.Sphere.Localization; - using Grpc.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Localization; @@ -80,6 +79,7 @@ public class ChatRoomController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var relatedUser = await accounts.GetAccountAsync( new GetAccountRequest { Id = request.RelatedUserId.ToString() } @@ -112,18 +112,17 @@ public class ChatRoomController( { Type = ChatRoomType.DirectMessage, IsPublic = false, + AccountId = accountId, Members = new List { new() { - AccountId = Guid.Parse(currentUser.Id), - Role = ChatMemberRole.Owner, - JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) + AccountId = accountId, + JoinedAt = SystemClock.Instance.GetCurrentInstant() }, new() { AccountId = request.RelatedUserId, - Role = ChatMemberRole.Member, JoinedAt = null, // Pending status } } @@ -154,11 +153,12 @@ public class ChatRoomController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var currentId = Guid.Parse(currentUser.Id); var room = await db.ChatRooms .Include(c => c.Members) .Where(c => c.Type == ChatRoomType.DirectMessage && c.Members.Count == 2) - .Where(c => c.Members.Any(m => m.AccountId == Guid.Parse(currentUser.Id))) + .Where(c => c.Members.Any(m => m.AccountId == currentId)) .Where(c => c.Members.Any(m => m.AccountId == accountId)) .FirstOrDefaultAsync(); if (room is null) return NotFound(); @@ -168,7 +168,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; } @@ -198,7 +198,6 @@ public class ChatRoomController( { new() { - Role = ChatMemberRole.Owner, AccountId = accountId, JoinedAt = SystemClock.Instance.GetCurrentInstant() } @@ -297,6 +296,7 @@ public class ChatRoomController( public async Task> UpdateChatRoom(Guid id, [FromBody] ChatRoomRequest request) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var chatRoom = await db.ChatRooms .Where(e => e.Id == id) @@ -305,16 +305,18 @@ public class ChatRoomController( if (chatRoom.RealmId is not null) { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), - [RealmMemberRole.Moderator])) + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, accountId, [RealmMemberRole.Moderator])) return StatusCode(403, "You need at least be a realm moderator to update the chat."); } - else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Moderator)) - return StatusCode(403, "You need at least be a moderator to update the chat."); + else if (chatRoom.Type == ChatRoomType.DirectMessage && await crs.IsChatMember(chatRoom.Id, accountId)) + return StatusCode(403, "You need be part of the DM to update the chat."); + else if (chatRoom.AccountId != accountId) + return StatusCode(403, "You need be the owner to update the chat."); if (request.RealmId is not null) { - if (!await rs.IsMemberWithRole(request.RealmId.Value, Guid.Parse(currentUser.Id), [RealmMemberRole.Moderator])) + if (!await rs.IsMemberWithRole(request.RealmId.Value, Guid.Parse(currentUser.Id), + [RealmMemberRole.Moderator])) return StatusCode(403, "You need at least be a moderator to transfer the chat linked to the realm."); chatRoom.RealmId = request.RealmId; } @@ -406,7 +408,8 @@ public class ChatRoomController( [HttpDelete("{id:guid}")] public async Task DeleteChatRoom(Guid id) { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var chatRoom = await db.ChatRooms .Where(e => e.Id == id) @@ -415,12 +418,13 @@ public class ChatRoomController( if (chatRoom.RealmId is not null) { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), - [RealmMemberRole.Moderator])) + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, accountId, [RealmMemberRole.Moderator])) return StatusCode(403, "You need at least be a realm moderator to delete the chat."); } - else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Owner)) - return StatusCode(403, "You need at least be the owner to delete the chat."); + else if (chatRoom.Type == ChatRoomType.DirectMessage && await crs.IsChatMember(chatRoom.Id, accountId)) + return StatusCode(403, "You need be part of the DM to update the chat."); + else if (chatRoom.AccountId != accountId) + return StatusCode(403, "You need be the owner to update the chat."); var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; @@ -497,9 +501,11 @@ public class ChatRoomController( { if (currentUser is null) return Unauthorized(); var member = await db.ChatMembers - .Where(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id) && m.JoinedAt != null && m.LeaveAt == null) + .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."); + 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 @@ -532,7 +538,8 @@ public class ChatRoomController( { if (currentUser is null) return Unauthorized(); var member = await db.ChatMembers - .Where(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id) && m.JoinedAt != null && m.LeaveAt == null) + .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."); } @@ -594,8 +601,7 @@ public class ChatRoomController( [HttpPost("invites/{roomId:guid}")] [Authorize] - public async Task> InviteMember(Guid roomId, - [FromBody] ChatMemberRequest request) + public async Task> InviteMember(Guid roomId, [FromBody] ChatMemberRequest request) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var accountId = Guid.Parse(currentUser.Id); @@ -613,7 +619,7 @@ public class ChatRoomController( Status = -100 }); - if (relationship?.Relationship != null && relationship.Relationship.Status == -100) + if (relationship?.Relationship is { Status: -100 }) return StatusCode(403, "You cannot invite a user that blocked you."); var chatRoom = await db.ChatRooms @@ -621,26 +627,21 @@ public class ChatRoomController( .FirstOrDefaultAsync(); if (chatRoom is null) return NotFound(); + var operatorMember = await db.ChatMembers + .Where(p => p.AccountId == accountId && p.ChatRoomId == chatRoom.Id) + .FirstOrDefaultAsync(); + if (operatorMember is null) return StatusCode(403, "You need to be a part of chat to invite member to the chat."); + // Handle realm-owned chat rooms if (chatRoom.RealmId is not null) { if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, accountId, [RealmMemberRole.Moderator])) return StatusCode(403, "You need at least be a realm moderator to invite members to this chat."); } - else - { - 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) - return StatusCode(403, - "You need at least be a moderator to invite other members to this chat room."); - if (chatMember.Role < request.Role) - return StatusCode(403, "You cannot invite member with higher permission than yours."); - } + else if (chatRoom.Type == ChatRoomType.DirectMessage && await crs.IsChatMember(chatRoom.Id, accountId)) + return StatusCode(403, "You need be part of the DM to invite member to the chat."); + else if (chatRoom.AccountId != accountId) + return StatusCode(403, "You need be the owner to invite member to this chat."); var existingMember = await db.ChatMembers .Where(m => m.AccountId == request.RelatedUserId) @@ -648,9 +649,7 @@ public class ChatRoomController( .FirstOrDefaultAsync(); if (existingMember != null) { - if (existingMember.LeaveAt == null) - return BadRequest("This user has been joined the chat cannot be invited again."); - + existingMember.InvitedById = operatorMember.Id; existingMember.LeaveAt = null; existingMember.JoinedAt = null; db.ChatMembers.Update(existingMember); @@ -661,10 +660,10 @@ public class ChatRoomController( { Action = "chatrooms.invite", Meta = - { - { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) }, - { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(relatedUser.Id.ToString()) } - }, + { + { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(relatedUser.Id.ToString()) } + }, AccountId = currentUser.Id, UserAgent = Request.Headers.UserAgent, IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() @@ -675,9 +674,9 @@ public class ChatRoomController( var newMember = new SnChatMember { + InvitedById = operatorMember.Id, AccountId = Guid.Parse(relatedUser.Id), ChatRoomId = roomId, - Role = request.Role, }; db.ChatMembers.Add(newMember); @@ -770,7 +769,7 @@ public class ChatRoomController( .FirstOrDefaultAsync(); if (member is null) return NotFound(); - member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); + db.ChatMembers.Remove(member); await db.SaveChangesAsync(); return NoContent(); @@ -814,74 +813,12 @@ public class ChatRoomController( return Ok(targetMember); } - [HttpPatch("{roomId:guid}/members/{memberId:guid}/role")] - [Authorize] - public async Task> UpdateChatMemberRole(Guid roomId, Guid memberId, [FromBody] int newRole) - { - if (newRole >= ChatMemberRole.Owner) return BadRequest("Unable to set chat member to owner or greater role."); - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var chatRoom = await db.ChatRooms - .Where(r => r.Id == roomId) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - - // Check if the chat room is owned by a realm - if (chatRoom.RealmId is not null) - { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), [RealmMemberRole.Moderator])) - return StatusCode(403, "You need at least be a realm moderator to change member roles."); - } - else - { - var targetMember = await db.ChatMembers - .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) - .FirstOrDefaultAsync(); - if (targetMember is null) return NotFound(); - - // Check if the current user has permission to change roles - if ( - !await crs.IsMemberWithRole( - chatRoom.Id, - Guid.Parse(currentUser.Id), - ChatMemberRole.Moderator, - targetMember.Role, - newRole - ) - ) - return StatusCode(403, "You don't have enough permission to edit the roles of members."); - - targetMember.Role = newRole; - db.ChatMembers.Update(targetMember); - await db.SaveChangesAsync(); - - await crs.PurgeRoomMembersCache(roomId); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.role.edit", - Meta = - { - { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) }, - { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) }, - { "new_role", Google.Protobuf.WellKnownTypes.Value.ForNumber(newRole) } - }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return Ok(targetMember); - } - - return BadRequest(); - } - [HttpDelete("{roomId:guid}/members/{memberId:guid}")] [Authorize] public async Task RemoveChatMember(Guid roomId, Guid memberId) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var chatRoom = await db.ChatRooms .Where(r => r.Id == roomId) @@ -891,15 +828,13 @@ public class ChatRoomController( // Check if the chat room is owned by a realm if (chatRoom.RealmId is not null) { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), - [RealmMemberRole.Moderator])) + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, accountId, [RealmMemberRole.Moderator])) return StatusCode(403, "You need at least be a realm moderator to remove members."); } - else - { - if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), [ChatMemberRole.Moderator])) - return StatusCode(403, "You need at least be a moderator to remove members."); - } + else if (chatRoom.Type == ChatRoomType.DirectMessage && await crs.IsChatMember(chatRoom.Id, accountId)) + return StatusCode(403, "You need be part of the DM to update the chat."); + else if (chatRoom.AccountId != accountId) + return StatusCode(403, "You need be the owner to update the chat."); // Find the target member var member = await db.ChatMembers @@ -907,10 +842,6 @@ public class ChatRoomController( .FirstOrDefaultAsync(); if (member is null) return NotFound(); - // Check if the current user has sufficient permissions - if (!await crs.IsMemberWithRole(chatRoom.Id, memberId, member.Role)) - return StatusCode(403, "You cannot remove members with equal or higher roles."); - member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); await db.SaveChangesAsync(); _ = crs.PurgeRoomMembersCache(roomId); @@ -964,8 +895,7 @@ public class ChatRoomController( { AccountId = Guid.Parse(currentUser.Id), ChatRoomId = roomId, - Role = ChatMemberRole.Member, - JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) + JoinedAt = SystemClock.Instance.GetCurrentInstant() }; db.ChatMembers.Add(newMember); @@ -989,6 +919,12 @@ public class ChatRoomController( public async Task LeaveChat(Guid roomId) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var chat = await db.ChatRooms.FirstOrDefaultAsync(c => c.Id == roomId); + if (chat is null) return NotFound(); + if (chat.AccountId == accountId) + return BadRequest("You cannot leave you own chat room"); var member = await db.ChatMembers .Where(m => m.JoinedAt != null && m.LeaveAt == null) @@ -997,20 +933,7 @@ public class ChatRoomController( .FirstOrDefaultAsync(); if (member is null) return NotFound(); - if (member.Role == ChatMemberRole.Owner) - { - // Check if this is the only owner - var otherOwners = await db.ChatMembers - .Where(m => m.ChatRoomId == roomId) - .Where(m => m.Role == ChatMemberRole.Owner) - .Where(m => m.AccountId != Guid.Parse(currentUser.Id)) - .AnyAsync(); - - if (!otherOwners) - return BadRequest("The last owner cannot leave the chat. Transfer ownership first or delete the chat."); - } - - member.LeaveAt = Instant.FromDateTimeUtc(DateTime.UtcNow); + member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); db.Update(member); await db.SaveChangesAsync(); await crs.PurgeRoomMembersCache(roomId); @@ -1056,4 +979,4 @@ public class ChatRoomController( } ); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatRoomService.cs b/DysonNetwork.Sphere/Chat/ChatRoomService.cs index bfd4199..30d58ac 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomService.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomService.cs @@ -133,16 +133,11 @@ public class ChatRoomService( return room; } - public async Task IsMemberWithRole(Guid roomId, Guid accountId, params int[] requiredRoles) + public async Task IsChatMember(Guid roomId, Guid accountId) { - if (requiredRoles.Length == 0) - return false; - - var maxRequiredRole = requiredRoles.Max(); - var member = await db.ChatMembers + return await db.ChatMembers .Where(m => m.ChatRoomId == roomId && m.AccountId == accountId && m.JoinedAt != null && m.LeaveAt == null) - .FirstOrDefaultAsync(); - return member?.Role >= maxRequiredRole; + .AnyAsync(); } public async Task LoadMemberAccount(SnChatMember member) diff --git a/DysonNetwork.Sphere/Chat/ChatService.cs b/DysonNetwork.Sphere/Chat/ChatService.cs index a45a9ef..97d66c6 100644 --- a/DysonNetwork.Sphere/Chat/ChatService.cs +++ b/DysonNetwork.Sphere/Chat/ChatService.cs @@ -580,7 +580,7 @@ public partial class ChatService( { var call = await GetCallOngoingAsync(roomId); if (call is null) throw new InvalidOperationException("No ongoing call was not found."); - if (sender.Role < ChatMemberRole.Moderator && call.SenderId != sender.Id) + if (sender.AccountId != call.Room.AccountId && call.SenderId != sender.Id) throw new InvalidOperationException("You are not the call initiator either the chat room moderator."); // End the realtime session if it exists diff --git a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs index 960fc07..c3abbd3 100644 --- a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs +++ b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs @@ -55,7 +55,7 @@ public class RealtimeCallController( .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); - if (member == null || member.Role < ChatMemberRole.Member) + if (member == null) return StatusCode(403, "You need to be a member to view call status."); var ongoingCall = await db.ChatRealtimeCall @@ -81,7 +81,7 @@ public class RealtimeCallController( .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null) .FirstOrDefaultAsync(); - if (member == null || member.Role < ChatMemberRole.Member) + if (member == null) return StatusCode(403, "You need to be a member to join a call."); // Get ongoing call @@ -93,7 +93,7 @@ public class RealtimeCallController( if (string.IsNullOrEmpty(ongoingCall.SessionId)) return BadRequest("Call session is not properly configured."); - var isAdmin = member.Role >= ChatMemberRole.Moderator; + var isAdmin = member.AccountId == ongoingCall.Room.AccountId || ongoingCall.Room.Type == ChatRoomType.DirectMessage; var userToken = realtime.GetUserToken(currentUser, ongoingCall.SessionId, isAdmin); // Get LiveKit endpoint from configuration @@ -154,8 +154,8 @@ public class RealtimeCallController( .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) - return StatusCode(403, "You need to be a normal member to start a call."); + if (member == null) + return StatusCode(403, "You need to be a member to start a call."); var ongoingCall = await cs.GetCallOngoingAsync(roomId); if (ongoingCall is not null) return StatusCode(423, "There is already an ongoing call inside the chatroom."); @@ -173,8 +173,8 @@ public class RealtimeCallController( var member = await db.ChatMembers .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."); + if (member == null) + return StatusCode(403, "You need to be a member to end a call."); try { diff --git a/DysonNetwork.Sphere/Migrations/20251130125717_SimplerChatRoom.Designer.cs b/DysonNetwork.Sphere/Migrations/20251130125717_SimplerChatRoom.Designer.cs new file mode 100644 index 0000000..a30dd7b --- /dev/null +++ b/DysonNetwork.Sphere/Migrations/20251130125717_SimplerChatRoom.Designer.cs @@ -0,0 +1,1940 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using DysonNetwork.Shared.Models; +using DysonNetwork.Sphere; +using DysonNetwork.Sphere.WebReader; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Sphere.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20251130125717_SimplerChatRoom")] + partial class SimplerChatRoom + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("BreakUntil") + .HasColumnType("timestamp with time zone") + .HasColumnName("break_until"); + + b.Property("ChatRoomId") + .HasColumnType("uuid") + .HasColumnName("chat_room_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("InvitedById") + .HasColumnType("uuid") + .HasColumnName("invited_by_id"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("joined_at"); + + b.Property("LastReadAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_at"); + + b.Property("LeaveAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("leave_at"); + + b.Property("Nick") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("nick"); + + b.Property("Notify") + .HasColumnType("integer") + .HasColumnName("notify"); + + b.Property("TimeoutCause") + .HasColumnType("jsonb") + .HasColumnName("timeout_cause"); + + b.Property("TimeoutUntil") + .HasColumnType("timestamp with time zone") + .HasColumnName("timeout_until"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_chat_members"); + + b.HasAlternateKey("ChatRoomId", "AccountId") + .HasName("ak_chat_members_chat_room_id_account_id"); + + b.HasIndex("InvitedById") + .HasDatabaseName("ix_chat_members_invited_by_id"); + + b.ToTable("chat_members", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property>("Attachments") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("attachments"); + + b.Property("ChatRoomId") + .HasColumnType("uuid") + .HasColumnName("chat_room_id"); + + b.Property("Content") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("EditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("edited_at"); + + b.Property("ForwardedMessageId") + .HasColumnType("uuid") + .HasColumnName("forwarded_message_id"); + + b.Property>("MembersMentioned") + .HasColumnType("jsonb") + .HasColumnName("members_mentioned"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Nonce") + .IsRequired() + .HasMaxLength(36) + .HasColumnType("character varying(36)") + .HasColumnName("nonce"); + + b.Property("RepliedMessageId") + .HasColumnType("uuid") + .HasColumnName("replied_message_id"); + + b.Property("SenderId") + .HasColumnType("uuid") + .HasColumnName("sender_id"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_chat_messages"); + + b.HasIndex("ChatRoomId") + .HasDatabaseName("ix_chat_messages_chat_room_id"); + + b.HasIndex("ForwardedMessageId") + .HasDatabaseName("ix_chat_messages_forwarded_message_id"); + + b.HasIndex("RepliedMessageId") + .HasDatabaseName("ix_chat_messages_replied_message_id"); + + b.HasIndex("SenderId") + .HasDatabaseName("ix_chat_messages_sender_id"); + + b.ToTable("chat_messages", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessageReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Attitude") + .HasColumnType("integer") + .HasColumnName("attitude"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("MessageId") + .HasColumnType("uuid") + .HasColumnName("message_id"); + + b.Property("SenderId") + .HasColumnType("uuid") + .HasColumnName("sender_id"); + + b.Property("Symbol") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("symbol"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_chat_reactions"); + + b.HasIndex("MessageId") + .HasDatabaseName("ix_chat_reactions_message_id"); + + b.HasIndex("SenderId") + .HasDatabaseName("ix_chat_reactions_sender_id"); + + b.ToTable("chat_reactions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatRoom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Background") + .HasColumnType("jsonb") + .HasColumnName("background"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("IsCommunity") + .HasColumnType("boolean") + .HasColumnName("is_community"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("is_public"); + + b.Property("Name") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("Picture") + .HasColumnType("jsonb") + .HasColumnName("picture"); + + b.Property("RealmId") + .HasColumnType("uuid") + .HasColumnName("realm_id"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_chat_rooms"); + + b.ToTable("chat_rooms", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPoll", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ended_at"); + + b.Property("IsAnonymous") + .HasColumnType("boolean") + .HasColumnName("is_anonymous"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_polls"); + + b.HasIndex("PublisherId") + .HasDatabaseName("ix_polls_publisher_id"); + + b.ToTable("polls", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPollAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property>("Answer") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("answer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("PollId") + .HasColumnType("uuid") + .HasColumnName("poll_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_poll_answers"); + + b.HasIndex("PollId") + .HasDatabaseName("ix_poll_answers_poll_id"); + + b.ToTable("poll_answers", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPollQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("IsRequired") + .HasColumnType("boolean") + .HasColumnName("is_required"); + + b.Property>("Options") + .HasColumnType("jsonb") + .HasColumnName("options"); + + b.Property("Order") + .HasColumnType("integer") + .HasColumnName("order"); + + b.Property("PollId") + .HasColumnType("uuid") + .HasColumnName("poll_id"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_poll_questions"); + + b.HasIndex("PollId") + .HasDatabaseName("ix_poll_questions_poll_id"); + + b.ToTable("poll_questions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property>("Attachments") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("attachments"); + + b.Property("AwardedScore") + .HasColumnType("numeric") + .HasColumnName("awarded_score"); + + b.Property("Content") + .HasColumnType("text") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("Downvotes") + .HasColumnType("integer") + .HasColumnName("downvotes"); + + b.Property("EditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("edited_at"); + + b.Property("EmbedView") + .HasColumnType("jsonb") + .HasColumnName("embed_view"); + + b.Property("ForwardedGone") + .HasColumnType("boolean") + .HasColumnName("forwarded_gone"); + + b.Property("ForwardedPostId") + .HasColumnType("uuid") + .HasColumnName("forwarded_post_id"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("PinMode") + .HasColumnType("integer") + .HasColumnName("pin_mode"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("published_at"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("RealmId") + .HasColumnType("uuid") + .HasColumnName("realm_id"); + + b.Property("RepliedGone") + .HasColumnType("boolean") + .HasColumnName("replied_gone"); + + b.Property("RepliedPostId") + .HasColumnType("uuid") + .HasColumnName("replied_post_id"); + + b.Property>("SensitiveMarks") + .HasColumnType("jsonb") + .HasColumnName("sensitive_marks"); + + b.Property("Slug") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("slug"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Upvotes") + .HasColumnType("integer") + .HasColumnName("upvotes"); + + b.Property("ViewsTotal") + .HasColumnType("integer") + .HasColumnName("views_total"); + + b.Property("ViewsUnique") + .HasColumnType("integer") + .HasColumnName("views_unique"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_posts"); + + b.HasIndex("ForwardedPostId") + .HasDatabaseName("ix_posts_forwarded_post_id"); + + b.HasIndex("PublisherId") + .HasDatabaseName("ix_posts_publisher_id"); + + b.HasIndex("RepliedPostId") + .HasDatabaseName("ix_posts_replied_post_id"); + + b.ToTable("posts", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostAward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Amount") + .HasColumnType("numeric") + .HasColumnName("amount"); + + b.Property("Attitude") + .HasColumnType("integer") + .HasColumnName("attitude"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Message") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PostId") + .HasColumnType("uuid") + .HasColumnName("post_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_post_awards"); + + b.HasIndex("PostId") + .HasDatabaseName("ix_post_awards_post_id"); + + b.ToTable("post_awards", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("slug"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_post_categories"); + + b.ToTable("post_categories", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostCategorySubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("TagId") + .HasColumnType("uuid") + .HasColumnName("tag_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_post_category_subscriptions"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_post_category_subscriptions_category_id"); + + b.HasIndex("TagId") + .HasDatabaseName("ix_post_category_subscriptions_tag_id"); + + b.ToTable("post_category_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("slug"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_post_collections"); + + b.HasIndex("PublisherId") + .HasDatabaseName("ix_post_collections_publisher_id"); + + b.ToTable("post_collections", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostFeaturedRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("FeaturedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("featured_at"); + + b.Property("PostId") + .HasColumnType("uuid") + .HasColumnName("post_id"); + + b.Property("SocialCredits") + .HasColumnType("integer") + .HasColumnName("social_credits"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_post_featured_records"); + + b.HasIndex("PostId") + .HasDatabaseName("ix_post_featured_records_post_id"); + + b.ToTable("post_featured_records", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Attitude") + .HasColumnType("integer") + .HasColumnName("attitude"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("PostId") + .HasColumnType("uuid") + .HasColumnName("post_id"); + + b.Property("Symbol") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("symbol"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_post_reactions"); + + b.HasIndex("PostId") + .HasDatabaseName("ix_post_reactions_post_id"); + + b.ToTable("post_reactions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("slug"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_post_tags"); + + b.ToTable("post_tags", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Background") + .HasColumnType("jsonb") + .HasColumnName("background"); + + b.Property("Bio") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("bio"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("Nick") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("nick"); + + b.Property("Picture") + .HasColumnType("jsonb") + .HasColumnName("picture"); + + b.Property("RealmId") + .HasColumnType("uuid") + .HasColumnName("realm_id"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Verification") + .HasColumnType("jsonb") + .HasColumnName("verification"); + + b.HasKey("Id") + .HasName("pk_publishers"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_publishers_name"); + + b.ToTable("publishers", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisherFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Flag") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("flag"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_publisher_features"); + + b.HasIndex("PublisherId") + .HasDatabaseName("ix_publisher_features_publisher_id"); + + b.ToTable("publisher_features", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisherMember", b => + { + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("joined_at"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("PublisherId", "AccountId") + .HasName("pk_publisher_members"); + + b.ToTable("publisher_members", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisherSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("Tier") + .HasColumnType("integer") + .HasColumnName("tier"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_publisher_subscriptions"); + + b.HasIndex("PublisherId") + .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); + + b.ToTable("publisher_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealtimeCall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ended_at"); + + b.Property("ProviderName") + .HasColumnType("text") + .HasColumnName("provider_name"); + + b.Property("RoomId") + .HasColumnType("uuid") + .HasColumnName("room_id"); + + b.Property("SenderId") + .HasColumnType("uuid") + .HasColumnName("sender_id"); + + b.Property("SessionId") + .HasColumnType("text") + .HasColumnName("session_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UpstreamConfigJson") + .HasColumnType("jsonb") + .HasColumnName("upstream"); + + b.HasKey("Id") + .HasName("pk_chat_realtime_call"); + + b.HasIndex("RoomId") + .HasDatabaseName("ix_chat_realtime_call_room_id"); + + b.HasIndex("SenderId") + .HasDatabaseName("ix_chat_realtime_call_sender_id"); + + b.ToTable("chat_realtime_call", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnSticker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Image") + .HasColumnType("jsonb") + .HasColumnName("image"); + + b.Property("ImageId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("image_id"); + + b.Property("PackId") + .HasColumnType("uuid") + .HasColumnName("pack_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("slug"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_stickers"); + + b.HasIndex("PackId") + .HasDatabaseName("ix_stickers_pack_id"); + + b.HasIndex("Slug") + .HasDatabaseName("ix_stickers_slug"); + + b.ToTable("stickers", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.StickerPack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("Prefix") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("prefix"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_sticker_packs"); + + b.HasIndex("Prefix") + .IsUnique() + .HasDatabaseName("ix_sticker_packs_prefix"); + + b.HasIndex("PublisherId") + .HasDatabaseName("ix_sticker_packs_publisher_id"); + + b.ToTable("sticker_packs", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.StickerPackOwnership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("PackId") + .HasColumnType("uuid") + .HasColumnName("pack_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_sticker_pack_ownerships"); + + b.HasIndex("PackId") + .HasDatabaseName("ix_sticker_pack_ownerships_pack_id"); + + b.ToTable("sticker_pack_ownerships", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebArticle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Author") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("author"); + + b.Property("Content") + .HasColumnType("text") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("FeedId") + .HasColumnType("uuid") + .HasColumnName("feed_id"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Preview") + .HasColumnType("jsonb") + .HasColumnName("preview"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("published_at"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("url"); + + b.HasKey("Id") + .HasName("pk_web_articles"); + + b.HasIndex("FeedId") + .HasDatabaseName("ix_web_articles_feed_id"); + + b.HasIndex("Url") + .IsUnique() + .HasDatabaseName("ix_web_articles_url"); + + b.ToTable("web_articles", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebFeed", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Config") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("config"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("description"); + + b.Property("Preview") + .HasColumnType("jsonb") + .HasColumnName("preview"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("title"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("url"); + + b.HasKey("Id") + .HasName("pk_web_feeds"); + + b.HasIndex("PublisherId") + .HasDatabaseName("ix_web_feeds_publisher_id"); + + b.HasIndex("Url") + .IsUnique() + .HasDatabaseName("ix_web_feeds_url"); + + b.ToTable("web_feeds", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebFeedSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("FeedId") + .HasColumnType("uuid") + .HasColumnName("feed_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_web_feed_subscriptions"); + + b.HasIndex("FeedId") + .HasDatabaseName("ix_web_feed_subscriptions_feed_id"); + + b.ToTable("web_feed_subscriptions", (string)null); + }); + + modelBuilder.Entity("SnPostSnPostCategory", b => + { + b.Property("CategoriesId") + .HasColumnType("uuid") + .HasColumnName("categories_id"); + + b.Property("PostsId") + .HasColumnType("uuid") + .HasColumnName("posts_id"); + + b.HasKey("CategoriesId", "PostsId") + .HasName("pk_post_category_links"); + + b.HasIndex("PostsId") + .HasDatabaseName("ix_post_category_links_posts_id"); + + b.ToTable("post_category_links", (string)null); + }); + + modelBuilder.Entity("SnPostSnPostCollection", b => + { + b.Property("CollectionsId") + .HasColumnType("uuid") + .HasColumnName("collections_id"); + + b.Property("PostsId") + .HasColumnType("uuid") + .HasColumnName("posts_id"); + + b.HasKey("CollectionsId", "PostsId") + .HasName("pk_post_collection_links"); + + b.HasIndex("PostsId") + .HasDatabaseName("ix_post_collection_links_posts_id"); + + b.ToTable("post_collection_links", (string)null); + }); + + modelBuilder.Entity("SnPostSnPostTag", b => + { + b.Property("PostsId") + .HasColumnType("uuid") + .HasColumnName("posts_id"); + + b.Property("TagsId") + .HasColumnType("uuid") + .HasColumnName("tags_id"); + + b.HasKey("PostsId", "TagsId") + .HasName("pk_post_tag_links"); + + b.HasIndex("TagsId") + .HasDatabaseName("ix_post_tag_links_tags_id"); + + b.ToTable("post_tag_links", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "ChatRoom") + .WithMany("Members") + .HasForeignKey("ChatRoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "InvitedBy") + .WithMany() + .HasForeignKey("InvitedById") + .HasConstraintName("fk_chat_members_chat_members_invited_by_id"); + + b.Navigation("ChatRoom"); + + b.Navigation("InvitedBy"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "ChatRoom") + .WithMany() + .HasForeignKey("ChatRoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "ForwardedMessage") + .WithMany() + .HasForeignKey("ForwardedMessageId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "RepliedMessage") + .WithMany() + .HasForeignKey("RepliedMessageId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chat_messages_chat_members_sender_id"); + + b.Navigation("ChatRoom"); + + b.Navigation("ForwardedMessage"); + + b.Navigation("RepliedMessage"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessageReaction", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "Message") + .WithMany("Reactions") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); + + b.Navigation("Message"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPoll", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany("Polls") + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_polls_publishers_publisher_id"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPollAnswer", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPoll", "Poll") + .WithMany() + .HasForeignKey("PollId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_poll_answers_polls_poll_id"); + + b.Navigation("Poll"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPollQuestion", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPoll", "Poll") + .WithMany("Questions") + .HasForeignKey("PollId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_poll_questions_polls_poll_id"); + + b.Navigation("Poll"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPost", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPost", "ForwardedPost") + .WithMany() + .HasForeignKey("ForwardedPostId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_posts_posts_forwarded_post_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany("Posts") + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_posts_publishers_publisher_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnPost", "RepliedPost") + .WithMany() + .HasForeignKey("RepliedPostId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_posts_posts_replied_post_id"); + + b.Navigation("ForwardedPost"); + + b.Navigation("Publisher"); + + b.Navigation("RepliedPost"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostAward", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPost", "Post") + .WithMany("Awards") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_awards_posts_post_id"); + + b.Navigation("Post"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostCategorySubscription", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPostCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_post_category_subscriptions_post_categories_category_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnPostTag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .HasConstraintName("fk_post_category_subscriptions_post_tags_tag_id"); + + b.Navigation("Category"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostCollection", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany("Collections") + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_collections_publishers_publisher_id"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostFeaturedRecord", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPost", "Post") + .WithMany("FeaturedRecords") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_featured_records_posts_post_id"); + + b.Navigation("Post"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPostReaction", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPost", "Post") + .WithMany("Reactions") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_reactions_posts_post_id"); + + b.Navigation("Post"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisherFeature", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany("Features") + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_publisher_features_publishers_publisher_id"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisherMember", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany("Members") + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_publisher_members_publishers_publisher_id"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisherSubscription", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany("Subscriptions") + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealtimeCall", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "Room") + .WithMany() + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); + + b.Navigation("Room"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnSticker", b => + { + b.HasOne("DysonNetwork.Shared.Models.StickerPack", "Pack") + .WithMany("Stickers") + .HasForeignKey("PackId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_stickers_sticker_packs_pack_id"); + + b.Navigation("Pack"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.StickerPack", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany() + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.StickerPackOwnership", b => + { + b.HasOne("DysonNetwork.Shared.Models.StickerPack", "Pack") + .WithMany("Ownerships") + .HasForeignKey("PackId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_sticker_pack_ownerships_sticker_packs_pack_id"); + + b.Navigation("Pack"); + }); + + modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebArticle", b => + { + b.HasOne("DysonNetwork.Sphere.WebReader.WebFeed", "Feed") + .WithMany("Articles") + .HasForeignKey("FeedId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_web_articles_web_feeds_feed_id"); + + b.Navigation("Feed"); + }); + + modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebFeed", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublisher", "Publisher") + .WithMany() + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_web_feeds_publishers_publisher_id"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebFeedSubscription", b => + { + b.HasOne("DysonNetwork.Sphere.WebReader.WebFeed", "Feed") + .WithMany() + .HasForeignKey("FeedId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_web_feed_subscriptions_web_feeds_feed_id"); + + b.Navigation("Feed"); + }); + + modelBuilder.Entity("SnPostSnPostCategory", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPostCategory", null) + .WithMany() + .HasForeignKey("CategoriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_category_links_post_categories_categories_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnPost", null) + .WithMany() + .HasForeignKey("PostsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_category_links_posts_posts_id"); + }); + + modelBuilder.Entity("SnPostSnPostCollection", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPostCollection", null) + .WithMany() + .HasForeignKey("CollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnPost", null) + .WithMany() + .HasForeignKey("PostsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_collection_links_posts_posts_id"); + }); + + modelBuilder.Entity("SnPostSnPostTag", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPost", null) + .WithMany() + .HasForeignKey("PostsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_tag_links_posts_posts_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnPostTag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b => + { + b.Navigation("Reactions"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatRoom", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPoll", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPost", b => + { + b.Navigation("Awards"); + + b.Navigation("FeaturedRecords"); + + b.Navigation("Reactions"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublisher", b => + { + b.Navigation("Collections"); + + b.Navigation("Features"); + + b.Navigation("Members"); + + b.Navigation("Polls"); + + b.Navigation("Posts"); + + b.Navigation("Subscriptions"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.StickerPack", b => + { + b.Navigation("Ownerships"); + + b.Navigation("Stickers"); + }); + + modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebFeed", b => + { + b.Navigation("Articles"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Sphere/Migrations/20251130125717_SimplerChatRoom.cs b/DysonNetwork.Sphere/Migrations/20251130125717_SimplerChatRoom.cs new file mode 100644 index 0000000..01e479d --- /dev/null +++ b/DysonNetwork.Sphere/Migrations/20251130125717_SimplerChatRoom.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Sphere.Migrations +{ + /// + public partial class SimplerChatRoom : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "is_bot", + table: "chat_members"); + + migrationBuilder.DropColumn( + name: "role", + table: "chat_members"); + + migrationBuilder.AddColumn( + name: "invited_by_id", + table: "chat_members", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_chat_members_invited_by_id", + table: "chat_members", + column: "invited_by_id"); + + migrationBuilder.AddForeignKey( + name: "fk_chat_members_chat_members_invited_by_id", + table: "chat_members", + column: "invited_by_id", + principalTable: "chat_members", + principalColumn: "id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_chat_members_chat_members_invited_by_id", + table: "chat_members"); + + migrationBuilder.DropIndex( + name: "ix_chat_members_invited_by_id", + table: "chat_members"); + + migrationBuilder.DropColumn( + name: "invited_by_id", + table: "chat_members"); + + migrationBuilder.AddColumn( + name: "is_bot", + table: "chat_members", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "role", + table: "chat_members", + type: "integer", + nullable: false, + defaultValue: 0); + } + } +} diff --git a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs index 256c626..9987a40 100644 --- a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs @@ -54,9 +54,9 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("deleted_at"); - b.Property("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); + b.Property("InvitedById") + .HasColumnType("uuid") + .HasColumnName("invited_by_id"); b.Property("JoinedAt") .HasColumnType("timestamp with time zone") @@ -79,10 +79,6 @@ namespace DysonNetwork.Sphere.Migrations .HasColumnType("integer") .HasColumnName("notify"); - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - b.Property("TimeoutCause") .HasColumnType("jsonb") .HasColumnName("timeout_cause"); @@ -101,6 +97,9 @@ namespace DysonNetwork.Sphere.Migrations b.HasAlternateKey("ChatRoomId", "AccountId") .HasName("ak_chat_members_chat_room_id_account_id"); + b.HasIndex("InvitedById") + .HasDatabaseName("ix_chat_members_invited_by_id"); + b.ToTable("chat_members", (string)null); }); @@ -1505,7 +1504,14 @@ namespace DysonNetwork.Sphere.Migrations .IsRequired() .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); + b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "InvitedBy") + .WithMany() + .HasForeignKey("InvitedById") + .HasConstraintName("fk_chat_members_chat_members_invited_by_id"); + b.Navigation("ChatRoom"); + + b.Navigation("InvitedBy"); }); modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b =>