♻️ Centralized data models (wip)
This commit is contained in:
@@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Content;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -33,7 +33,7 @@ public partial class ChatController(
|
||||
public class ChatSummaryResponse
|
||||
{
|
||||
public int UnreadCount { get; set; }
|
||||
public Message? LastMessage { get; set; }
|
||||
public SnChatMessage? LastMessage { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet("summary")]
|
||||
@@ -71,7 +71,7 @@ public partial class ChatController(
|
||||
}
|
||||
|
||||
[HttpGet("{roomId:guid}/messages")]
|
||||
public async Task<ActionResult<List<Message>>> ListMessages(Guid roomId, [FromQuery] int offset,
|
||||
public async Task<ActionResult<List<SnChatMessage>>> ListMessages(Guid roomId, [FromQuery] int offset,
|
||||
[FromQuery] int take = 20)
|
||||
{
|
||||
var currentUser = HttpContext.Items["CurrentUser"] as Account;
|
||||
@@ -114,7 +114,7 @@ public partial class ChatController(
|
||||
}
|
||||
|
||||
[HttpGet("{roomId:guid}/messages/{messageId:guid}")]
|
||||
public async Task<ActionResult<Message>> GetMessage(Guid roomId, Guid messageId)
|
||||
public async Task<ActionResult<SnChatMessage>> GetMessage(Guid roomId, Guid messageId)
|
||||
{
|
||||
var currentUser = HttpContext.Items["CurrentUser"] as Account;
|
||||
|
||||
@@ -165,7 +165,7 @@ public partial class ChatController(
|
||||
if (member == null || member.Role < ChatMemberRole.Member)
|
||||
return StatusCode(403, "You need to be a normal member to send messages here.");
|
||||
|
||||
var message = new Message
|
||||
var message = new SnChatMessage
|
||||
{
|
||||
Type = "text",
|
||||
SenderId = member.Id,
|
||||
@@ -182,7 +182,7 @@ public partial class ChatController(
|
||||
var queryResponse = await files.GetFileBatchAsync(queryRequest);
|
||||
message.Attachments = queryResponse.Files
|
||||
.OrderBy(f => request.AttachmentsId.IndexOf(f.Id))
|
||||
.Select(CloudFileReferenceObject.FromProtoValue)
|
||||
.Select(SnCloudFileReferenceObject.FromProtoValue)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
@@ -1,145 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
public enum ChatRoomType
|
||||
{
|
||||
Group,
|
||||
DirectMessage
|
||||
}
|
||||
|
||||
public class ChatRoom : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string? Name { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public ChatRoomType Type { get; set; }
|
||||
public bool IsCommunity { get; set; }
|
||||
public bool IsPublic { get; set; }
|
||||
|
||||
// Outdated fields, for backward compability
|
||||
[MaxLength(32)] public string? PictureId { get; set; }
|
||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
|
||||
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
|
||||
|
||||
[JsonIgnore] public ICollection<ChatMember> Members { get; set; } = new List<ChatMember>();
|
||||
|
||||
public Guid? RealmId { get; set; }
|
||||
public Realm.Realm? Realm { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonPropertyName("members")]
|
||||
public ICollection<ChatMemberTransmissionObject> DirectMembers { get; set; } =
|
||||
new List<ChatMemberTransmissionObject>();
|
||||
|
||||
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,
|
||||
Mentions,
|
||||
None
|
||||
}
|
||||
|
||||
public enum ChatTimeoutCauseType
|
||||
{
|
||||
ByModerator = 0,
|
||||
BySlowMode = 1,
|
||||
}
|
||||
|
||||
public class ChatTimeoutCause
|
||||
{
|
||||
public ChatTimeoutCauseType Type { get; set; }
|
||||
public Guid? SenderId { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMember : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ChatRoomId { get; set; }
|
||||
public ChatRoom ChatRoom { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
[NotMapped] public AccountReference? Account { get; set; }
|
||||
[NotMapped] public AccountStatusReference? Status { get; set; }
|
||||
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// The break time is the user doesn't receive any message from this member for a while.
|
||||
/// Expect mentioned him or her.
|
||||
/// </summary>
|
||||
public Instant? BreakUntil { get; set; }
|
||||
/// <summary>
|
||||
/// The timeout is the user can't send any message.
|
||||
/// Set by the moderator of the chat room.
|
||||
/// </summary>
|
||||
public Instant? TimeoutUntil { get; set; }
|
||||
/// <summary>
|
||||
/// The timeout cause is the reason why the user is timeout.
|
||||
/// </summary>
|
||||
[Column(TypeName = "jsonb")] public ChatTimeoutCause? TimeoutCause { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMemberTransmissionObject : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ChatRoomId { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
[NotMapped] public AccountReference Account { get; set; } = null!;
|
||||
|
||||
[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 Instant? BreakUntil { get; set; }
|
||||
public Instant? TimeoutUntil { get; set; }
|
||||
public ChatTimeoutCause? TimeoutCause { get; set; }
|
||||
|
||||
public static ChatMemberTransmissionObject FromEntity(ChatMember member)
|
||||
{
|
||||
return new ChatMemberTransmissionObject
|
||||
{
|
||||
Id = member.Id,
|
||||
ChatRoomId = member.ChatRoomId,
|
||||
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,
|
||||
CreatedAt = member.CreatedAt,
|
||||
UpdatedAt = member.UpdatedAt,
|
||||
DeletedAt = member.DeletedAt
|
||||
};
|
||||
}
|
||||
}
|
@@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Shared;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using DysonNetwork.Sphere.Localization;
|
||||
@@ -12,6 +11,7 @@ using Grpc.Core;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
using DysonNetwork.Shared.Models;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class ChatRoomController(
|
||||
) : ControllerBase
|
||||
{
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ActionResult<ChatRoom>> GetChatRoom(Guid id)
|
||||
public async Task<ActionResult<SnChatRoom>> GetChatRoom(Guid id)
|
||||
{
|
||||
var chatRoom = await db.ChatRooms
|
||||
.Where(c => c.Id == id)
|
||||
@@ -48,7 +48,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<ChatRoom>>> ListJoinedChatRooms()
|
||||
public async Task<ActionResult<List<SnChatRoom>>> ListJoinedChatRooms()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
@@ -74,7 +74,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpPost("direct")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatRoom>> CreateDirectMessage([FromBody] DirectMessageRequest request)
|
||||
public async Task<ActionResult<SnChatRoom>> CreateDirectMessage([FromBody] DirectMessageRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
@@ -106,11 +106,11 @@ public class ChatRoomController(
|
||||
return BadRequest("You already have a DM with this user.");
|
||||
|
||||
// Create new DM chat room
|
||||
var dmRoom = new ChatRoom
|
||||
var dmRoom = new SnChatRoom
|
||||
{
|
||||
Type = ChatRoomType.DirectMessage,
|
||||
IsPublic = false,
|
||||
Members = new List<ChatMember>
|
||||
Members = new List<SnChatMember>
|
||||
{
|
||||
new()
|
||||
{
|
||||
@@ -148,7 +148,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpGet("direct/{accountId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatRoom>> GetDirectChatRoom(Guid accountId)
|
||||
public async Task<ActionResult<SnChatRoom>> GetDirectChatRoom(Guid accountId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
@@ -178,19 +178,19 @@ public class ChatRoomController(
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
[RequiredPermission("global", "chat.create")]
|
||||
public async Task<ActionResult<ChatRoom>> CreateChatRoom(ChatRoomRequest request)
|
||||
public async Task<ActionResult<SnChatRoom>> CreateChatRoom(ChatRoomRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized();
|
||||
if (request.Name is null) return BadRequest("You cannot create a chat room without a name.");
|
||||
|
||||
var chatRoom = new ChatRoom
|
||||
var chatRoom = new SnChatRoom
|
||||
{
|
||||
Name = request.Name,
|
||||
Description = request.Description ?? string.Empty,
|
||||
IsCommunity = request.IsCommunity ?? false,
|
||||
IsPublic = request.IsPublic ?? false,
|
||||
Type = ChatRoomType.Group,
|
||||
Members = new List<ChatMember>
|
||||
Members = new List<SnChatMember>
|
||||
{
|
||||
new()
|
||||
{
|
||||
@@ -215,7 +215,7 @@ public class ChatRoomController(
|
||||
{
|
||||
var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId });
|
||||
if (fileResponse == null) return BadRequest("Invalid picture id, unable to find the file on cloud.");
|
||||
chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
chatRoom.Picture = SnCloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
|
||||
await fileRefs.CreateReferenceAsync(new CreateReferenceRequest
|
||||
{
|
||||
@@ -236,7 +236,7 @@ public class ChatRoomController(
|
||||
{
|
||||
var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId });
|
||||
if (fileResponse == null) return BadRequest("Invalid background id, unable to find the file on cloud.");
|
||||
chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
chatRoom.Background = SnCloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
|
||||
await fileRefs.CreateReferenceAsync(new CreateReferenceRequest
|
||||
{
|
||||
@@ -290,7 +290,7 @@ public class ChatRoomController(
|
||||
|
||||
|
||||
[HttpPatch("{id:guid}")]
|
||||
public async Task<ActionResult<ChatRoom>> UpdateChatRoom(Guid id, [FromBody] ChatRoomRequest request)
|
||||
public async Task<ActionResult<SnChatRoom>> UpdateChatRoom(Guid id, [FromBody] ChatRoomRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized();
|
||||
|
||||
@@ -341,7 +341,7 @@ public class ChatRoomController(
|
||||
ResourceId = chatRoom.ResourceIdentifier
|
||||
});
|
||||
|
||||
chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
chatRoom.Picture = SnCloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
}
|
||||
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
|
||||
{
|
||||
@@ -371,7 +371,7 @@ public class ChatRoomController(
|
||||
ResourceId = chatRoom.ResourceIdentifier
|
||||
});
|
||||
|
||||
chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
chatRoom.Background = SnCloudFileReferenceObject.FromProtoValue(fileResponse);
|
||||
}
|
||||
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
|
||||
{
|
||||
@@ -468,7 +468,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpGet("{roomId:guid}/members/me")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatMember>> GetRoomIdentity(Guid roomId)
|
||||
public async Task<ActionResult<SnChatMember>> GetRoomIdentity(Guid roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser)
|
||||
return Unauthorized();
|
||||
@@ -484,7 +484,7 @@ public class ChatRoomController(
|
||||
}
|
||||
|
||||
[HttpGet("{roomId:guid}/members")]
|
||||
public async Task<ActionResult<List<ChatMember>>> ListMembers(Guid roomId,
|
||||
public async Task<ActionResult<List<SnChatMember>>> ListMembers(Guid roomId,
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery] int offset = 0,
|
||||
[FromQuery] bool withStatus = false
|
||||
@@ -561,7 +561,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpPost("invites/{roomId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatMember>> InviteMember(Guid roomId,
|
||||
public async Task<ActionResult<SnChatMember>> InviteMember(Guid roomId,
|
||||
[FromBody] ChatMemberRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
@@ -620,7 +620,7 @@ public class ChatRoomController(
|
||||
if (hasExistingMember)
|
||||
return BadRequest("This user has been joined the chat cannot be invited again.");
|
||||
|
||||
var newMember = new ChatMember
|
||||
var newMember = new SnChatMember
|
||||
{
|
||||
AccountId = Guid.Parse(relatedUser.Id),
|
||||
ChatRoomId = roomId,
|
||||
@@ -651,7 +651,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpGet("invites")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<ChatMember>>> ListChatInvites()
|
||||
public async Task<ActionResult<List<SnChatMember>>> ListChatInvites()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized();
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
@@ -674,7 +674,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpPost("invites/{roomId:guid}/accept")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatRoom>> AcceptChatInvite(Guid roomId)
|
||||
public async Task<ActionResult<SnChatRoom>> AcceptChatInvite(Guid roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
@@ -731,7 +731,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpPatch("{roomId:guid}/members/me/notify")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatMember>> UpdateChatMemberNotify(
|
||||
public async Task<ActionResult<SnChatMember>> UpdateChatMemberNotify(
|
||||
Guid roomId,
|
||||
[FromBody] ChatMemberNotifyRequest request
|
||||
)
|
||||
@@ -763,7 +763,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpPatch("{roomId:guid}/members/{memberId:guid}/role")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatMember>> UpdateChatMemberRole(Guid roomId, Guid memberId, [FromBody] int newRole)
|
||||
public async Task<ActionResult<SnChatMember>> 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();
|
||||
@@ -885,7 +885,7 @@ public class ChatRoomController(
|
||||
|
||||
[HttpPost("{roomId:guid}/members/me")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<ChatRoom>> JoinChatRoom(Guid roomId)
|
||||
public async Task<ActionResult<SnChatRoom>> JoinChatRoom(Guid roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
|
||||
@@ -901,7 +901,7 @@ public class ChatRoomController(
|
||||
if (existingMember != null)
|
||||
return BadRequest("You are already a member of this chat room.");
|
||||
|
||||
var newMember = new ChatMember
|
||||
var newMember = new SnChatMember
|
||||
{
|
||||
AccountId = Guid.Parse(currentUser.Id),
|
||||
ChatRoomId = roomId,
|
||||
@@ -966,7 +966,7 @@ public class ChatRoomController(
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task _SendInviteNotify(ChatMember member, Account sender)
|
||||
private async Task _SendInviteNotify(SnChatMember member, Account sender)
|
||||
{
|
||||
var account = await accounts.GetAccountAsync(new GetAccountRequest { Id = member.AccountId.ToString() });
|
||||
CultureService.SetCultureInfo(account);
|
||||
|
@@ -1,8 +1,9 @@
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using Account = DysonNetwork.Shared.Data.AccountReference;
|
||||
using Account = DysonNetwork.Shared.Data.SnAccount;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
@@ -16,10 +17,10 @@ public class ChatRoomService(
|
||||
private const string RoomMembersCacheKeyPrefix = "chatroom:members:";
|
||||
private const string ChatMemberCacheKey = "chatroom:{0}:member:{1}";
|
||||
|
||||
public async Task<List<ChatMember>> ListRoomMembers(Guid roomId)
|
||||
public async Task<List<SnChatMember>> ListRoomMembers(Guid roomId)
|
||||
{
|
||||
var cacheKey = RoomMembersCacheKeyPrefix + roomId;
|
||||
var cachedMembers = await cache.GetAsync<List<ChatMember>>(cacheKey);
|
||||
var cachedMembers = await cache.GetAsync<List<SnChatMember>>(cacheKey);
|
||||
if (cachedMembers != null)
|
||||
return cachedMembers;
|
||||
|
||||
@@ -38,10 +39,10 @@ public class ChatRoomService(
|
||||
return members;
|
||||
}
|
||||
|
||||
public async Task<ChatMember?> GetRoomMember(Guid accountId, Guid chatRoomId)
|
||||
public async Task<SnChatMember?> GetRoomMember(Guid accountId, Guid chatRoomId)
|
||||
{
|
||||
var cacheKey = string.Format(ChatMemberCacheKey, accountId, chatRoomId);
|
||||
var member = await cache.GetAsync<ChatMember?>(cacheKey);
|
||||
var member = await cache.GetAsync<SnChatMember?>(cacheKey);
|
||||
if (member is not null) return member;
|
||||
|
||||
member = await db.ChatMembers
|
||||
@@ -66,7 +67,7 @@ public class ChatRoomService(
|
||||
await cache.RemoveGroupAsync(chatRoomGroup);
|
||||
}
|
||||
|
||||
public async Task<List<ChatRoom>> SortChatRoomByLastMessage(List<ChatRoom> rooms)
|
||||
public async Task<List<SnChatRoom>> SortChatRoomByLastMessage(List<SnChatRoom> rooms)
|
||||
{
|
||||
var roomIds = rooms.Select(r => r.Id).ToList();
|
||||
var lastMessages = await db.ChatMessages
|
||||
@@ -83,7 +84,7 @@ public class ChatRoomService(
|
||||
return sortedRooms;
|
||||
}
|
||||
|
||||
public async Task<List<ChatRoom>> LoadDirectMessageMembers(List<ChatRoom> rooms, Guid userId)
|
||||
public async Task<List<SnChatRoom>> LoadDirectMessageMembers(List<SnChatRoom> rooms, Guid userId)
|
||||
{
|
||||
var directRoomsId = rooms
|
||||
.Where(r => r.Type == ChatRoomType.DirectMessage)
|
||||
@@ -91,7 +92,7 @@ public class ChatRoomService(
|
||||
.ToList();
|
||||
if (directRoomsId.Count == 0) return rooms;
|
||||
|
||||
List<ChatMember> members = directRoomsId.Count != 0
|
||||
List<SnChatMember> members = directRoomsId.Count != 0
|
||||
? await db.ChatMembers
|
||||
.Where(m => directRoomsId.Contains(m.ChatRoomId))
|
||||
.Where(m => m.AccountId != userId)
|
||||
@@ -100,7 +101,7 @@ public class ChatRoomService(
|
||||
: [];
|
||||
members = await LoadMemberAccounts(members);
|
||||
|
||||
Dictionary<Guid, List<ChatMember>> directMembers = new();
|
||||
Dictionary<Guid, List<SnChatMember>> directMembers = new();
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (!directMembers.ContainsKey(member.ChatRoomId))
|
||||
@@ -116,7 +117,7 @@ public class ChatRoomService(
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<ChatRoom> LoadDirectMessageMembers(ChatRoom room, Guid userId)
|
||||
public async Task<SnChatRoom> LoadDirectMessageMembers(SnChatRoom room, Guid userId)
|
||||
{
|
||||
if (room.Type != ChatRoomType.DirectMessage) return room;
|
||||
var members = await db.ChatMembers
|
||||
@@ -143,14 +144,14 @@ public class ChatRoomService(
|
||||
return member?.Role >= maxRequiredRole;
|
||||
}
|
||||
|
||||
public async Task<ChatMember> LoadMemberAccount(ChatMember member)
|
||||
public async Task<SnChatMember> LoadMemberAccount(SnChatMember member)
|
||||
{
|
||||
var account = await accountsHelper.GetAccount(member.AccountId);
|
||||
member.Account = Account.FromProtoValue(account);
|
||||
return member;
|
||||
}
|
||||
|
||||
public async Task<List<ChatMember>> LoadMemberAccounts(ICollection<ChatMember> members)
|
||||
public async Task<List<SnChatMember>> LoadMemberAccounts(ICollection<SnChatMember> members)
|
||||
{
|
||||
var accountIds = members.Select(m => m.AccountId).ToList();
|
||||
var accounts = (await accountsHelper.GetAccountBatch(accountIds)).ToDictionary(a => Guid.Parse(a.Id), a => a);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Sphere.Chat.Realtime;
|
||||
using DysonNetwork.Sphere.WebReader;
|
||||
@@ -29,7 +29,7 @@ public partial class ChatService(
|
||||
/// This method is designed to be called from a background task
|
||||
/// </summary>
|
||||
/// <param name="message">The message to process link previews for</param>
|
||||
private async Task ProcessMessageLinkPreviewAsync(Message message)
|
||||
private async Task ProcessMessageLinkPreviewAsync(SnChatMessage message)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -66,7 +66,7 @@ public partial class ChatService(
|
||||
logger.LogDebug($"Updated message {message.Id} with {embedsList.Count} link previews");
|
||||
|
||||
// Create and store sync message for link preview update
|
||||
var syncMessage = new Message
|
||||
var syncMessage = new SnChatMessage
|
||||
{
|
||||
Type = "messages.update.links",
|
||||
ChatRoomId = dbMessage.ChatRoomId,
|
||||
@@ -114,7 +114,7 @@ public partial class ChatService(
|
||||
/// <param name="message">The message to process</param>
|
||||
/// <param name="webReader">The web reader service</param>
|
||||
/// <returns>The message with link previews added to its meta data</returns>
|
||||
public async Task<Message> PreviewMessageLinkAsync(Message message, WebReaderService? webReader = null)
|
||||
public async Task<SnChatMessage> PreviewMessageLinkAsync(SnChatMessage message, WebReaderService? webReader = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message.Content))
|
||||
return message;
|
||||
@@ -172,9 +172,9 @@ public partial class ChatService(
|
||||
}
|
||||
|
||||
private async Task DeliverWebSocketMessage(
|
||||
Message message,
|
||||
SnChatMessage message,
|
||||
string type,
|
||||
List<ChatMember> members,
|
||||
List<SnChatMember> members,
|
||||
IServiceScope scope
|
||||
)
|
||||
{
|
||||
@@ -195,7 +195,7 @@ public partial class ChatService(
|
||||
logger.LogInformation($"Delivered message to {request.UserIds.Count} accounts.");
|
||||
}
|
||||
|
||||
public async Task<Message> SendMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||
public async Task<SnChatMessage> SendMessageAsync(SnChatMessage message, SnChatMember sender, SnChatRoom room)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message.Nonce)) message.Nonce = Guid.NewGuid().ToString();
|
||||
message.CreatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
@@ -230,9 +230,9 @@ public partial class ChatService(
|
||||
}
|
||||
|
||||
private async Task DeliverMessageAsync(
|
||||
Message message,
|
||||
ChatMember sender,
|
||||
ChatRoom room,
|
||||
SnChatMessage message,
|
||||
SnChatMember sender,
|
||||
SnChatRoom room,
|
||||
string type = WebSocketPacketType.MessageNew,
|
||||
bool notify = true
|
||||
)
|
||||
@@ -254,11 +254,11 @@ public partial class ChatService(
|
||||
}
|
||||
|
||||
private async Task SendPushNotificationsAsync(
|
||||
Message message,
|
||||
ChatMember sender,
|
||||
ChatRoom room,
|
||||
SnChatMessage message,
|
||||
SnChatMember sender,
|
||||
SnChatRoom room,
|
||||
string type,
|
||||
List<ChatMember> members,
|
||||
List<SnChatMember> members,
|
||||
IServiceScope scope
|
||||
)
|
||||
{
|
||||
@@ -292,7 +292,7 @@ public partial class ChatService(
|
||||
logger.LogInformation($"Delivered message to {accountsToNotify.Count} accounts.");
|
||||
}
|
||||
|
||||
private PushNotification BuildNotification(Message message, ChatMember sender, ChatRoom room, string roomSubject,
|
||||
private PushNotification BuildNotification(SnChatMessage message, SnChatMember sender, SnChatRoom room, string roomSubject,
|
||||
string type)
|
||||
{
|
||||
var metaDict = new Dictionary<string, object>
|
||||
@@ -325,7 +325,7 @@ public partial class ChatService(
|
||||
return notification;
|
||||
}
|
||||
|
||||
private string BuildNotificationBody(Message message, string type)
|
||||
private string BuildNotificationBody(SnChatMessage message, string type)
|
||||
{
|
||||
if (message.DeletedAt is not null)
|
||||
return "Deleted a message";
|
||||
@@ -356,7 +356,7 @@ public partial class ChatService(
|
||||
}
|
||||
}
|
||||
|
||||
private List<Account> FilterAccountsForNotification(List<ChatMember> members, Message message, ChatMember sender)
|
||||
private List<Account> FilterAccountsForNotification(List<SnChatMember> members, SnChatMessage message, SnChatMember sender)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
@@ -377,7 +377,7 @@ public partial class ChatService(
|
||||
return accountsToNotify.Where(a => a.Id != sender.AccountId.ToString()).ToList();
|
||||
}
|
||||
|
||||
private async Task CreateFileReferencesForMessageAsync(Message message)
|
||||
private async Task CreateFileReferencesForMessageAsync(SnChatMessage message)
|
||||
{
|
||||
var files = message.Attachments.Distinct().ToList();
|
||||
if (files.Count == 0) return;
|
||||
@@ -391,7 +391,7 @@ public partial class ChatService(
|
||||
await fileRefs.CreateReferenceBatchAsync(request);
|
||||
}
|
||||
|
||||
private async Task UpdateFileReferencesForMessageAsync(Message message, List<string> attachmentsId)
|
||||
private async Task UpdateFileReferencesForMessageAsync(SnChatMessage message, List<string> attachmentsId)
|
||||
{
|
||||
// Delete existing references for this message
|
||||
await fileRefs.DeleteResourceReferencesAsync(
|
||||
@@ -411,10 +411,10 @@ public partial class ChatService(
|
||||
var queryRequest = new GetFileBatchRequest();
|
||||
queryRequest.Ids.AddRange(attachmentsId);
|
||||
var queryResult = await filesClient.GetFileBatchAsync(queryRequest);
|
||||
message.Attachments = queryResult.Files.Select(CloudFileReferenceObject.FromProtoValue).ToList();
|
||||
message.Attachments = queryResult.Files.Select(SnCloudFileReferenceObject.FromProtoValue).ToList();
|
||||
}
|
||||
|
||||
private async Task DeleteFileReferencesForMessageAsync(Message message)
|
||||
private async Task DeleteFileReferencesForMessageAsync(SnChatMessage message)
|
||||
{
|
||||
var messageResourceId = $"message:{message.Id}";
|
||||
await fileRefs.DeleteResourceReferencesAsync(
|
||||
@@ -474,7 +474,7 @@ public partial class ChatService(
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, Message?>> ListLastMessageForUser(Guid userId)
|
||||
public async Task<Dictionary<Guid, SnChatMessage?>> ListLastMessageForUser(Guid userId)
|
||||
{
|
||||
var userRooms = await db.ChatMembers
|
||||
.Where(m => m.LeaveAt == null && m.JoinedAt != null)
|
||||
@@ -517,9 +517,9 @@ public partial class ChatService(
|
||||
return messages;
|
||||
}
|
||||
|
||||
public async Task<RealtimeCall> CreateCallAsync(ChatRoom room, ChatMember sender)
|
||||
public async Task<SnRealtimeCall> CreateCallAsync(SnChatRoom room, SnChatMember sender)
|
||||
{
|
||||
var call = new RealtimeCall
|
||||
var call = new SnRealtimeCall
|
||||
{
|
||||
RoomId = room.Id,
|
||||
SenderId = sender.Id,
|
||||
@@ -547,7 +547,7 @@ public partial class ChatService(
|
||||
db.ChatRealtimeCall.Add(call);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await SendMessageAsync(new Message
|
||||
await SendMessageAsync(new SnChatMessage
|
||||
{
|
||||
Type = "call.start",
|
||||
ChatRoomId = room.Id,
|
||||
@@ -561,7 +561,7 @@ public partial class ChatService(
|
||||
return call;
|
||||
}
|
||||
|
||||
public async Task EndCallAsync(Guid roomId, ChatMember sender)
|
||||
public async Task EndCallAsync(Guid roomId, SnChatMember sender)
|
||||
{
|
||||
var call = await GetCallOngoingAsync(roomId);
|
||||
if (call is null) throw new InvalidOperationException("No ongoing call was not found.");
|
||||
@@ -592,7 +592,7 @@ public partial class ChatService(
|
||||
db.ChatRealtimeCall.Update(call);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await SendMessageAsync(new Message
|
||||
await SendMessageAsync(new SnChatMessage
|
||||
{
|
||||
Type = "call.ended",
|
||||
ChatRoomId = call.RoomId,
|
||||
@@ -605,7 +605,7 @@ public partial class ChatService(
|
||||
}, call.Sender, call.Room);
|
||||
}
|
||||
|
||||
public async Task<RealtimeCall?> GetCallOngoingAsync(Guid roomId)
|
||||
public async Task<SnRealtimeCall?> GetCallOngoingAsync(Guid roomId)
|
||||
{
|
||||
return await db.ChatRealtimeCall
|
||||
.Where(c => c.RoomId == roomId)
|
||||
@@ -660,8 +660,8 @@ public partial class ChatService(
|
||||
}
|
||||
|
||||
|
||||
public async Task<Message> UpdateMessageAsync(
|
||||
Message message,
|
||||
public async Task<SnChatMessage> UpdateMessageAsync(
|
||||
SnChatMessage message,
|
||||
Dictionary<string, object>? meta = null,
|
||||
string? content = null,
|
||||
Guid? repliedMessageId = null,
|
||||
@@ -705,7 +705,7 @@ public partial class ChatService(
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Create and store sync message for the update
|
||||
var syncMessage = new Message
|
||||
var syncMessage = new SnChatMessage
|
||||
{
|
||||
Type = "messages.update",
|
||||
ChatRoomId = message.ChatRoomId,
|
||||
@@ -751,7 +751,7 @@ public partial class ChatService(
|
||||
/// Soft deletes a message and notifies other chat members
|
||||
/// </summary>
|
||||
/// <param name="message">The message to delete</param>
|
||||
public async Task DeleteMessageAsync(Message message)
|
||||
public async Task DeleteMessageAsync(SnChatMessage message)
|
||||
{
|
||||
// Only allow deleting regular text messages
|
||||
if (message.Type != "text")
|
||||
@@ -770,7 +770,7 @@ public partial class ChatService(
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Create and store sync message for the deletion
|
||||
var syncMessage = new Message
|
||||
var syncMessage = new SnChatMessage
|
||||
{
|
||||
Type = "messages.delete",
|
||||
ChatRoomId = message.ChatRoomId,
|
||||
@@ -805,6 +805,6 @@ public partial class ChatService(
|
||||
|
||||
public class SyncResponse
|
||||
{
|
||||
public List<Message> Messages { get; set; } = [];
|
||||
public List<SnChatMessage> Messages { get; set; } = [];
|
||||
public Instant CurrentTimestamp { get; set; }
|
||||
}
|
||||
|
@@ -1,80 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
public class Message : ModelBase, IIdentifiedResource
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(1024)] public string Type { get; set; } = null!;
|
||||
[MaxLength(4096)] public string? Content { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
[Column(TypeName = "jsonb")] public List<Guid>? MembersMentioned { get; set; }
|
||||
[MaxLength(36)] public string Nonce { get; set; } = null!;
|
||||
public Instant? EditedAt { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb")] public List<CloudFileReferenceObject> Attachments { get; set; } = [];
|
||||
|
||||
public ICollection<MessageReaction> Reactions { get; set; } = new List<MessageReaction>();
|
||||
|
||||
public Guid? RepliedMessageId { get; set; }
|
||||
public Message? RepliedMessage { get; set; }
|
||||
public Guid? ForwardedMessageId { get; set; }
|
||||
public Message? ForwardedMessage { get; set; }
|
||||
|
||||
public Guid SenderId { get; set; }
|
||||
public ChatMember Sender { get; set; } = null!;
|
||||
public Guid ChatRoomId { get; set; }
|
||||
[JsonIgnore] public ChatRoom ChatRoom { get; set; } = null!;
|
||||
|
||||
public string ResourceIdentifier => $"message:{Id}";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow clone of this message for sync operations
|
||||
/// </summary>
|
||||
/// <returns>A new Message instance with copied properties</returns>
|
||||
public Message Clone()
|
||||
{
|
||||
return new Message
|
||||
{
|
||||
Id = Id,
|
||||
Type = Type,
|
||||
Content = Content,
|
||||
Meta = Meta,
|
||||
MembersMentioned = MembersMentioned,
|
||||
Nonce = Nonce,
|
||||
EditedAt = EditedAt,
|
||||
Attachments = Attachments,
|
||||
RepliedMessageId = RepliedMessageId,
|
||||
ForwardedMessageId = ForwardedMessageId,
|
||||
SenderId = SenderId,
|
||||
Sender = Sender,
|
||||
ChatRoomId = ChatRoomId,
|
||||
ChatRoom = ChatRoom,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageReactionAttitude
|
||||
{
|
||||
Positive,
|
||||
Neutral,
|
||||
Negative,
|
||||
}
|
||||
|
||||
public class MessageReaction : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid MessageId { get; set; }
|
||||
[JsonIgnore] public Message Message { get; set; } = null!;
|
||||
public Guid SenderId { get; set; }
|
||||
public ChatMember Sender { get; set; } = null!;
|
||||
|
||||
[MaxLength(256)] public string Symbol { get; set; } = null!;
|
||||
public MessageReactionAttitude Attitude { get; set; }
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Sphere.Chat.Realtime;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
public class RealtimeCall : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Instant? EndedAt { get; set; }
|
||||
|
||||
public Guid SenderId { get; set; }
|
||||
public ChatMember Sender { get; set; } = null!;
|
||||
public Guid RoomId { get; set; }
|
||||
public ChatRoom Room { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Provider name (e.g., "cloudflare", "agora", "twilio")
|
||||
/// </summary>
|
||||
public string? ProviderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Service provider's session identifier
|
||||
/// </summary>
|
||||
public string? SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// JSONB column containing provider-specific configuration
|
||||
/// </summary>
|
||||
[Column(name: "upstream", TypeName = "jsonb")]
|
||||
public string? UpstreamConfigJson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deserialized upstream configuration
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public Dictionary<string, object> UpstreamConfig
|
||||
{
|
||||
get => string.IsNullOrEmpty(UpstreamConfigJson)
|
||||
? new Dictionary<string, object>()
|
||||
: JsonSerializer.Deserialize<Dictionary<string, object>>(UpstreamConfigJson) ?? new Dictionary<string, object>();
|
||||
set => UpstreamConfigJson = value.Count > 0
|
||||
? JsonSerializer.Serialize(value)
|
||||
: null;
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Sphere.Chat.Realtime;
|
||||
using Livekit.Server.Sdk.Dotnet;
|
||||
@@ -46,7 +47,7 @@ public class RealtimeCallController(
|
||||
|
||||
[HttpGet("{roomId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<RealtimeCall>> GetOngoingCall(Guid roomId)
|
||||
public async Task<ActionResult<SnRealtimeCall>> GetOngoingCall(Guid roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
|
||||
@@ -145,7 +146,7 @@ public class RealtimeCallController(
|
||||
|
||||
[HttpPost("{roomId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<RealtimeCall>> StartCall(Guid roomId)
|
||||
public async Task<ActionResult<SnRealtimeCall>> StartCall(Guid roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
|
||||
@@ -165,7 +166,7 @@ public class RealtimeCallController(
|
||||
|
||||
[HttpDelete("{roomId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<RealtimeCall>> EndCall(Guid roomId)
|
||||
public async Task<ActionResult<SnRealtimeCall>> EndCall(Guid roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
|
||||
@@ -250,7 +251,7 @@ public class CallParticipant
|
||||
/// <summary>
|
||||
/// The participant's profile in the chat
|
||||
/// </summary>
|
||||
public ChatMember? Profile { get; set; }
|
||||
public SnChatMember? Profile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the participant joined the call
|
||||
|
Reference in New Issue
Block a user