diff --git a/backend/src/Contracts/Messaging/Domain/Media.cs b/backend/src/Contracts/Messaging/Domain/Media.cs new file mode 100644 index 0000000..4e4ea69 --- /dev/null +++ b/backend/src/Contracts/Messaging/Domain/Media.cs @@ -0,0 +1,17 @@ +using System; + +namespace Knot.Contracts.Messaging.Domain; + +public class Media +{ + public Guid Id { get; set; } + public string Type { get; set; } = string.Empty; + public string? Url { get; set; } + public string? ThumbnailUrl { get; set; } + public long? Size { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public string? FileId { get; set; } + public string? Filename { get; set; } + public string? Duration { get; set; } +} diff --git a/backend/src/Contracts/Messaging/Domain/MediaMessage.cs b/backend/src/Contracts/Messaging/Domain/MediaMessage.cs new file mode 100644 index 0000000..c1e3c70 --- /dev/null +++ b/backend/src/Contracts/Messaging/Domain/MediaMessage.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace Knot.Contracts.Messaging.Domain; + +public class MediaMessage : Message +{ + public override string Type => MediaType.ToString().ToLower(); + public override string? Content { get; protected set; } + public string? Caption { get => Content; private set => Content = value; } + public MediaType MediaType { get; private set; } + + private List _media = new(); + public IReadOnlyCollection Media => _media.AsReadOnly(); + + private MediaMessage() : base() { MediaType = MediaType.File; } + + public MediaMessage(Guid id, Guid chatId, Guid senderId, MediaType mediaType, string? caption, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) + : base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) { MediaType = mediaType; Caption = caption; } + + public MediaMessage(Guid id, Guid chatId, Guid senderId, string mediaType, string? caption, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) + : this(id, chatId, senderId, Enum.TryParse(mediaType, true, out var mt) ? mt : MediaType.File, caption, replyToId, forwardedFromId, createdAt, isImported) { } + + public void AddMedia(string type, string url, string? filename, long? size) => _media.Add(new Media { Type = type, Url = url, FileId = filename, Size = size }); + + public override void Edit(string newCaption) => base.Edit(newCaption); + + public override void Delete() { Caption = null; base.Delete(); } +} diff --git a/backend/src/Contracts/Messaging/Domain/Message.cs b/backend/src/Contracts/Messaging/Domain/Message.cs index ba63ae2..d624c46 100644 --- a/backend/src/Contracts/Messaging/Domain/Message.cs +++ b/backend/src/Contracts/Messaging/Domain/Message.cs @@ -16,18 +16,17 @@ public abstract class Message : AggregateRoot public MessageState State { get; protected set; } public abstract string Type { get; } public abstract string? Content { get; protected set; } - public virtual string? Quote { get; protected set; } = null; - public virtual Guid? StoryId => null; - public virtual string? StoryMediaUrl => null; - public virtual string? StoryMediaType => null; - public virtual IReadOnlyCollection Media => Array.Empty(); + public bool IsEdited => HasState(MessageState.IsEdited); public bool IsDeleted => HasState(MessageState.IsDeleted); - public bool IsDeletedForUser(Guid userId) => _deletedFor.Exists(d => d.UserId == userId); + protected List _deletedFor = new(); public IReadOnlyCollection DeletedFor => _deletedFor.AsReadOnly(); + protected Message() : base(Guid.Empty) { } - protected Message(Guid id, Guid chatId, Guid senderId, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) : base(id) + + protected Message(Guid id, Guid chatId, Guid senderId, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) + : base(id) { ChatId = chatId; SenderId = senderId; @@ -36,91 +35,22 @@ public abstract class Message : AggregateRoot CreatedAt = createdAt; if (isImported) AddState(MessageState.IsImported); } + public void AddState(MessageState state) => State |= state; public void RemoveState(MessageState state) => State &= ~state; public bool HasState(MessageState state) => (State & state) == state; + public bool IsDeletedForUser(Guid userId) => _deletedFor.Exists(d => d.UserId == userId); + public virtual void Delete() => AddState(MessageState.IsDeleted); - public virtual void Edit(string newContent) { Content = newContent; AddState(MessageState.IsEdited); } - public void DeleteForUser(Guid userId) { if (!_deletedFor.Exists(x => x.UserId == userId)) _deletedFor.Add(new DeletedMessage(Id, userId)); } -} + public virtual void Edit(string newContent) + { + Content = newContent; + AddState(MessageState.IsEdited); + } -public class Media -{ - public Guid Id { get; set; } - public string Type { get; set; } = string.Empty; - public string? Url { get; set; } - public string? ThumbnailUrl { get; set; } - public long? Size { get; set; } - public int Width { get; set; } - public int Height { get; set; } - public string? FileId { get; set; } - public string? Filename { get; set; } - public string? Duration { get; set; } -} - -public class TextMessage : Message -{ - public override string Type => "text"; - public override string? Content { get; protected set; } - public TextMessage() : base() { } - public TextMessage(Guid id, Guid chatId, Guid senderId, string content, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported = false) - : base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) => Content = content; - public TextMessage(Guid id, Guid chatId, Guid senderId, string content, Guid? replyToId, string? quote, Guid? forwardedFromId, DateTime createdAt, bool isImported = false) - : this(id, chatId, senderId, content, replyToId, forwardedFromId, createdAt, isImported) => Quote = quote; -} - -public class MediaMessage : Message -{ - public override string Type => MediaType.ToString().ToLower(); - public override string? Content { get; protected set; } - public string? Caption { get => Content; private set => Content = value; } - public MediaType MediaType { get; private set; } - private List _media = new(); - public override IReadOnlyCollection Media => _media.AsReadOnly(); - private MediaMessage() : base() { MediaType = MediaType.File; } - public MediaMessage(Guid id, Guid chatId, Guid senderId, MediaType mediaType, string? caption, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) - : base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) { MediaType = mediaType; Caption = caption; } - public MediaMessage(Guid id, Guid chatId, Guid senderId, string mediaType, string? caption, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) - : this(id, chatId, senderId, Enum.TryParse(mediaType, true, out var mt) ? mt : MediaType.File, caption, replyToId, forwardedFromId, createdAt, isImported) { } - public void AddMedia(string type, string url, string? filename, long? size) => _media.Add(new Media { Type = type, Url = url, FileId = filename, Size = size }); - public override void Edit(string newCaption) => base.Edit(newCaption); - public override void Delete() { Caption = null; base.Delete(); } -} - -public class StoryMessage : Message -{ - public override string Type => "story"; - public override string? Content { get; protected set; } - public override Guid? StoryId { get; } - public string? InternalStoryMediaUrl { get; private set; } - public override string? StoryMediaUrl => InternalStoryMediaUrl; - public override string? StoryMediaType { get; } - public StoryMessage() : base() { } - public StoryMessage(Guid id, Guid chatId, Guid senderId, Guid storyId, string? storyMediaUrl, string? storyMediaType, DateTime createdAt) - : base(id, chatId, senderId, null, null, createdAt, false) { StoryId = storyId; InternalStoryMediaUrl = storyMediaUrl; StoryMediaType = storyMediaType; } - public StoryMessage(Guid id, Guid chatId, Guid senderId, Guid storyId, string? storyMediaUrl, string? storyMediaType, string? content, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) - : this(id, chatId, senderId, storyId, storyMediaUrl, storyMediaType, createdAt) { Content = content; ReplyToId = replyToId; ForwardedFromId = forwardedFromId; if (isImported) AddState(MessageState.IsImported); } -} - -public class PollMessage : Message -{ - public override string Type => "poll"; - public override string? Content { get; protected set; } - public List Options { get; } = new(); - public List Votes { get; } = new(); - public bool IsMultipleChoice { get; set; } - public DateTime? ExpiresAt { get; set; } - public bool IsClosed { get; set; } - public PollMessage() : base() { } - public PollMessage(Guid id, Guid chatId, Guid senderId, string? question, List? options, bool isAnonymous, bool isMultiple, DateTime? expiresAt, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) - : base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) - { - Content = question ?? "Poll"; - if (options != null) foreach (var opt in options) Options.Add(new PollOption { Text = opt }); - IsMultipleChoice = isMultiple; - ExpiresAt = expiresAt; + public void DeleteForUser(Guid userId) + { + if (!_deletedFor.Exists(x => x.UserId == userId)) + _deletedFor.Add(new DeletedMessage(Id, userId)); } } - -public class PollOption { public string Text { get; set; } = string.Empty; public int VoteCount { get; set; } } -public class PollVote { public Guid OptionIndex { get; set; } public Guid UserId { get; set; } public DateTime VotedAt { get; set; } } diff --git a/backend/src/Contracts/Messaging/Domain/PollMessage.cs b/backend/src/Contracts/Messaging/Domain/PollMessage.cs new file mode 100644 index 0000000..1785984 --- /dev/null +++ b/backend/src/Contracts/Messaging/Domain/PollMessage.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace Knot.Contracts.Messaging.Domain; + +public class PollMessage : Message +{ + public override string Type => "poll"; + public override string? Content { get; protected set; } + public List Options { get; } = new(); + public List Votes { get; } = new(); + public bool IsMultipleChoice { get; set; } + public DateTime? ExpiresAt { get; set; } + public bool IsClosed { get; set; } + + public PollMessage() : base() { } + + public PollMessage(Guid id, Guid chatId, Guid senderId, string? question, List? options, bool isAnonymous, bool isMultiple, DateTime? expiresAt, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) + : base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) + { + Content = question ?? "Poll"; + if (options != null) + { + foreach (var opt in options) + { + Options.Add(new PollOption { Text = opt }); + } + } + IsMultipleChoice = isMultiple; + ExpiresAt = expiresAt; + } +} diff --git a/backend/src/Contracts/Messaging/Domain/PollOption.cs b/backend/src/Contracts/Messaging/Domain/PollOption.cs new file mode 100644 index 0000000..a7feb75 --- /dev/null +++ b/backend/src/Contracts/Messaging/Domain/PollOption.cs @@ -0,0 +1,7 @@ +namespace Knot.Contracts.Messaging.Domain; + +public class PollOption +{ + public string Text { get; set; } = string.Empty; + public int VoteCount { get; set; } +} diff --git a/backend/src/Contracts/Messaging/Domain/PollVote.cs b/backend/src/Contracts/Messaging/Domain/PollVote.cs new file mode 100644 index 0000000..80f59ed --- /dev/null +++ b/backend/src/Contracts/Messaging/Domain/PollVote.cs @@ -0,0 +1,10 @@ +using System; + +namespace Knot.Contracts.Messaging.Domain; + +public class PollVote +{ + public Guid OptionIndex { get; set; } + public Guid UserId { get; set; } + public DateTime VotedAt { get; set; } +} diff --git a/backend/src/Contracts/Messaging/Domain/StoryMessage.cs b/backend/src/Contracts/Messaging/Domain/StoryMessage.cs new file mode 100644 index 0000000..dbc4069 --- /dev/null +++ b/backend/src/Contracts/Messaging/Domain/StoryMessage.cs @@ -0,0 +1,31 @@ +using System; + +namespace Knot.Contracts.Messaging.Domain; + +public class StoryMessage : Message +{ + public override string Type => "story"; + public override string? Content { get; protected set; } + public Guid? StoryId { get; private set; } + public string? StoryMediaUrl { get; private set; } + public string? StoryMediaType { get; private set; } + + public StoryMessage() : base() { } + + public StoryMessage(Guid id, Guid chatId, Guid senderId, Guid storyId, string? storyMediaUrl, string? storyMediaType, DateTime createdAt) + : base(id, chatId, senderId, null, null, createdAt, false) + { + StoryId = storyId; + StoryMediaUrl = storyMediaUrl; + StoryMediaType = storyMediaType; + } + + public StoryMessage(Guid id, Guid chatId, Guid senderId, Guid storyId, string? storyMediaUrl, string? storyMediaType, string? content, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported) + : this(id, chatId, senderId, storyId, storyMediaUrl, storyMediaType, createdAt) + { + Content = content; + ReplyToId = replyToId; + ForwardedFromId = forwardedFromId; + if (isImported) AddState(MessageState.IsImported); + } +} diff --git a/backend/src/Contracts/Messaging/Domain/TextMessage.cs b/backend/src/Contracts/Messaging/Domain/TextMessage.cs new file mode 100644 index 0000000..f6b9543 --- /dev/null +++ b/backend/src/Contracts/Messaging/Domain/TextMessage.cs @@ -0,0 +1,18 @@ +using System; + +namespace Knot.Contracts.Messaging.Domain; + +public class TextMessage : Message +{ + public override string Type => "text"; + public override string? Content { get; protected set; } + public string? Quote { get; protected set; } + + public TextMessage() : base() { } + + public TextMessage(Guid id, Guid chatId, Guid senderId, string content, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported = false) + : base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) => Content = content; + + public TextMessage(Guid id, Guid chatId, Guid senderId, string content, Guid? replyToId, string? quote, Guid? forwardedFromId, DateTime createdAt, bool isImported = false) + : this(id, chatId, senderId, content, replyToId, forwardedFromId, createdAt, isImported) => Quote = quote; +} diff --git a/backend/src/Modules/Conversations/Application/Chats/GetChatById/GetChatById.cs b/backend/src/Modules/Conversations/Application/Chats/GetChatById/GetChatById.cs index 3eca23d..e9445ab 100644 --- a/backend/src/Modules/Conversations/Application/Chats/GetChatById/GetChatById.cs +++ b/backend/src/Modules/Conversations/Application/Chats/GetChatById/GetChatById.cs @@ -109,6 +109,10 @@ internal sealed class GetChatByIdQueryHandler : IQueryHandler new ReadByDto(m.UserId)) .ToList(); + var textMessage = latestMessage as TextMessage; + var mediaMessage = latestMessage as MediaMessage; + var storyMessage = latestMessage as StoryMessage; + messagesList.Add(new ChatMessageDto( latestMessage.Id, latestMessage.ChatId, @@ -116,15 +120,15 @@ internal sealed class GetChatByIdQueryHandler : IQueryHandler new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList(), + mediaMessage?.Media.Select(media => new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList() ?? new List(), senderObj != null ? new MessageSenderDto( senderObj.Id, senderObj.Username, diff --git a/backend/src/Modules/Conversations/Application/Chats/GetChats/GetChats.cs b/backend/src/Modules/Conversations/Application/Chats/GetChats/GetChats.cs index 9fcb72c..865e0e3 100644 --- a/backend/src/Modules/Conversations/Application/Chats/GetChats/GetChats.cs +++ b/backend/src/Modules/Conversations/Application/Chats/GetChats/GetChats.cs @@ -101,6 +101,10 @@ internal sealed class GetChatsQueryHandler : IQueryHandler new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList(), + mediaMessage?.Media.Select(media => new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList() ?? new List(), senderObj != null ? new MessageSenderDto( senderObj.Id, senderObj.Username, diff --git a/backend/src/Modules/Conversations/Application/Messages/GetMessages/GetMessagesQuery.cs b/backend/src/Modules/Conversations/Application/Messages/GetMessages/GetMessagesQuery.cs index ccec892..ea55446 100644 --- a/backend/src/Modules/Conversations/Application/Messages/GetMessages/GetMessagesQuery.cs +++ b/backend/src/Modules/Conversations/Application/Messages/GetMessages/GetMessagesQuery.cs @@ -96,7 +96,7 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler new MediaDto(rm.Id, rm.Type, rm.Url, rm.Filename, rm.Size)).ToList(), + (replyMsg as MediaMessage)?.Media.Select(rm => new MediaDto(rm.Id, rm.Type, rm.Url, rm.Filename, rm.Size)).ToList() ?? new List(), senderObj ); } @@ -117,6 +117,10 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList(), + storyMessage?.StoryId, + storyMessage?.StoryMediaUrl, + storyMessage?.StoryMediaType, + mediaMessage?.Media.Select(media => new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList() ?? new List(), senders.TryGetValue(message.SenderId, out var senderUser) ? new MessageSenderDto(senderUser.Id, senderUser.Username, senderUser.DisplayName, senderUser.Avatar) : null, chat.Members.Where(m => m.LastReadSequenceId >= message.SequenceId && m.UserId != message.SenderId).Select(m => new ReadByDto(m.UserId)).ToList(), reactionsWithUser diff --git a/backend/src/Modules/Conversations/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs b/backend/src/Modules/Conversations/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs index c242f62..7fe4fe9 100644 --- a/backend/src/Modules/Conversations/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs +++ b/backend/src/Modules/Conversations/Application/Messages/GetSharedMedia/GetSharedMediaQuery.cs @@ -48,13 +48,20 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler match.Value).ToList() : new List(); - var messageMediaColl = message.Media; - var mediaLinks = (messageMediaColl ?? Enumerable.Empty()).Where(media => media.Type?.ToString().ToLower() == "link").Select(media => media.Url).ToList(); + + var mediaLinks = (mediaMessage?.Media ?? Enumerable.Empty()) + .Where(media => media.Type?.ToString().ToLower() == "link" && !string.IsNullOrEmpty(media.Url)) + .Select(media => media.Url!) + .ToList(); var allLinks = contentLinks.Concat(mediaLinks).Distinct().ToList(); if (allLinks.Any()) @@ -72,7 +79,7 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler { - var mediaType = media.Type?.ToLower() ?? "file"; - var isGif = mediaType == "image" && (media.Url.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) || media.Url.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)); + var mType = media.Type?.ToLower() ?? "file"; + var isGif = mType == "image" && media.Url != null && (media.Url.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) || media.Url.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)); if (filterType == "gifs") { @@ -90,12 +97,12 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList() diff --git a/backend/src/Modules/Conversations/Application/Messages/SearchMessages/SearchMessagesQuery.cs b/backend/src/Modules/Conversations/Application/Messages/SearchMessages/SearchMessagesQuery.cs index 0b3cc75..69250e8 100644 --- a/backend/src/Modules/Conversations/Application/Messages/SearchMessages/SearchMessagesQuery.cs +++ b/backend/src/Modules/Conversations/Application/Messages/SearchMessages/SearchMessagesQuery.cs @@ -41,28 +41,34 @@ internal sealed class SearchMessagesQueryHandler : IQueryHandler r.MessageId).ToDictionary(g => g.Key, g => g.ToList()); - var result = messages.Select(message => new SearchMessageDto( - message.Id, - message.ChatId, - message.SenderId, + var result = messages.Select(message => { + var textMessage = message as TextMessage; + var mediaMessage = message as MediaMessage; + var storyMessage = message as StoryMessage; + + return new SearchMessageDto( + message.Id, + message.ChatId, + message.SenderId, message.Content, message.Type, message.ReplyToId, - message.Quote, + textMessage?.Quote, message.IsEdited, message.IsDeleted, message.CreatedAt, message.SequenceId, message.ForwardedFromId, null, - message.StoryId, - message.StoryMediaUrl, - message.StoryMediaType, - message.Media.Select(media => new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList(), - senders.TryGetValue(message.SenderId, out var senderUser) ? new MessageSenderDto(senderUser.Id, senderUser.Username, senderUser.DisplayName, senderUser.Avatar) : new MessageSenderDto(message.SenderId, "unknown", "Unknown", null), - reactionsByMessage.TryGetValue(message.Id, out var mr) ? mr.Select(reaction => new SimpleReactionDto(reaction.UserId, reaction.Emoji)).ToList() : new List(), - new List() - )).ToList(); + storyMessage?.StoryId, + storyMessage?.StoryMediaUrl, + storyMessage?.StoryMediaType, + mediaMessage?.Media.Select(media => new MediaDto(media.Id, media.Type, media.Url, media.Filename, media.Size)).ToList() ?? new List(), + senders.TryGetValue(message.SenderId, out var senderUser) ? new MessageSenderDto(senderUser.Id, senderUser.Username, senderUser.DisplayName, senderUser.Avatar) : new MessageSenderDto(message.SenderId, "unknown", "Unknown", null), + reactionsByMessage.TryGetValue(message.Id, out var mr) ? mr.Select(reaction => new SimpleReactionDto(reaction.UserId, reaction.Emoji)).ToList() : new List(), + new List() + ); + }).ToList(); return Result.Success(result); } diff --git a/backend/src/Modules/Messaging/Domain/MediaMessage.cs b/backend/src/Modules/Messaging/Domain/MediaMessage.cs index 130b520..70af7de 100644 --- a/backend/src/Modules/Messaging/Domain/MediaMessage.cs +++ b/backend/src/Modules/Messaging/Domain/MediaMessage.cs @@ -13,7 +13,7 @@ public class MediaMessage : Message public MediaType MediaType { get; private set; } // image, video, file, voice private List _media = new(); - public override IReadOnlyCollection Media => _media.AsReadOnly(); + public IReadOnlyCollection Media => _media.AsReadOnly(); private MediaMessage() : base() { diff --git a/backend/src/Modules/Messaging/Domain/Message.cs b/backend/src/Modules/Messaging/Domain/Message.cs index 78c9f6e..cde6437 100644 --- a/backend/src/Modules/Messaging/Domain/Message.cs +++ b/backend/src/Modules/Messaging/Domain/Message.cs @@ -34,12 +34,6 @@ public abstract class Message : AggregateRoot public abstract string Type { get; } public abstract string? Content { get; protected set; } - public virtual string? Quote { get; protected set; } = null; - public virtual Guid? StoryId => null; - public virtual string? StoryMediaUrl => null; - public virtual string? StoryMediaType => null; - public virtual IReadOnlyCollection Media => Array.Empty(); - public bool IsEdited => HasState(MessageState.IsEdited); public bool IsDeleted => HasState(MessageState.IsDeleted); public bool IsDeletedForUser(Guid userId) => _deletedFor.Exists(d => d.UserId == userId); diff --git a/backend/src/Modules/Messaging/Domain/PollMessage.cs b/backend/src/Modules/Messaging/Domain/PollMessage.cs index 5eabb8b..fa0201d 100644 --- a/backend/src/Modules/Messaging/Domain/PollMessage.cs +++ b/backend/src/Modules/Messaging/Domain/PollMessage.cs @@ -112,47 +112,3 @@ public class PollMessage : Message base.Delete(); } } - -public class PollOption -{ - public Guid Id { get; private set; } - public Guid PollId { get; private set; } - public string Text { get; private set; } - - // Денормализованные данные для быстрой отдачи - public int VotesCount { get; private set; } - public int Percentage { get; private set; } - - private PollOption() { } // EF - - public PollOption(Guid id, Guid pollId, string text) - { - Id = id; - PollId = pollId; - Text = text; - } - - public void UpdateResults(int count, int percentage) - { - VotesCount = count; - Percentage = percentage; - } -} - -public class PollVote -{ - public Guid PollId { get; private set; } - public Guid UserId { get; private set; } - public Guid OptionId { get; private set; } - public DateTime VotedAt { get; private set; } - - private PollVote() { } // EF - - public PollVote(Guid pollId, Guid userId, Guid optionId, DateTime votedAt) - { - PollId = pollId; - UserId = userId; - OptionId = optionId; - VotedAt = votedAt; - } -} diff --git a/backend/src/Modules/Messaging/Domain/PollOption.cs b/backend/src/Modules/Messaging/Domain/PollOption.cs new file mode 100644 index 0000000..a8597ea --- /dev/null +++ b/backend/src/Modules/Messaging/Domain/PollOption.cs @@ -0,0 +1,29 @@ +using System; + +namespace Knot.Modules.Messaging.Domain; + +public class PollOption +{ + public Guid Id { get; private set; } + public Guid PollId { get; private set; } + public string Text { get; private set; } = string.Empty; + + // Денормализованные данные для быстрой отдачи + public int VotesCount { get; private set; } + public int Percentage { get; private set; } + + private PollOption() { } // EF + + public PollOption(Guid id, Guid pollId, string text) + { + Id = id; + PollId = pollId; + Text = text; + } + + public void UpdateResults(int count, int percentage) + { + VotesCount = count; + Percentage = percentage; + } +} diff --git a/backend/src/Modules/Messaging/Domain/PollVote.cs b/backend/src/Modules/Messaging/Domain/PollVote.cs new file mode 100644 index 0000000..47ca2ca --- /dev/null +++ b/backend/src/Modules/Messaging/Domain/PollVote.cs @@ -0,0 +1,21 @@ +using System; + +namespace Knot.Modules.Messaging.Domain; + +public class PollVote +{ + public Guid PollId { get; private set; } + public Guid UserId { get; private set; } + public Guid OptionId { get; private set; } + public DateTime VotedAt { get; private set; } + + private PollVote() { } // EF + + public PollVote(Guid pollId, Guid userId, Guid optionId, DateTime votedAt) + { + PollId = pollId; + UserId = userId; + OptionId = optionId; + VotedAt = votedAt; + } +} diff --git a/backend/src/Modules/Messaging/Domain/StoryMessage.cs b/backend/src/Modules/Messaging/Domain/StoryMessage.cs index b7d21c1..75aaf29 100644 --- a/backend/src/Modules/Messaging/Domain/StoryMessage.cs +++ b/backend/src/Modules/Messaging/Domain/StoryMessage.cs @@ -10,13 +10,13 @@ public class StoryMessage : Message public override string? Content { get; protected set; } public Guid InternalStoryId { get; private set; } - public override Guid? StoryId => InternalStoryId; + public Guid? StoryId => InternalStoryId; public string InternalStoryMediaUrl { get; private set; } - public override string? StoryMediaUrl => InternalStoryMediaUrl; + public string? StoryMediaUrl => InternalStoryMediaUrl; public MediaType InternalStoryMediaType { get; private set; } - public override string? StoryMediaType => InternalStoryMediaType.ToString().ToLower(); + public string? StoryMediaType => InternalStoryMediaType.ToString().ToLower(); private StoryMessage() : base() { diff --git a/backend/src/Modules/Messaging/Domain/TextMessage.cs b/backend/src/Modules/Messaging/Domain/TextMessage.cs index 0ff7450..8245b83 100644 --- a/backend/src/Modules/Messaging/Domain/TextMessage.cs +++ b/backend/src/Modules/Messaging/Domain/TextMessage.cs @@ -8,7 +8,7 @@ public class TextMessage : Message { public override string Type => "text"; public override string? Content { get; protected set; } - public override string? Quote { get; protected set; } + public string? Quote { get; protected set; } private TextMessage() : base() { diff --git a/backend/src/Modules/Messaging/Infrastructure/Handlers/MessageSentDomainEventHandler.cs b/backend/src/Modules/Messaging/Infrastructure/Handlers/MessageSentDomainEventHandler.cs index 02a4e12..5f16bb5 100644 --- a/backend/src/Modules/Messaging/Infrastructure/Handlers/MessageSentDomainEventHandler.cs +++ b/backend/src/Modules/Messaging/Infrastructure/Handlers/MessageSentDomainEventHandler.cs @@ -54,8 +54,8 @@ public sealed class MessageSentDomainEventHandler : INotificationHandler new { rm.Type, rm.Url }).ToList(), + Quote = message is TextMessage tm ? tm.Quote : null, + media = (replyMsg as MediaMessage)?.Media.Select(rm => new { rm.Type, rm.Url }).ToList() ?? (object)Array.Empty(), Sender = replySenderInfo != null ? new { Id = replySenderInfo.Id, Username = replySenderInfo.Username, DisplayName = replySenderInfo.DisplayName } : new { Id = replyMsg.SenderId, Username = "unknown", DisplayName = "Unknown" } @@ -75,19 +75,19 @@ public sealed class MessageSentDomainEventHandler : INotificationHandler new + quote = message is TextMessage tm2 ? tm2.Quote : null, + media = (message as MediaMessage)?.Media.Select(m => new { type = m.Type, url = m.Url, filename = m.FileId, size = m.Size - }).ToList(), + }).ToList() ?? (object)Array.Empty(), sender = senderObj, readBy = new List(), - storyId = message.StoryId, - storyMediaUrl = message.StoryMediaUrl, - storyMediaType = message.StoryMediaType + storyId = (message as StoryMessage)?.StoryId, + storyMediaUrl = (message as StoryMessage)?.StoryMediaUrl, + storyMediaType = (message as StoryMessage)?.StoryMediaType }, cancellationToken); } } diff --git a/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/MongoDbMapConfigurator.cs b/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/MongoDbMapConfigurator.cs index 693f229..7cae70d 100644 --- a/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/MongoDbMapConfigurator.cs +++ b/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/MongoDbMapConfigurator.cs @@ -30,13 +30,15 @@ public static class MongoDbMapConfigurator cm.AutoMap(); cm.MapField("_deletedFor").SetElementName("DeletedFor"); cm.MapProperty(c => c.Content).SetSerializer(new EncryptedStringSerializer()); - cm.MapProperty(c => c.Quote).SetSerializer(new EncryptedStringSerializer()); + cm.SetIgnoreExtraElements(true); cm.SetIsRootClass(true); }); BsonClassMap.RegisterClassMap(cm => { cm.AutoMap(); + cm.MapProperty(c => c.Quote).SetSerializer(new EncryptedStringSerializer()); + cm.SetIgnoreExtraElements(true); cm.SetDiscriminator("TextMessage"); }); @@ -44,6 +46,7 @@ public static class MongoDbMapConfigurator { cm.AutoMap(); cm.MapField("_media").SetElementName("Media"); + cm.SetIgnoreExtraElements(true); cm.SetDiscriminator("MediaMessage"); cm.UnmapProperty(c => c.Caption); }); @@ -51,8 +54,9 @@ public static class MongoDbMapConfigurator BsonClassMap.RegisterClassMap(cm => { cm.AutoMap(); + cm.SetIgnoreExtraElements(true); cm.SetDiscriminator("StoryMessage"); - cm.MapProperty(c => c.InternalStoryMediaUrl).SetSerializer(new EncryptedStringSerializer()); + cm.MapProperty(c => c.StoryMediaUrl).SetSerializer(new EncryptedStringSerializer()); }); BsonClassMap.RegisterClassMap(cm => cm.AutoMap()); @@ -63,6 +67,7 @@ public static class MongoDbMapConfigurator cm.AutoMap(); cm.MapProperty(c => c.Options).SetElementName("_options"); cm.MapProperty(c => c.Votes).SetElementName("_votes"); + cm.SetIgnoreExtraElements(true); cm.SetDiscriminator("PollMessage"); });