mirror of
https://github.com/Govor-team/Govor.git
synced 2026-07-21 19:54:55 +00:00
Refactor media attachments to use MediaFile entity
This commit introduces the MediaFile entity and updates the media attachments model, repositories, and related logic to reference MediaFile instead of storing file metadata directly in MediaAttachments. Controller, service, and SignalR request/response contracts are updated to support the new structure. Database configurations and validators are also adjusted accordingly. This refactor improves media management and normalization in the data model.
This commit is contained in:
@@ -329,8 +329,8 @@ public class FriendsServiceTests
|
|||||||
f.Status = FriendshipStatus.Pending;
|
f.Status = FriendshipStatus.Pending;
|
||||||
});
|
});
|
||||||
|
|
||||||
_usersRepositoryMock.Setup(f => f.FindByIdAsync(userId))
|
_friendshipsRepositoryMock.Setup(f => f.FindByUserIdAsync(userId))
|
||||||
.ReturnsAsync(user);
|
.ReturnsAsync(friendships);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _service.GetIncomingRequestsAsync(userId);
|
var result = await _service.GetIncomingRequestsAsync(userId);
|
||||||
@@ -346,8 +346,8 @@ public class FriendsServiceTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
|
|
||||||
_usersRepositoryMock
|
_friendshipsRepositoryMock
|
||||||
.Setup(r => r.FindByIdAsync(userId))
|
.Setup(r => r.FindByUserIdAsync(userId))
|
||||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
|
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Govor.Application.Interfaces;
|
using Govor.Application.Interfaces;
|
||||||
|
using Govor.Application.Interfaces.Medias;
|
||||||
using Govor.Contracts.Requests;
|
using Govor.Contracts.Requests;
|
||||||
using Govor.Core.Models;
|
using Govor.Core.Models;
|
||||||
using Govor.Core.Repositories.MediasAttachments;
|
using Govor.Core.Repositories.MediasAttachments;
|
||||||
@@ -13,12 +14,12 @@ namespace Govor.API.Controllers;
|
|||||||
public class MediaController : Controller
|
public class MediaController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<MediaController> _logger;
|
private readonly ILogger<MediaController> _logger;
|
||||||
private readonly IStorageService _storageService;
|
private readonly IMediaService _mediaService;
|
||||||
|
|
||||||
public MediaController(ILogger<MediaController> logger, IStorageService storageService)
|
public MediaController(ILogger<MediaController> logger, IMediaService mediaService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_storageService = storageService;
|
_mediaService = mediaService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("upload")]
|
[HttpPost("upload")]
|
||||||
@@ -27,11 +28,10 @@ public class MediaController : Controller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = await _storageService.SaveAsync(request.Data,request.FileName);
|
var result = await _mediaService.UploadMediaAsync(new Media(request.Data, request.FileName, request.Type,
|
||||||
var mediaId = Guid.NewGuid();
|
request.MimeType, request.EncryptedKey));
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
return Ok(mediaId);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
+13
-13
@@ -92,14 +92,14 @@ public class ChatsHub : Hub
|
|||||||
RecipientType: request.RecipientType,
|
RecipientType: request.RecipientType,
|
||||||
SendAt: DateTime.UtcNow,
|
SendAt: DateTime.UtcNow,
|
||||||
Media: request.MediaAttachments?.Select(f => new SendMedia(
|
Media: request.MediaAttachments?.Select(f => new SendMedia(
|
||||||
f.MediaId, f.EncryptedKey, f.Type, f.MimeType)) ?? Array.Empty<SendMedia>()
|
f.MediaId, f.EncryptedKey)) ?? Array.Empty<SendMedia>()
|
||||||
);
|
);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _messageService.SendMessageAsync(sendMessageParams);
|
var result = await _messageService.SendMessageAsync(sendMessageParams);
|
||||||
|
|
||||||
if (!result.IsSuccess || result.MessageId == Guid.Empty)
|
if (!result.IsSuccess || result.Message.Id == Guid.Empty)
|
||||||
{
|
{
|
||||||
_logger.LogError(result.Exception, "Failed to send message from {SenderId} to {RecipientId}. Error: {ErrorMessage}", senderId, request.RecipientId, result.Exception?.Message ?? "Unknown error");
|
_logger.LogError(result.Exception, "Failed to send message from {SenderId} to {RecipientId}. Error: {ErrorMessage}", senderId, request.RecipientId, result.Exception?.Message ?? "Unknown error");
|
||||||
if (result.Exception != null) throw result.Exception;
|
if (result.Exception != null) throw result.Exception;
|
||||||
@@ -108,14 +108,15 @@ public class ChatsHub : Hub
|
|||||||
|
|
||||||
var messageResponse = new UserMessageResponse // Assuming a response DTO
|
var messageResponse = new UserMessageResponse // Assuming a response DTO
|
||||||
{
|
{
|
||||||
MessageId = result.MessageId,
|
MessageId = result.Message.Id,
|
||||||
SenderId = senderId,
|
SenderId = result.Message.SenderId,
|
||||||
RecipientId = request.RecipientId,
|
RecipientId = result.Message.RecipientId,
|
||||||
RecipientType = request.RecipientType,
|
RecipientType = result.Message.RecipientType,
|
||||||
EncryptedContent = request.EncryptedContent,
|
EncryptedContent = result.Message.EncryptedContent,
|
||||||
SentAt = sendMessageParams.SendAt,
|
SentAt = result.Message.SentAt,
|
||||||
IsEdited = false,
|
IsEdited = false,
|
||||||
MediaAttachments = request.MediaAttachments,
|
MediaAttachments = result.Message.MediaAttachments
|
||||||
|
.Select(m => m.MediaFile).ToList(),
|
||||||
ReplyToMessageId = request.ReplyToMessageId
|
ReplyToMessageId = request.ReplyToMessageId
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,7 +135,7 @@ public class ChatsHub : Hub
|
|||||||
// Notify sender (confirmation) on their connection
|
// Notify sender (confirmation) on their connection
|
||||||
await Clients.Caller.SendAsync("MessageSent", messageResponse); // Or use "ReceiveMessage" if the sender should also just get it like anyone else
|
await Clients.Caller.SendAsync("MessageSent", messageResponse); // Or use "ReceiveMessage" if the sender should also just get it like anyone else
|
||||||
|
|
||||||
_logger.LogInformation("Message {MessageId} sent successfully from {SenderId} to {RecipientId} ({RecipientType})", result.MessageId, senderId, request.RecipientId, request.RecipientType);
|
_logger.LogInformation("Message {MessageId} sent successfully from {SenderId} to {RecipientId} ({RecipientType})", result.Message.Id, senderId, request.RecipientId, request.RecipientType);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -145,18 +146,17 @@ public class ChatsHub : Hub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Remove(Guid recipientId, Guid messageId)
|
public async Task Remove(RemoveMessageRequest request)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Edit(string newMessage, Guid messageId)
|
public async Task Edit(EditMessageRequest request)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Guid GetUserId(bool suppressException = false)
|
private Guid GetUserId(bool suppressException = false)
|
||||||
{
|
{
|
||||||
var userIdClaim = Context.User?.FindFirst("userId")?.Value;
|
var userIdClaim = Context.User?.FindFirst("userId")?.Value;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Govor.Core.Models;
|
||||||
|
|
||||||
|
namespace Govor.Application.Interfaces.Medias;
|
||||||
|
|
||||||
|
public interface IMediaService
|
||||||
|
{
|
||||||
|
public Task<MediaUploadResult> UploadMediaAsync(Media file);
|
||||||
|
public Task DeleteMediaAsync(Guid fileId);
|
||||||
|
public Task<MediaUploadResult> GetMediaAsync(string url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Media(byte[] Data, string FileName, MediaType Type, string MineType, string EncryptedKey);
|
||||||
|
|
||||||
|
public record MediaUploadResult(Guid? MediaId, string Url);
|
||||||
@@ -17,8 +17,8 @@ public interface IMessageService
|
|||||||
|
|
||||||
// Define specific result types for clarity, including original message for notifications if needed
|
// Define specific result types for clarity, including original message for notifications if needed
|
||||||
|
|
||||||
public record SendMessageResult(bool IsSuccess, Exception? Exception, Guid MessageId)
|
public record SendMessageResult(bool IsSuccess, Exception? Exception, Message Message)
|
||||||
: Result(IsSuccess, Exception, MessageId);
|
: Result(IsSuccess, Exception, Message.Id);
|
||||||
|
|
||||||
public record EditMessageResult(bool IsSuccess, Exception? Exception, Message? OriginalMessage)
|
public record EditMessageResult(bool IsSuccess, Exception? Exception, Message? OriginalMessage)
|
||||||
: Result(IsSuccess, Exception, OriginalMessage?.Id ?? Guid.Empty)
|
: Result(IsSuccess, Exception, OriginalMessage?.Id ?? Guid.Empty)
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ using Govor.Core.Models;
|
|||||||
|
|
||||||
namespace Govor.Application.Interfaces.Messages.Parameters;
|
namespace Govor.Application.Interfaces.Messages.Parameters;
|
||||||
|
|
||||||
public record SendMedia(Guid MediaId, string EncryptedKey, MediaType Type, string MimeType);
|
public record SendMedia(Guid MediaId, string EncryptedKey);
|
||||||
@@ -105,7 +105,8 @@ public class FriendsService : IFriendsService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var friendships = await _friendshipsRepository.FindByUserIdAsync(userId);
|
var friendships = await _friendshipsRepository.FindByUserIdAsync(userId);
|
||||||
return friendships.Where(f => f.AddresseeId == userId && f.Status == FriendshipStatus.Pending).ToList();
|
return friendships.Where(f => f.AddresseeId == userId && f.Status == FriendshipStatus.Pending).ToList()
|
||||||
|
?? new List<Friendship>();
|
||||||
}
|
}
|
||||||
catch (NotFoundByKeyException<Guid> ex)
|
catch (NotFoundByKeyException<Guid> ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Govor.Application.Interfaces;
|
||||||
|
using Govor.Application.Interfaces.Medias;
|
||||||
|
|
||||||
|
namespace Govor.Application.Services;
|
||||||
|
|
||||||
|
public class MediaService : IMediaService
|
||||||
|
{
|
||||||
|
private IStorageService _storageService;
|
||||||
|
|
||||||
|
public MediaService(IStorageService storageService)
|
||||||
|
{
|
||||||
|
_storageService = storageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<MediaUploadResult> UploadMediaAsync(Media file)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteMediaAsync(Guid fileId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<MediaUploadResult> GetMediaAsync(string url)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,10 +38,10 @@ public class MessageService : IMessageService
|
|||||||
// Validate recipient
|
// Validate recipient
|
||||||
if (sendParams.RecipientType == RecipientType.User)
|
if (sendParams.RecipientType == RecipientType.User)
|
||||||
{
|
{
|
||||||
if (!await _usersRepository.ExistsByIdAsync(sendParams.RecipientId)) // Changed to ExistsByIdAsync
|
if (!await _usersRepository.ExistsByIdAsync(sendParams.RecipientId))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Attempt to send message to non-existent user {RecipientId}", sendParams.RecipientId);
|
_logger.LogWarning("Attempt to send message to non-existent user {RecipientId}", sendParams.RecipientId);
|
||||||
return new SendMessageResult(false, new KeyNotFoundException($"Recipient user {sendParams.RecipientId} not found."), Guid.Empty);
|
return new SendMessageResult(false, new KeyNotFoundException($"Recipient user {sendParams.RecipientId} not found."), default);
|
||||||
}
|
}
|
||||||
// Verify friendship for private messages
|
// Verify friendship for private messages
|
||||||
await _verifyFriendship.VerifyAsync(sendParams.FromUserId, sendParams.RecipientId);
|
await _verifyFriendship.VerifyAsync(sendParams.FromUserId, sendParams.RecipientId);
|
||||||
@@ -51,7 +51,7 @@ public class MessageService : IMessageService
|
|||||||
if (!_groupsRepository.Exists(sendParams.RecipientId))
|
if (!_groupsRepository.Exists(sendParams.RecipientId))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Attempt to send message to non-existent group {GroupId}", sendParams.RecipientId);
|
_logger.LogWarning("Attempt to send message to non-existent group {GroupId}", sendParams.RecipientId);
|
||||||
return new SendMessageResult(false, new KeyNotFoundException($"Recipient group {sendParams.RecipientId} not found."), Guid.Empty);
|
return new SendMessageResult(false, new KeyNotFoundException($"Recipient group {sendParams.RecipientId} not found."), default);
|
||||||
}
|
}
|
||||||
// TODO: Optionally, verify if sender is a member of the group
|
// TODO: Optionally, verify if sender is a member of the group
|
||||||
// bool isMember = await _groupsRepository.IsUserMemberOfGroupAsync(sendParams.FromUserId, sendParams.RecipientId);
|
// bool isMember = await _groupsRepository.IsUserMemberOfGroupAsync(sendParams.FromUserId, sendParams.RecipientId);
|
||||||
@@ -64,7 +64,7 @@ public class MessageService : IMessageService
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogError("Invalid recipient type specified: {RecipientType}", sendParams.RecipientType);
|
_logger.LogError("Invalid recipient type specified: {RecipientType}", sendParams.RecipientType);
|
||||||
return new SendMessageResult(false, new ArgumentException("Invalid recipient type."), Guid.Empty);
|
return new SendMessageResult(false, new ArgumentException("Invalid recipient type."), default);
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageId = Guid.NewGuid();
|
var messageId = Guid.NewGuid();
|
||||||
@@ -80,23 +80,20 @@ public class MessageService : IMessageService
|
|||||||
ReplyToMessageId = sendParams.ReplyToMessageId,
|
ReplyToMessageId = sendParams.ReplyToMessageId,
|
||||||
MediaAttachments = sendParams.Media?.Select(m => new MediaAttachments
|
MediaAttachments = sendParams.Media?.Select(m => new MediaAttachments
|
||||||
{
|
{
|
||||||
Id = m.MediaId, // Assuming SendMedia provides Id or it's generated here/by repo
|
Id = Guid.NewGuid(),
|
||||||
MessageId = messageId,
|
MessageId = messageId,
|
||||||
Type = m.Type,
|
MediaFileId = m.MediaId
|
||||||
MimeType = m.MimeType,
|
|
||||||
EncryptedKey = m.EncryptedKey,
|
|
||||||
// FilePath will be set by storage service or handled by repository if media ID is pre-generated
|
|
||||||
}).ToList() ?? new List<MediaAttachments>()
|
}).ToList() ?? new List<MediaAttachments>()
|
||||||
};
|
};
|
||||||
|
|
||||||
await _messagesRepository.AddAsync(message);
|
await _messagesRepository.AddAsync(message);
|
||||||
_logger.LogInformation("Message {MessageId} from {SenderId} to {RecipientId} ({RecipientType}) saved successfully.", messageId, sendParams.FromUserId, sendParams.RecipientId, sendParams.RecipientType);
|
_logger.LogInformation("Message {MessageId} from {SenderId} to {RecipientId} ({RecipientType}) saved successfully.", messageId, sendParams.FromUserId, sendParams.RecipientId, sendParams.RecipientType);
|
||||||
return new SendMessageResult(true, null, messageId);
|
return new SendMessageResult(true, null, message);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error sending message from {SenderId} to {RecipientId} ({RecipientType})", sendParams.FromUserId, sendParams.RecipientId, sendParams.RecipientType);
|
_logger.LogError(ex, "Error sending message from {SenderId} to {RecipientId} ({RecipientType})", sendParams.FromUserId, sendParams.RecipientId, sendParams.RecipientType);
|
||||||
return new SendMessageResult(false, ex, Guid.Empty);
|
return new SendMessageResult(false, ex, default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +130,12 @@ public class MessageService : IMessageService
|
|||||||
|
|
||||||
await _messagesRepository.UpdateAsync(message);
|
await _messagesRepository.UpdateAsync(message);
|
||||||
_logger.LogInformation("Message {MessageId} edited successfully by user {EditorId}", editParams.MessageId, editParams.EditorId);
|
_logger.LogInformation("Message {MessageId} edited successfully by user {EditorId}", editParams.MessageId, editParams.EditorId);
|
||||||
return new EditMessageResult(true, null, originalMessageForNotification);
|
return new EditMessageResult(true, default, originalMessageForNotification);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error editing message {MessageId} by user {EditorId}", editParams.MessageId, editParams.EditorId);
|
_logger.LogError(ex, "Error editing message {MessageId} by user {EditorId}", editParams.MessageId, editParams.EditorId);
|
||||||
return new EditMessageResult(false, ex, null);
|
return new EditMessageResult(false, ex, default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +156,7 @@ public class MessageService : IMessageService
|
|||||||
// }
|
// }
|
||||||
// } else {
|
// } else {
|
||||||
_logger.LogWarning("User {DeleterId} attempted to delete message {MessageId} not sent by them (sender was {SenderId})", deleteParams.DeleterId, deleteParams.MessageId, message.SenderId);
|
_logger.LogWarning("User {DeleterId} attempted to delete message {MessageId} not sent by them (sender was {SenderId})", deleteParams.DeleterId, deleteParams.MessageId, message.SenderId);
|
||||||
return new DeleteMessageResult(false, new UnauthorizedAccessException("User is not authorized to delete this message."), null);
|
return new DeleteMessageResult(false, new UnauthorizedAccessException("User is not authorized to delete this message."), default);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,12 +171,12 @@ public class MessageService : IMessageService
|
|||||||
await _messagesRepository.RemoveAsync(deleteParams.MessageId);
|
await _messagesRepository.RemoveAsync(deleteParams.MessageId);
|
||||||
|
|
||||||
_logger.LogInformation("Message {MessageId} deleted successfully by user {DeleterId}", deleteParams.MessageId, deleteParams.DeleterId);
|
_logger.LogInformation("Message {MessageId} deleted successfully by user {DeleterId}", deleteParams.MessageId, deleteParams.DeleterId);
|
||||||
return new DeleteMessageResult(true, null, originalMessageForNotification);
|
return new DeleteMessageResult(true, default, originalMessageForNotification);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting message {MessageId} by user {DeleterId}", deleteParams.MessageId, deleteParams.DeleterId);
|
_logger.LogError(ex, "Error deleting message {MessageId} by user {DeleterId}", deleteParams.MessageId, deleteParams.DeleterId);
|
||||||
return new DeleteMessageResult(false, ex, null);
|
return new DeleteMessageResult(false, ex, default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Govor.Contracts.Requests.SignalR;
|
||||||
|
|
||||||
|
public class EditMessageRequest
|
||||||
|
{
|
||||||
|
public Guid MessageId { get; set; }
|
||||||
|
public string NewEncryptedContent { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -6,6 +6,4 @@ public record MediaReference
|
|||||||
{
|
{
|
||||||
public Guid MediaId { get; init; }
|
public Guid MediaId { get; init; }
|
||||||
public string EncryptedKey { get; init; } = string.Empty;
|
public string EncryptedKey { get; init; } = string.Empty;
|
||||||
public MediaType Type { get; init; }
|
|
||||||
public string MimeType { get; init; } = string.Empty;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Govor.Contracts.Requests.SignalR;
|
||||||
|
|
||||||
|
public class RemoveMessageRequest
|
||||||
|
{
|
||||||
|
public Guid MessageId { get; set; }
|
||||||
|
}
|
||||||
@@ -13,5 +13,5 @@ public record UserMessageResponse
|
|||||||
public Guid? ReplyToMessageId { get; init; }
|
public Guid? ReplyToMessageId { get; init; }
|
||||||
public DateTime SentAt { get; init; }
|
public DateTime SentAt { get; init; }
|
||||||
public bool IsEdited { get; init; } = false;
|
public bool IsEdited { get; init; } = false;
|
||||||
public List<MediaReference> MediaAttachments { get; init; } = new List<MediaReference>();
|
public List<MediaFile> MediaAttachments { get; init; } = new List<MediaFile>();
|
||||||
}
|
}
|
||||||
@@ -15,10 +15,8 @@ public class MediaAttachmentsValidator : IObjectValidator<MediaAttachments>
|
|||||||
throw new ArgumentException("Id cannot be empty", nameof(attachments));
|
throw new ArgumentException("Id cannot be empty", nameof(attachments));
|
||||||
if(attachments.MessageId == Guid.Empty)
|
if(attachments.MessageId == Guid.Empty)
|
||||||
throw new ArgumentException("MessageId cannot be empty", nameof(attachments));
|
throw new ArgumentException("MessageId cannot be empty", nameof(attachments));
|
||||||
if(string.IsNullOrWhiteSpace(attachments.FilePath))
|
if(string.IsNullOrWhiteSpace(attachments.MediaFile.Url))
|
||||||
throw new ArgumentException("File path cannot be empty", nameof(attachments));
|
throw new ArgumentException("File path cannot be empty", nameof(attachments));
|
||||||
if(string.IsNullOrWhiteSpace(attachments.EncryptedKey))
|
|
||||||
throw new ArgumentException("Encrypted key cannot be empty", nameof(attachments));
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,22 +4,16 @@ public class MediaAttachments
|
|||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid MessageId { get; set; }
|
public Guid MessageId { get; set; }
|
||||||
public MediaType Type { get; set; }
|
public Guid MediaFileId { get; set; }
|
||||||
public string FilePath { get; set; } = string.Empty; // local path in filesystem
|
public Message Message { get; set; }
|
||||||
public string MimeType { get; set; } = string.Empty;
|
public MediaFile MediaFile { get; set; }
|
||||||
public string? EncryptedKey { get; set; }
|
|
||||||
public Message Message { get; set; } = null!;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is not MediaAttachments other) return false;
|
if (obj is not MediaAttachments other) return false;
|
||||||
|
|
||||||
return Id == other.Id &&
|
return Id == other.Id &&
|
||||||
MessageId == other.MessageId &&
|
MessageId == other.MessageId &&
|
||||||
EncryptedKey == other.EncryptedKey &&
|
MediaFileId == other.MediaFileId;
|
||||||
Type == other.Type &&
|
|
||||||
FilePath == other.FilePath &&
|
|
||||||
MimeType == other.MimeType;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Govor.Core.Models;
|
||||||
|
|
||||||
|
public class MediaFile
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public MediaType MediaType { get; set; }
|
||||||
|
public string MineType { get; set; }
|
||||||
|
public DateTime DateCreated { get; set; }
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ public class Message
|
|||||||
public List<MessageView> MessageViews { get; set; } = new List<MessageView>();
|
public List<MessageView> MessageViews { get; set; } = new List<MessageView>();
|
||||||
|
|
||||||
public Guid? ReplyToMessageId { get; set; }
|
public Guid? ReplyToMessageId { get; set; }
|
||||||
public Message? ReplyToMessage { get; set; } // navigation
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,17 +10,14 @@ public class MediaAttachmentsConfiguration : IEntityTypeConfiguration<MediaAttac
|
|||||||
{
|
{
|
||||||
builder.HasKey(ma => ma.Id);
|
builder.HasKey(ma => ma.Id);
|
||||||
|
|
||||||
builder.Property(ma => ma.FilePath)
|
builder.HasOne(ma => ma.Message)
|
||||||
.IsRequired();
|
.WithMany(m => m.MediaAttachments)
|
||||||
|
.HasForeignKey(ma => ma.MessageId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
builder.Property(ma => ma.MimeType)
|
builder.HasOne(ma => ma.MediaFile)
|
||||||
.IsRequired();
|
.WithMany()
|
||||||
|
.HasForeignKey(ma => ma.MediaFileId)
|
||||||
builder.Property(ma => ma.EncryptedKey)
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
.HasMaxLength(512); // зависит от шифра
|
|
||||||
|
|
||||||
builder.Property(ma => ma.Type)
|
|
||||||
.HasConversion<string>() // enum as string (e.g., "Image")
|
|
||||||
.IsRequired();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Govor.Core.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace Govor.Data.Configurations;
|
||||||
|
|
||||||
|
public class MediaFileConfiguration : IEntityTypeConfiguration<MediaFile>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<MediaFile> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(ma => ma.Id);
|
||||||
|
|
||||||
|
builder.Property(mf => mf.Url)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(mf => mf.MediaType)
|
||||||
|
.HasConversion<string>() // enum as string (e.g., "Image")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(ma => ma.MineType)
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(mf => mf.DateCreated)
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,11 +25,6 @@ public class MessagesConfiguration : IEntityTypeConfiguration<Message>
|
|||||||
.HasForeignKey(mv => mv.MessageId)
|
.HasForeignKey(mv => mv.MessageId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
builder.HasOne(m => m.ReplyToMessage)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(m => m.ReplyToMessageId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.Property(m => m.EncryptedContent)
|
builder.Property(m => m.EncryptedContent)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public class GovorDbContext(DbContextOptions<GovorDbContext> options) : DbContex
|
|||||||
public virtual DbSet<MessageView> MessageViews { get; set; }
|
public virtual DbSet<MessageView> MessageViews { get; set; }
|
||||||
public virtual DbSet<MessageReaction> MessageReactions { get; set; }
|
public virtual DbSet<MessageReaction> MessageReactions { get; set; }
|
||||||
public virtual DbSet<MediaAttachments> MediaAttachments { get; set; }
|
public virtual DbSet<MediaAttachments> MediaAttachments { get; set; }
|
||||||
|
public virtual DbSet<MediaFile> MediaFiles { get; set; }
|
||||||
|
|
||||||
public virtual DbSet<ChatGroup> ChatGroups { get; set; }
|
public virtual DbSet<ChatGroup> ChatGroups { get; set; }
|
||||||
public virtual DbSet<GroupMembership> GroupMemberships { get; set; }
|
public virtual DbSet<GroupMembership> GroupMemberships { get; set; }
|
||||||
@@ -33,6 +34,7 @@ public class GovorDbContext(DbContextOptions<GovorDbContext> options) : DbContex
|
|||||||
modelBuilder.ApplyConfiguration(new MessageReactionConfiguration());
|
modelBuilder.ApplyConfiguration(new MessageReactionConfiguration());
|
||||||
modelBuilder.ApplyConfiguration(new MediaAttachmentsConfiguration());
|
modelBuilder.ApplyConfiguration(new MediaAttachmentsConfiguration());
|
||||||
modelBuilder.ApplyConfiguration(new MessageViewConfiguration());
|
modelBuilder.ApplyConfiguration(new MessageViewConfiguration());
|
||||||
|
modelBuilder.ApplyConfiguration(new MediaFileConfiguration());
|
||||||
|
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,9 +77,7 @@ public class MediaAttachmentsRepository : IMediaAttachmentsRepository
|
|||||||
.ExecuteUpdateAsync(u => u
|
.ExecuteUpdateAsync(u => u
|
||||||
.SetProperty(m => m.MessageId, attachments.MessageId)
|
.SetProperty(m => m.MessageId, attachments.MessageId)
|
||||||
.SetProperty(m => m.Message, attachments.Message)
|
.SetProperty(m => m.Message, attachments.Message)
|
||||||
.SetProperty(m => m.FilePath, attachments.FilePath)
|
.SetProperty(m => m.MediaFileId, attachments.MediaFileId)
|
||||||
.SetProperty(m => m.MimeType, attachments.MimeType)
|
|
||||||
.SetProperty(m => m.EncryptedKey, attachments.EncryptedKey)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rowsAffected == 0)
|
if (rowsAffected == 0)
|
||||||
@@ -121,11 +119,8 @@ public class MediaAttachmentsRepository : IMediaAttachmentsRepository
|
|||||||
|
|
||||||
return _context.MediaAttachments.Any(
|
return _context.MediaAttachments.Any(
|
||||||
e => e.Id == attachments.Id &&
|
e => e.Id == attachments.Id &&
|
||||||
e.EncryptedKey == attachments.EncryptedKey &&
|
|
||||||
e.MimeType == attachments.MimeType &&
|
|
||||||
e.FilePath == attachments.FilePath &&
|
|
||||||
e.MessageId == attachments.MessageId &&
|
e.MessageId == attachments.MessageId &&
|
||||||
e.Type == attachments.Type
|
e.MediaFileId == attachments.MediaFileId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,8 @@ public class MessagesRepository : IMessagesRepository
|
|||||||
{
|
{
|
||||||
return await _context.Messages
|
return await _context.Messages
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.ReplyToMessage)
|
.Include(m => m.MediaAttachments)
|
||||||
|
.ThenInclude(m => m.MediaFile)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.ToListOrThrowIfEmpty(new NotFoundException("No messages found in the database"));
|
.ToListOrThrowIfEmpty(new NotFoundException("No messages found in the database"));
|
||||||
}
|
}
|
||||||
@@ -30,7 +31,6 @@ public class MessagesRepository : IMessagesRepository
|
|||||||
{
|
{
|
||||||
return await _context.Messages
|
return await _context.Messages
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.ReplyToMessage)
|
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(m => m.Id == messageId)
|
.FirstOrDefaultAsync(m => m.Id == messageId)
|
||||||
?? throw new NotFoundByKeyException<Guid>(messageId, "Message with given id does not exist");
|
?? throw new NotFoundByKeyException<Guid>(messageId, "Message with given id does not exist");
|
||||||
@@ -40,7 +40,8 @@ public class MessagesRepository : IMessagesRepository
|
|||||||
{
|
{
|
||||||
return await _context.Messages
|
return await _context.Messages
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.ReplyToMessage)
|
.Include(m => m.MediaAttachments)
|
||||||
|
.ThenInclude(m => m.MediaFile)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Where(m => m.SenderId == senderId)
|
.Where(m => m.SenderId == senderId)
|
||||||
.ToListOrThrowIfEmpty(new NotFoundByKeyException<Guid>(senderId, "Messages with given sender id do not exist"));
|
.ToListOrThrowIfEmpty(new NotFoundByKeyException<Guid>(senderId, "Messages with given sender id do not exist"));
|
||||||
@@ -50,7 +51,8 @@ public class MessagesRepository : IMessagesRepository
|
|||||||
{
|
{
|
||||||
return await _context.Messages
|
return await _context.Messages
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.ReplyToMessage)
|
.Include(m => m.MediaAttachments)
|
||||||
|
.ThenInclude(m => m.MediaFile)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Where(m => m.RecipientId == receiverId)
|
.Where(m => m.RecipientId == receiverId)
|
||||||
.ToListOrThrowIfEmpty(new NotFoundByKeyException<Guid>(receiverId, "Messages with given recipient id do not exist"));
|
.ToListOrThrowIfEmpty(new NotFoundByKeyException<Guid>(receiverId, "Messages with given recipient id do not exist"));
|
||||||
@@ -60,7 +62,8 @@ public class MessagesRepository : IMessagesRepository
|
|||||||
{
|
{
|
||||||
return await _context.Messages
|
return await _context.Messages
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.ReplyToMessage)
|
.Include(m => m.MediaAttachments)
|
||||||
|
.ThenInclude(m => m.MediaFile)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Where(m => m.SenderId == senderId
|
.Where(m => m.SenderId == senderId
|
||||||
&& m.RecipientId == receiverId
|
&& m.RecipientId == receiverId
|
||||||
@@ -72,7 +75,8 @@ public class MessagesRepository : IMessagesRepository
|
|||||||
{
|
{
|
||||||
return await _context.Messages
|
return await _context.Messages
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.ReplyToMessage)
|
.Include(m => m.MediaAttachments)
|
||||||
|
.ThenInclude(m => m.MediaFile)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Where(m => m.SentAt == date)
|
.Where(m => m.SentAt == date)
|
||||||
.ToListOrThrowIfEmpty(new NotFoundByKeyException<DateTime>(date, "Messages sent at date do not exist"));
|
.ToListOrThrowIfEmpty(new NotFoundByKeyException<DateTime>(date, "Messages sent at date do not exist"));
|
||||||
|
|||||||
Reference in New Issue
Block a user