mirror of
https://github.com/Govor-team/Govor.git
synced 2026-09-20 09:53:12 +00:00
Add read receipts and editing limits
Introduced message read tracking with a dedicated MessageReadingService, SignalR read request/response contracts, and hub notification flow for read receipts. Added configurable message edit constraints through MessageEditingOptions and a shared INowDateTimeProvider so timestamps are consistent across the app. Also fixed recipient validation in message removal and improved push token upsert behavior by matching the correct user/session record.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Govor.Application.Authentication.JWT;
|
||||
using Govor.Application.Messages;
|
||||
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
@@ -8,7 +9,7 @@ public static class AddOptionExtensions
|
||||
{
|
||||
services.Configure<JwtAccessOption>(configuration.GetSection(nameof(JwtAccessOption)));
|
||||
services.Configure<JwtRefreshOption>(configuration.GetSection(nameof(JwtRefreshOption)));
|
||||
|
||||
services.Configure<MessageEditingOptions>(configuration.GetSection(nameof(MessageEditingOptions)));
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Govor.Application.Authentication.JWT;
|
||||
using Govor.Application.Friends;
|
||||
using Govor.Application.Groups;
|
||||
using Govor.Application.Infrastructure.AdminsStuff;
|
||||
using Govor.Application.Infrastructure.Common;
|
||||
using Govor.Application.Infrastructure.Extensions;
|
||||
using Govor.Application.Infrastructure.Validators;
|
||||
using Govor.Application.Medias;
|
||||
@@ -41,6 +42,8 @@ public static class ConfigurationProgramExtensions
|
||||
services.AddScoped<IInvitationGenerator, InvitationGenerator>();
|
||||
services.AddScoped<ISynchingService, SynchingService>();
|
||||
|
||||
services.AddScoped<INowDateTimeProvider, NowDateTimeProvider>();
|
||||
|
||||
// Friends services
|
||||
services.AddScoped<IFriendshipService, FriendshipService>();
|
||||
services.AddScoped<IFriendRequestCommandService, FriendRequestCommandService>();
|
||||
@@ -64,6 +67,7 @@ public static class ConfigurationProgramExtensions
|
||||
|
||||
//services.AddScoped<IMessageCommandService, MessageCommandService>();
|
||||
services.AddScoped<IMessageSendingService, MessageSendingService>();
|
||||
services.AddScoped<IMessageReadingService, MessageReadingService>();
|
||||
services.AddScoped<IMessageEditingService, MessageEditingService>();
|
||||
services.AddScoped<IMessageRemovingService, MessageRemovingService>();
|
||||
services.AddScoped<IVerifyFriendship, VerifyFriendship>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.API.Hubs.Infrastructure;
|
||||
using Govor.Application.Exceptions.VerifyFriendship;
|
||||
using Govor.Application.Infrastructure.Common;
|
||||
using Govor.Application.Messages;
|
||||
using Govor.Application.Messages.Parameters;
|
||||
using Govor.Contracts.Requests.SignalR;
|
||||
@@ -15,24 +16,31 @@ namespace Govor.API.Hubs;
|
||||
public class ChatsHub : Hub
|
||||
{
|
||||
private readonly ILogger<ChatsHub> _logger;
|
||||
private readonly IMessageReadingService _messageReadingService;
|
||||
private readonly IMessageSendingService _messageSendingService;
|
||||
private readonly IMessageEditingService _messageEditingService;
|
||||
private readonly IMessageRemovingService _messageRemovingService;
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
private readonly IChatNotificationService _notifier;
|
||||
private readonly IConnectionManager _connectionManager;
|
||||
|
||||
private readonly INowDateTimeProvider _nowDateTimeProvider;
|
||||
|
||||
public ChatsHub(ILogger<ChatsHub> logger,
|
||||
IMessageReadingService messageReadingService,
|
||||
IMessageSendingService messageSendingService,
|
||||
IMessageEditingService messageEditingService,
|
||||
IMessageRemovingService messageRemovingService,
|
||||
IHubUserAccessor userAccessor,
|
||||
IChatNotificationService notifier,
|
||||
INowDateTimeProvider nowDateTimeProvider,
|
||||
IConnectionManager connectionManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_nowDateTimeProvider = nowDateTimeProvider;
|
||||
_messageSendingService = messageSendingService;
|
||||
_messageEditingService = messageEditingService;
|
||||
_messageRemovingService = messageRemovingService;
|
||||
_messageReadingService = messageReadingService;
|
||||
_userAccessor = userAccessor;
|
||||
_notifier = notifier;
|
||||
_connectionManager = connectionManager;
|
||||
@@ -85,14 +93,40 @@ public class ChatsHub : Hub
|
||||
return HubResult<UserMessageResponse>.Ok(response);
|
||||
}, request.RecipientId);
|
||||
}
|
||||
|
||||
// --- Read ---
|
||||
public async Task<HubResult<MessageReadResponse>> Read(ReadMessageRequest request)
|
||||
{
|
||||
return await SafeExecute(async (userId) =>
|
||||
{
|
||||
var result = await _messageReadingService.ReadMessageAsync(userId, request.MessageId);
|
||||
if(!result.IsSuccess)
|
||||
throw new InvalidOperationException(result.Error.ToString());
|
||||
|
||||
var message = result.Value;
|
||||
|
||||
var msgv = message.MessageViews.First(v => v.UserId == userId);
|
||||
|
||||
var response = new MessageReadResponse()
|
||||
{
|
||||
ViewId = msgv.Id,
|
||||
MessageId = request.MessageId,
|
||||
ReaderId = userId,
|
||||
WhenWas = msgv.ViewedAt,
|
||||
RecipientId = message.RecipientId,
|
||||
RecipientType = message.RecipientType,
|
||||
};
|
||||
|
||||
await _notifier.NotifyMessageWasReadAsync(response);
|
||||
|
||||
return HubResult<MessageReadResponse>.Ok(response);
|
||||
}, request.MessageId);
|
||||
}
|
||||
// --- REMOVE ---
|
||||
public async Task<HubResult<MessageRemovedResponse>> Remove(RemoveMessageRequest request)
|
||||
{
|
||||
return await SafeExecute(async (userId) =>
|
||||
{
|
||||
var result = await _messageRemovingService.DeleteMessageAsync(
|
||||
new DeleteMessage(
|
||||
var deletemessage = new DeleteMessage(
|
||||
userId,
|
||||
request.MessageId,
|
||||
ForceRemove: request.RequestType switch
|
||||
@@ -100,8 +134,10 @@ public class ChatsHub : Hub
|
||||
RemoveMessageRequestType.HideForMe => false,
|
||||
RemoveMessageRequestType.ForceRemove => true,
|
||||
_ => false
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
var result = await _messageRemovingService.DeleteMessageAsync(deletemessage);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
throw new InvalidOperationException(result.Error.ToString());
|
||||
@@ -110,7 +146,8 @@ public class ChatsHub : Hub
|
||||
{
|
||||
MessageId = request.MessageId,
|
||||
SenderId = result.Value.SenderId,
|
||||
RecipientId = result.Value.RecipientId,
|
||||
RecipientId = result.Value.RecipientId, // private chat id or group id
|
||||
RequestType = request.RequestType,
|
||||
RecipientType = result.Value.RecipientType
|
||||
};
|
||||
|
||||
@@ -125,7 +162,12 @@ public class ChatsHub : Hub
|
||||
{
|
||||
return await SafeExecute(async (userId) =>
|
||||
{
|
||||
var editParams = new EditMessage(userId, request.MessageId, request.NewEncryptedContent, DateTime.UtcNow);
|
||||
var editParams = new EditMessage(
|
||||
userId,
|
||||
request.MessageId,
|
||||
request.NewEncryptedContent,
|
||||
_nowDateTimeProvider.Now);
|
||||
|
||||
var result = await _messageEditingService.EditMessageAsync(editParams);
|
||||
|
||||
if (!result.IsSuccess || result.OriginalMessage == null)
|
||||
@@ -199,7 +241,7 @@ public class ChatsHub : Hub
|
||||
FromUserId: senderId,
|
||||
RecipientId: request.RecipientId,
|
||||
RecipientType: request.RecipientType,
|
||||
SendAt: DateTime.UtcNow,
|
||||
SendAt: _nowDateTimeProvider.Now,
|
||||
Media: request.MediaAttachments?.Select(f => new SendMedia(f.MediaId, f.EncryptedKey))
|
||||
?? Array.Empty<SendMedia>()
|
||||
);
|
||||
|
||||
@@ -5,8 +5,9 @@ public static class ChatHubConstants
|
||||
public const string ReceiveMessage = "ReceiveMessage";
|
||||
public const string MessageSent = "MessageSent";
|
||||
public const string MessageRemoved = "MessageRemoved";
|
||||
public const string MessageEdited = "MessageEdit";
|
||||
|
||||
public const string MessageEdited = "MessageEdited";
|
||||
public static string MessageRead = "MessageReaded";
|
||||
|
||||
public static string GetUserGroup(Guid userId) => userId.ToString();
|
||||
public static string GetChatGroup(Guid groupId) => $"group_{groupId}";
|
||||
public static string GetPrivateChat(Guid groupId) => $"private_{groupId}";
|
||||
|
||||
@@ -45,6 +45,16 @@ public class ChatNotificationService : IChatNotificationService
|
||||
// .SendAsync(ChatHubConstants.MessageSent, message);
|
||||
}
|
||||
|
||||
public async Task NotifyMessageWasReadAsync(MessageReadResponse response)
|
||||
{
|
||||
await NotifyParticipantsAsync(
|
||||
response.ReaderId,
|
||||
response.RecipientId,
|
||||
response.RecipientType,
|
||||
ChatHubConstants.MessageRead,
|
||||
response);
|
||||
}
|
||||
|
||||
private async Task NotifyMessageReceivedInPrivateChatAsync(UserMessageResponse message)
|
||||
{
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Govor.API.Hubs.Infrastructure;
|
||||
public interface IChatNotificationService
|
||||
{
|
||||
Task NotifyMessageSentAsync(UserMessageResponse message);
|
||||
Task NotifyMessageWasReadAsync(MessageReadResponse response);
|
||||
Task NotifyMessageRemovedAsync(MessageRemovedResponse response);
|
||||
Task NotifyMessageEditedAsync(MessageEditResponse response);
|
||||
}
|
||||
@@ -19,5 +19,9 @@
|
||||
},
|
||||
"EncryptionOption": {
|
||||
"Secret": "8B2j9kkw9xP5m7nQwE2zY3A-=Q8zP7C4+TqLZpg"
|
||||
},
|
||||
"MessageEditingOptions": {
|
||||
"Enabled": true,
|
||||
"MaxEditTimeMinutes": 15
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user