mirror of
https://github.com/Govor-team/Govor.git
synced 2026-09-17 16:22:49 +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.Authentication.JWT;
|
||||||
|
using Govor.Application.Messages;
|
||||||
|
|
||||||
namespace Govor.API.Common.Extensions;
|
namespace Govor.API.Common.Extensions;
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ public static class AddOptionExtensions
|
|||||||
{
|
{
|
||||||
services.Configure<JwtAccessOption>(configuration.GetSection(nameof(JwtAccessOption)));
|
services.Configure<JwtAccessOption>(configuration.GetSection(nameof(JwtAccessOption)));
|
||||||
services.Configure<JwtRefreshOption>(configuration.GetSection(nameof(JwtRefreshOption)));
|
services.Configure<JwtRefreshOption>(configuration.GetSection(nameof(JwtRefreshOption)));
|
||||||
|
services.Configure<MessageEditingOptions>(configuration.GetSection(nameof(MessageEditingOptions)));
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ using Govor.Application.Authentication.JWT;
|
|||||||
using Govor.Application.Friends;
|
using Govor.Application.Friends;
|
||||||
using Govor.Application.Groups;
|
using Govor.Application.Groups;
|
||||||
using Govor.Application.Infrastructure.AdminsStuff;
|
using Govor.Application.Infrastructure.AdminsStuff;
|
||||||
|
using Govor.Application.Infrastructure.Common;
|
||||||
using Govor.Application.Infrastructure.Extensions;
|
using Govor.Application.Infrastructure.Extensions;
|
||||||
using Govor.Application.Infrastructure.Validators;
|
using Govor.Application.Infrastructure.Validators;
|
||||||
using Govor.Application.Medias;
|
using Govor.Application.Medias;
|
||||||
@@ -41,6 +42,8 @@ public static class ConfigurationProgramExtensions
|
|||||||
services.AddScoped<IInvitationGenerator, InvitationGenerator>();
|
services.AddScoped<IInvitationGenerator, InvitationGenerator>();
|
||||||
services.AddScoped<ISynchingService, SynchingService>();
|
services.AddScoped<ISynchingService, SynchingService>();
|
||||||
|
|
||||||
|
services.AddScoped<INowDateTimeProvider, NowDateTimeProvider>();
|
||||||
|
|
||||||
// Friends services
|
// Friends services
|
||||||
services.AddScoped<IFriendshipService, FriendshipService>();
|
services.AddScoped<IFriendshipService, FriendshipService>();
|
||||||
services.AddScoped<IFriendRequestCommandService, FriendRequestCommandService>();
|
services.AddScoped<IFriendRequestCommandService, FriendRequestCommandService>();
|
||||||
@@ -64,6 +67,7 @@ public static class ConfigurationProgramExtensions
|
|||||||
|
|
||||||
//services.AddScoped<IMessageCommandService, MessageCommandService>();
|
//services.AddScoped<IMessageCommandService, MessageCommandService>();
|
||||||
services.AddScoped<IMessageSendingService, MessageSendingService>();
|
services.AddScoped<IMessageSendingService, MessageSendingService>();
|
||||||
|
services.AddScoped<IMessageReadingService, MessageReadingService>();
|
||||||
services.AddScoped<IMessageEditingService, MessageEditingService>();
|
services.AddScoped<IMessageEditingService, MessageEditingService>();
|
||||||
services.AddScoped<IMessageRemovingService, MessageRemovingService>();
|
services.AddScoped<IMessageRemovingService, MessageRemovingService>();
|
||||||
services.AddScoped<IVerifyFriendship, VerifyFriendship>();
|
services.AddScoped<IVerifyFriendship, VerifyFriendship>();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Govor.API.Common.SignalR.Helpers;
|
using Govor.API.Common.SignalR.Helpers;
|
||||||
using Govor.API.Hubs.Infrastructure;
|
using Govor.API.Hubs.Infrastructure;
|
||||||
using Govor.Application.Exceptions.VerifyFriendship;
|
using Govor.Application.Exceptions.VerifyFriendship;
|
||||||
|
using Govor.Application.Infrastructure.Common;
|
||||||
using Govor.Application.Messages;
|
using Govor.Application.Messages;
|
||||||
using Govor.Application.Messages.Parameters;
|
using Govor.Application.Messages.Parameters;
|
||||||
using Govor.Contracts.Requests.SignalR;
|
using Govor.Contracts.Requests.SignalR;
|
||||||
@@ -15,24 +16,31 @@ namespace Govor.API.Hubs;
|
|||||||
public class ChatsHub : Hub
|
public class ChatsHub : Hub
|
||||||
{
|
{
|
||||||
private readonly ILogger<ChatsHub> _logger;
|
private readonly ILogger<ChatsHub> _logger;
|
||||||
|
private readonly IMessageReadingService _messageReadingService;
|
||||||
private readonly IMessageSendingService _messageSendingService;
|
private readonly IMessageSendingService _messageSendingService;
|
||||||
private readonly IMessageEditingService _messageEditingService;
|
private readonly IMessageEditingService _messageEditingService;
|
||||||
private readonly IMessageRemovingService _messageRemovingService;
|
private readonly IMessageRemovingService _messageRemovingService;
|
||||||
private readonly IHubUserAccessor _userAccessor;
|
private readonly IHubUserAccessor _userAccessor;
|
||||||
private readonly IChatNotificationService _notifier;
|
private readonly IChatNotificationService _notifier;
|
||||||
private readonly IConnectionManager _connectionManager;
|
private readonly IConnectionManager _connectionManager;
|
||||||
|
private readonly INowDateTimeProvider _nowDateTimeProvider;
|
||||||
|
|
||||||
public ChatsHub(ILogger<ChatsHub> logger,
|
public ChatsHub(ILogger<ChatsHub> logger,
|
||||||
|
IMessageReadingService messageReadingService,
|
||||||
IMessageSendingService messageSendingService,
|
IMessageSendingService messageSendingService,
|
||||||
IMessageEditingService messageEditingService,
|
IMessageEditingService messageEditingService,
|
||||||
|
IMessageRemovingService messageRemovingService,
|
||||||
IHubUserAccessor userAccessor,
|
IHubUserAccessor userAccessor,
|
||||||
IChatNotificationService notifier,
|
IChatNotificationService notifier,
|
||||||
|
INowDateTimeProvider nowDateTimeProvider,
|
||||||
IConnectionManager connectionManager)
|
IConnectionManager connectionManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_nowDateTimeProvider = nowDateTimeProvider;
|
||||||
_messageSendingService = messageSendingService;
|
_messageSendingService = messageSendingService;
|
||||||
_messageEditingService = messageEditingService;
|
_messageEditingService = messageEditingService;
|
||||||
|
_messageRemovingService = messageRemovingService;
|
||||||
|
_messageReadingService = messageReadingService;
|
||||||
_userAccessor = userAccessor;
|
_userAccessor = userAccessor;
|
||||||
_notifier = notifier;
|
_notifier = notifier;
|
||||||
_connectionManager = connectionManager;
|
_connectionManager = connectionManager;
|
||||||
@@ -85,14 +93,40 @@ public class ChatsHub : Hub
|
|||||||
return HubResult<UserMessageResponse>.Ok(response);
|
return HubResult<UserMessageResponse>.Ok(response);
|
||||||
}, request.RecipientId);
|
}, 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 ---
|
// --- REMOVE ---
|
||||||
public async Task<HubResult<MessageRemovedResponse>> Remove(RemoveMessageRequest request)
|
public async Task<HubResult<MessageRemovedResponse>> Remove(RemoveMessageRequest request)
|
||||||
{
|
{
|
||||||
return await SafeExecute(async (userId) =>
|
return await SafeExecute(async (userId) =>
|
||||||
{
|
{
|
||||||
var result = await _messageRemovingService.DeleteMessageAsync(
|
var deletemessage = new DeleteMessage(
|
||||||
new DeleteMessage(
|
|
||||||
userId,
|
userId,
|
||||||
request.MessageId,
|
request.MessageId,
|
||||||
ForceRemove: request.RequestType switch
|
ForceRemove: request.RequestType switch
|
||||||
@@ -100,8 +134,10 @@ public class ChatsHub : Hub
|
|||||||
RemoveMessageRequestType.HideForMe => false,
|
RemoveMessageRequestType.HideForMe => false,
|
||||||
RemoveMessageRequestType.ForceRemove => true,
|
RemoveMessageRequestType.ForceRemove => true,
|
||||||
_ => false
|
_ => false
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var result = await _messageRemovingService.DeleteMessageAsync(deletemessage);
|
||||||
|
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
throw new InvalidOperationException(result.Error.ToString());
|
throw new InvalidOperationException(result.Error.ToString());
|
||||||
@@ -110,7 +146,8 @@ public class ChatsHub : Hub
|
|||||||
{
|
{
|
||||||
MessageId = request.MessageId,
|
MessageId = request.MessageId,
|
||||||
SenderId = result.Value.SenderId,
|
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
|
RecipientType = result.Value.RecipientType
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,7 +162,12 @@ public class ChatsHub : Hub
|
|||||||
{
|
{
|
||||||
return await SafeExecute(async (userId) =>
|
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);
|
var result = await _messageEditingService.EditMessageAsync(editParams);
|
||||||
|
|
||||||
if (!result.IsSuccess || result.OriginalMessage == null)
|
if (!result.IsSuccess || result.OriginalMessage == null)
|
||||||
@@ -199,7 +241,7 @@ public class ChatsHub : Hub
|
|||||||
FromUserId: senderId,
|
FromUserId: senderId,
|
||||||
RecipientId: request.RecipientId,
|
RecipientId: request.RecipientId,
|
||||||
RecipientType: request.RecipientType,
|
RecipientType: request.RecipientType,
|
||||||
SendAt: DateTime.UtcNow,
|
SendAt: _nowDateTimeProvider.Now,
|
||||||
Media: request.MediaAttachments?.Select(f => new SendMedia(f.MediaId, f.EncryptedKey))
|
Media: request.MediaAttachments?.Select(f => new SendMedia(f.MediaId, f.EncryptedKey))
|
||||||
?? Array.Empty<SendMedia>()
|
?? Array.Empty<SendMedia>()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ public static class ChatHubConstants
|
|||||||
public const string ReceiveMessage = "ReceiveMessage";
|
public const string ReceiveMessage = "ReceiveMessage";
|
||||||
public const string MessageSent = "MessageSent";
|
public const string MessageSent = "MessageSent";
|
||||||
public const string MessageRemoved = "MessageRemoved";
|
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 GetUserGroup(Guid userId) => userId.ToString();
|
||||||
public static string GetChatGroup(Guid groupId) => $"group_{groupId}";
|
public static string GetChatGroup(Guid groupId) => $"group_{groupId}";
|
||||||
public static string GetPrivateChat(Guid groupId) => $"private_{groupId}";
|
public static string GetPrivateChat(Guid groupId) => $"private_{groupId}";
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ public class ChatNotificationService : IChatNotificationService
|
|||||||
// .SendAsync(ChatHubConstants.MessageSent, message);
|
// .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)
|
private async Task NotifyMessageReceivedInPrivateChatAsync(UserMessageResponse message)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Govor.API.Hubs.Infrastructure;
|
|||||||
public interface IChatNotificationService
|
public interface IChatNotificationService
|
||||||
{
|
{
|
||||||
Task NotifyMessageSentAsync(UserMessageResponse message);
|
Task NotifyMessageSentAsync(UserMessageResponse message);
|
||||||
|
Task NotifyMessageWasReadAsync(MessageReadResponse response);
|
||||||
Task NotifyMessageRemovedAsync(MessageRemovedResponse response);
|
Task NotifyMessageRemovedAsync(MessageRemovedResponse response);
|
||||||
Task NotifyMessageEditedAsync(MessageEditResponse response);
|
Task NotifyMessageEditedAsync(MessageEditResponse response);
|
||||||
}
|
}
|
||||||
@@ -19,5 +19,9 @@
|
|||||||
},
|
},
|
||||||
"EncryptionOption": {
|
"EncryptionOption": {
|
||||||
"Secret": "8B2j9kkw9xP5m7nQwE2zY3A-=Q8zP7C4+TqLZpg"
|
"Secret": "8B2j9kkw9xP5m7nQwE2zY3A-=Q8zP7C4+TqLZpg"
|
||||||
|
},
|
||||||
|
"MessageEditingOptions": {
|
||||||
|
"Enabled": true,
|
||||||
|
"MaxEditTimeMinutes": 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Govor.Application.Infrastructure.Common;
|
||||||
|
|
||||||
|
public interface INowDateTimeProvider
|
||||||
|
{
|
||||||
|
DateTime Now { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Govor.Application.Infrastructure.Common;
|
||||||
|
|
||||||
|
public class NowDateTimeProvider : INowDateTimeProvider
|
||||||
|
{
|
||||||
|
public DateTime Now => DateTime.UtcNow;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Govor.Domain.Common;
|
||||||
|
using Govor.Domain.Models.Messages;
|
||||||
|
using SmartRes;
|
||||||
|
|
||||||
|
namespace Govor.Application.Messages;
|
||||||
|
|
||||||
|
public interface IMessageReadingService
|
||||||
|
{
|
||||||
|
Task<Result<Message, Error>> ReadMessageAsync(Guid readerId, Guid messageId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Govor.Application.Messages;
|
||||||
|
|
||||||
|
public class MessageEditingOptions
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public int MaxEditTimeMinutes { get; set; } = 15;
|
||||||
|
}
|
||||||
@@ -3,18 +3,24 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Govor.Domain;
|
using Govor.Domain;
|
||||||
using Govor.Domain.Models.Messages;
|
using Govor.Domain.Models.Messages;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Govor.Application.Messages;
|
namespace Govor.Application.Messages;
|
||||||
|
|
||||||
public class MessageEditingService : IMessageEditingService
|
public class MessageEditingService : IMessageEditingService
|
||||||
{
|
{
|
||||||
private readonly GovorDbContext _dbContext;
|
private readonly GovorDbContext _dbContext;
|
||||||
private readonly ILogger<MessageEditingService> _logger;
|
private readonly ILogger<MessageEditingService> _logger;
|
||||||
|
private readonly MessageEditingOptions _options;
|
||||||
|
|
||||||
public MessageEditingService(GovorDbContext dbContext, ILogger<MessageEditingService> logger)
|
public MessageEditingService(
|
||||||
|
GovorDbContext dbContext,
|
||||||
|
ILogger<MessageEditingService> logger,
|
||||||
|
IOptions<MessageEditingOptions> options)
|
||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EditMessageResult> EditMessageAsync(EditMessage editParams)
|
public async Task<EditMessageResult> EditMessageAsync(EditMessage editParams)
|
||||||
@@ -25,15 +31,47 @@ public class MessageEditingService : IMessageEditingService
|
|||||||
|
|
||||||
if (message == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
return new EditMessageResult(false, new KeyNotFoundException("Message not found."), null);
|
return new EditMessageResult(
|
||||||
|
false,
|
||||||
|
new KeyNotFoundException("Message not found."),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.SenderId != editParams.EditorId)
|
if (message.SenderId != editParams.EditorId)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("User {EditorId} unauthorized to edit message {MessageId}", editParams.EditorId, editParams.MessageId);
|
_logger.LogWarning(
|
||||||
return new EditMessageResult(false, new UnauthorizedAccessException("User is not authorized to edit this message."), null);
|
"User {EditorId} unauthorized to edit message {MessageId}",
|
||||||
|
editParams.EditorId,
|
||||||
|
editParams.MessageId);
|
||||||
|
|
||||||
|
return new EditMessageResult(
|
||||||
|
false,
|
||||||
|
new UnauthorizedAccessException(
|
||||||
|
"User is not authorized to edit this message."),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверяем время, прошедшее с момента отправки
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var editDeadline = message.SentAt.AddMinutes(
|
||||||
|
_options.MaxEditTimeMinutes);
|
||||||
|
|
||||||
|
if (now > editDeadline && _options.Enabled)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Message {MessageId} cannot be edited. " +
|
||||||
|
"Edit time limit of {MaxEditTimeMinutes} minutes has expired.",
|
||||||
|
message.Id,
|
||||||
|
_options.MaxEditTimeMinutes);
|
||||||
|
|
||||||
|
return new EditMessageResult(
|
||||||
|
false,
|
||||||
|
new InvalidOperationException(
|
||||||
|
$"Message can only be edited within " +
|
||||||
|
$"{_options.MaxEditTimeMinutes} minutes after sending."),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
var originalMessageSnapshot = new Message
|
var originalMessageSnapshot = new Message
|
||||||
{
|
{
|
||||||
Id = message.Id,
|
Id = message.Id,
|
||||||
@@ -44,14 +82,20 @@ public class MessageEditingService : IMessageEditingService
|
|||||||
ReplyToMessageId = message.ReplyToMessageId,
|
ReplyToMessageId = message.ReplyToMessageId,
|
||||||
MediaAttachments = message.MediaAttachments?.ToList() ?? []
|
MediaAttachments = message.MediaAttachments?.ToList() ?? []
|
||||||
};
|
};
|
||||||
|
|
||||||
message.EncryptedContent = editParams.NewContent;
|
message.EncryptedContent = editParams.NewContent;
|
||||||
message.IsEdited = true;
|
message.IsEdited = true;
|
||||||
message.EditedAt = editParams.EditedAt;
|
message.EditedAt = editParams.EditedAt;
|
||||||
|
|
||||||
await _dbContext.SaveChangesAsync();
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
_logger.LogInformation("Message {MessageId} edited successfully.", editParams.MessageId);
|
_logger.LogInformation(
|
||||||
return new EditMessageResult(true, null, originalMessageSnapshot);
|
"Message {MessageId} edited successfully.",
|
||||||
|
editParams.MessageId);
|
||||||
|
|
||||||
|
return new EditMessageResult(
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
originalMessageSnapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using Govor.Application.Infrastructure.Common;
|
||||||
|
using Govor.Domain;
|
||||||
|
using Govor.Domain.Common;
|
||||||
|
using Govor.Domain.Models.Messages;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SmartRes;
|
||||||
|
|
||||||
|
namespace Govor.Application.Messages;
|
||||||
|
|
||||||
|
public class MessageReadingService : IMessageReadingService
|
||||||
|
{
|
||||||
|
private readonly GovorDbContext _dbContext;
|
||||||
|
private readonly ILogger<MessageReadingService> _logger;
|
||||||
|
private readonly INowDateTimeProvider _dateTimeProvider;
|
||||||
|
|
||||||
|
|
||||||
|
public MessageReadingService(
|
||||||
|
GovorDbContext dbContext,
|
||||||
|
ILogger<MessageReadingService> logger,
|
||||||
|
INowDateTimeProvider dateTimeProvider)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_logger = logger;
|
||||||
|
_dateTimeProvider = dateTimeProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<Message, Error>> ReadMessageAsync(Guid readerId, Guid messageId)
|
||||||
|
{
|
||||||
|
var message = await _dbContext.Messages
|
||||||
|
.Include(msg => msg.MessageViews)
|
||||||
|
.FirstOrDefaultAsync(msg => msg.Id == messageId);
|
||||||
|
|
||||||
|
if(message is null)
|
||||||
|
return Result<Message, Error>.Failure(Error.NotFound(
|
||||||
|
"Message not found",
|
||||||
|
"Message with id: " + messageId + " was not found.")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (CanReadMessage(readerId, message))
|
||||||
|
{
|
||||||
|
var view = new MessageView()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
MessageId = messageId,
|
||||||
|
UserId = readerId,
|
||||||
|
ViewedAt = _dateTimeProvider.Now,
|
||||||
|
};
|
||||||
|
|
||||||
|
_dbContext.MessageViews.Add(view);
|
||||||
|
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
_logger.LogInformation("Message with id: {Id} was read by user {user} at {time}", view.Id, view.UserId, view.ViewedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanReadMessage(Guid readerId, Message message)
|
||||||
|
{
|
||||||
|
if (message.RecipientType == RecipientType.User)
|
||||||
|
{
|
||||||
|
return _dbContext.PrivateChats.Any(pr => pr.UserAId == readerId || pr.UserBId == readerId) &&
|
||||||
|
message.MessageViews.All(mv => mv.UserId != readerId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ public class MessageRemovingService : IMessageRemovingService
|
|||||||
|
|
||||||
private async Task<Result<Message,Error>> ValidateUserRecipientAsync(Message message, DeleteMessage deleteParams)
|
private async Task<Result<Message,Error>> ValidateUserRecipientAsync(Message message, DeleteMessage deleteParams)
|
||||||
{
|
{
|
||||||
if (deleteParams.DeleterId == message.RecipientId)
|
if (deleteParams.DeleterId == message.SenderId)
|
||||||
{
|
{
|
||||||
return await ForceRemoveAsync(message);
|
return await ForceRemoveAsync(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Govor.Application.Infrastructure.Common;
|
||||||
using Govor.Domain;
|
using Govor.Domain;
|
||||||
using Govor.Domain.Common;
|
using Govor.Domain.Common;
|
||||||
using Govor.Domain.Models.Users;
|
using Govor.Domain.Models.Users;
|
||||||
@@ -11,9 +12,13 @@ public class PushTokenService : IPushTokenService
|
|||||||
{
|
{
|
||||||
private readonly GovorDbContext _context;
|
private readonly GovorDbContext _context;
|
||||||
private readonly ILogger<PushTokenService> _logger;
|
private readonly ILogger<PushTokenService> _logger;
|
||||||
|
private readonly INowDateTimeProvider _nowDateTimeProvider;
|
||||||
|
|
||||||
public PushTokenService(GovorDbContext context, ILogger<PushTokenService> logger)
|
public PushTokenService(GovorDbContext context,
|
||||||
|
INowDateTimeProvider nowDateTimeProvider,
|
||||||
|
ILogger<PushTokenService> logger)
|
||||||
{
|
{
|
||||||
|
_nowDateTimeProvider = nowDateTimeProvider;
|
||||||
_context = context;
|
_context = context;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@@ -139,7 +144,7 @@ public class PushTokenService : IPushTokenService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var existingToken = await _context.UserPushTokens
|
var existingToken = await _context.UserPushTokens
|
||||||
.FirstOrDefaultAsync(t => t.Token == token);
|
.FirstOrDefaultAsync(t => t.Platform == platform && t.UserId == userId && t.UserSessionId == sessionId);
|
||||||
|
|
||||||
if (existingToken is null)
|
if (existingToken is null)
|
||||||
{
|
{
|
||||||
@@ -150,18 +155,18 @@ public class PushTokenService : IPushTokenService
|
|||||||
UserSessionId = sessionId,
|
UserSessionId = sessionId,
|
||||||
Token = token,
|
Token = token,
|
||||||
Platform = platform,
|
Platform = platform,
|
||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = _nowDateTimeProvider.Now
|
||||||
};
|
};
|
||||||
|
|
||||||
await _context.UserPushTokens.AddAsync(newToken);
|
await _context.UserPushTokens.AddAsync(newToken);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
existingToken.UserId = userId;
|
existingToken.UserId = userId;
|
||||||
existingToken.UserSessionId = sessionId;
|
existingToken.UserSessionId = sessionId;
|
||||||
existingToken.Platform = platform;
|
existingToken.Platform = platform;
|
||||||
existingToken.UpdatedAt = DateTime.UtcNow;
|
existingToken.Token = token;
|
||||||
|
existingToken.UpdatedAt = _nowDateTimeProvider.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Govor.Contracts.Requests.SignalR;
|
||||||
|
|
||||||
|
public class ReadMessageRequest
|
||||||
|
{
|
||||||
|
public Guid MessageId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using Govor.Domain.Models.Messages;
|
||||||
|
|
||||||
|
namespace Govor.Contracts.Responses.SignalR;
|
||||||
|
|
||||||
|
public class MessageReadResponse
|
||||||
|
{
|
||||||
|
public required Guid ViewId { get; set; }
|
||||||
|
public required Guid MessageId { get; set; }
|
||||||
|
public required Guid ReaderId { get; set; }
|
||||||
|
public required Guid RecipientId { get; set; }
|
||||||
|
public required DateTime WhenWas { get; set; }
|
||||||
|
public RecipientType RecipientType { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user