Add private chat support and group model enhancements

Introduces repositories, validators, and integration tests for private chats. Refactors group-related models to support invitations and memberships, updates group repository interfaces and implementations, and enhances message service logic to handle private chat creation and retrieval. Also registers new services and validators in DI, and updates related tests.
This commit is contained in:
Artemy
2025-07-07 13:55:29 +07:00
parent 66819a015a
commit 2cb6a93060
21 changed files with 454 additions and 54 deletions
@@ -0,0 +1,7 @@
namespace Govor.API.Tests.IntegrationTests.EF.Repositories;
[TestFixture]
public class PrivateChatsRepositoryTests
{
}
@@ -0,0 +1,35 @@
using AutoFixture;
using Govor.API.Hubs;
using Govor.Application.Interfaces.Messages;
using Microsoft.Extensions.Logging;
using Moq;
namespace Govor.API.Tests.IntegrationTests.Hubs;
[TestFixture]
public class ChatsHubTests
{
private Mock<ILogger<ChatsHub>> _loggerMock;
private Mock<IMessageService> _messageServiceMock;
private Fixture _fixture;
private ChatsHub _chatsHub;
[SetUp]
public void SetUp()
{
_fixture = new Fixture();
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
_messageServiceMock = new Mock<IMessageService>();
_loggerMock = new Mock<ILogger<ChatsHub>>();
_chatsHub = new ChatsHub(
_loggerMock.Object,
_messageServiceMock.Object
);
}
// Test for Send action
}
@@ -0,0 +1,7 @@
namespace Govor.API.Tests.UnitTests.Infrastructure.Validators;
[TestFixture]
public class PrivateChatValidatorTests
{
}
@@ -5,6 +5,7 @@ using Govor.Application.Services;
using Govor.Core.Models; using Govor.Core.Models;
using Govor.Core.Repositories.Groups; using Govor.Core.Repositories.Groups;
using Govor.Core.Repositories.Messages; using Govor.Core.Repositories.Messages;
using Govor.Core.Repositories.PrivateChats;
using Govor.Core.Repositories.Users; using Govor.Core.Repositories.Users;
using Govor.Data.Repositories.Exceptions; using Govor.Data.Repositories.Exceptions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -19,6 +20,7 @@ public class MessageServiceTests
private Mock<IUsersRepository> _mockUsersRepo; private Mock<IUsersRepository> _mockUsersRepo;
private Mock<IGroupsRepository> _mockGroupsRepo; private Mock<IGroupsRepository> _mockGroupsRepo;
private Mock<IVerifyFriendship> _mockVerifyFriendship; private Mock<IVerifyFriendship> _mockVerifyFriendship;
private Mock<IPrivateChatsRepository> _mockPrivateChats;
private Mock<ILogger<MessageService>> _mockLogger; private Mock<ILogger<MessageService>> _mockLogger;
private MessageService _messageService; private MessageService _messageService;
@@ -29,6 +31,7 @@ public class MessageServiceTests
_mockUsersRepo = new Mock<IUsersRepository>(); _mockUsersRepo = new Mock<IUsersRepository>();
_mockGroupsRepo = new Mock<IGroupsRepository>(); _mockGroupsRepo = new Mock<IGroupsRepository>();
_mockVerifyFriendship = new Mock<IVerifyFriendship>(); _mockVerifyFriendship = new Mock<IVerifyFriendship>();
_mockPrivateChats = new Mock<IPrivateChatsRepository>();
_mockLogger = new Mock<ILogger<MessageService>>(); _mockLogger = new Mock<ILogger<MessageService>>();
_messageService = new MessageService( _messageService = new MessageService(
@@ -36,6 +39,7 @@ public class MessageServiceTests
_mockUsersRepo.Object, _mockUsersRepo.Object,
_mockGroupsRepo.Object, _mockGroupsRepo.Object,
_mockVerifyFriendship.Object, _mockVerifyFriendship.Object,
_mockPrivateChats.Object,
_mockLogger.Object); _mockLogger.Object);
} }
@@ -59,7 +63,8 @@ public class MessageServiceTests
_mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true); _mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true);
_mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).Returns(Task.CompletedTask); _mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).Returns(Task.CompletedTask);
_mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny<Message>())).Returns(Task.CompletedTask); _mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny<Message>())).Returns(Task.CompletedTask);
_mockPrivateChats.Setup(c => c.Exist(senderId, recipientId)).Returns(true);
_mockPrivateChats.Setup(c => c.GetByMembersAsync(senderId, recipientId)).ReturnsAsync(new PrivateChat(){Id = recipientId});
// Act // Act
var result = await _messageService.SendMessageAsync(sendMessageParams); var result = await _messageService.SendMessageAsync(sendMessageParams);
// Assert // Assert
@@ -90,7 +95,7 @@ public class MessageServiceTests
new List<SendMedia>()); new List<SendMedia>());
_mockGroupsRepo.Setup(r => r.Exists(groupId)).Returns(true); _mockGroupsRepo.Setup(r => r.Exists(groupId)).Returns(true);
_mockGroupsRepo.Setup(r => r.IsUserMemberOfGroupAsync(senderId, groupId)).Returns(true); _mockGroupsRepo.Setup(r => r.IsUserMemberOfGroupAsync(senderId, groupId)).ReturnsAsync(true);
_mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny<Message>())).Returns(Task.CompletedTask); _mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny<Message>())).Returns(Task.CompletedTask);
// Act // Act
@@ -130,7 +135,7 @@ public class MessageServiceTests
Assert.That(result.IsSuccess, Is.EqualTo(false)); Assert.That(result.IsSuccess, Is.EqualTo(false));
Assert.That(result.Exception, Is.Not.Null); Assert.That(result.Exception, Is.Not.Null);
Assert.That(result.Exception,Is.TypeOf<KeyNotFoundException>()); Assert.That(result.Exception,Is.TypeOf<KeyNotFoundException>());
Assert.That(result.MessageId, Is.EqualTo(Guid.Empty)); Assert.That(result.Message, Is.Null);
} }
[Test] [Test]
@@ -160,9 +165,11 @@ public class MessageServiceTests
Assert.That(result.IsSuccess, Is.EqualTo(false)); Assert.That(result.IsSuccess, Is.EqualTo(false));
Assert.That(result.Exception, Is.Not.Null); Assert.That(result.Exception, Is.Not.Null);
Assert.That(result.Exception,Is.TypeOf<FriendshipException>()); Assert.That(result.Exception,Is.TypeOf<FriendshipException>());
Assert.That(result.MessageId, Is.EqualTo(Guid.Empty)); Assert.That(result.Message, Is.Null);
} }
// Test for EditMessageAsync action // Test for EditMessageAsync action
[Test] [Test]
public async Task EditMessageAsync_Success() public async Task EditMessageAsync_Success()
@@ -17,6 +17,7 @@ using Govor.Core.Repositories.Groups;
using Govor.Core.Repositories.Invaites; using Govor.Core.Repositories.Invaites;
using Govor.Core.Repositories.MediasAttachments; using Govor.Core.Repositories.MediasAttachments;
using Govor.Core.Repositories.Messages; using Govor.Core.Repositories.Messages;
using Govor.Core.Repositories.PrivateChats;
using Govor.Core.Repositories.Users; using Govor.Core.Repositories.Users;
using Govor.Data; using Govor.Data;
using Govor.Data.Repositories; using Govor.Data.Repositories;
@@ -61,6 +62,7 @@ public static class ConfigurationProgramExtensions
services.AddScoped<IAdminsRepository, AdminsRepository>(); services.AddScoped<IAdminsRepository, AdminsRepository>();
services.AddScoped<IMediaAttachmentsRepository, MediaAttachmentsRepository>(); services.AddScoped<IMediaAttachmentsRepository, MediaAttachmentsRepository>();
services.AddScoped<IFriendshipsRepository, FriendshipsRepository>(); services.AddScoped<IFriendshipsRepository, FriendshipsRepository>();
services.AddScoped<IPrivateChatsRepository, PrivateChatsRepository>();
services.AddScoped<IGroupsRepository, GroupRepository>(); services.AddScoped<IGroupsRepository, GroupRepository>();
} }
@@ -72,6 +74,7 @@ public static class ConfigurationProgramExtensions
services.AddScoped<IObjectValidator<Admin>, AdminValidator>(); services.AddScoped<IObjectValidator<Admin>, AdminValidator>();
services.AddScoped<IObjectValidator<Invitation>, InvitationValidator>(); services.AddScoped<IObjectValidator<Invitation>, InvitationValidator>();
services.AddScoped<IObjectValidator<Friendship>, FriendshipValidator>(); services.AddScoped<IObjectValidator<Friendship>, FriendshipValidator>();
services.AddScoped<IObjectValidator<PrivateChat>, PrivateChatValidator>();
} }
public static void AddGovorDbContext(this IServiceCollection services, IConfiguration configuration) public static void AddGovorDbContext(this IServiceCollection services, IConfiguration configuration)
@@ -18,7 +18,7 @@ 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, Message Message) public record SendMessageResult(bool IsSuccess, Exception? Exception, Message Message)
: Result(IsSuccess, Exception, Message.Id); : Result(IsSuccess, Exception, Message?.Id ?? Guid.Empty);
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)
+37 -9
View File
@@ -4,6 +4,7 @@ using Govor.Application.Interfaces.Messages.Parameters;
using Govor.Core.Models; using Govor.Core.Models;
using Govor.Core.Repositories.Groups; using Govor.Core.Repositories.Groups;
using Govor.Core.Repositories.Messages; using Govor.Core.Repositories.Messages;
using Govor.Core.Repositories.PrivateChats;
using Govor.Core.Repositories.Users; using Govor.Core.Repositories.Users;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -14,6 +15,7 @@ public class MessageService : IMessageService
private readonly IMessagesRepository _messagesRepository; private readonly IMessagesRepository _messagesRepository;
private readonly IUsersRepository _usersRepository; // For validating user recipients private readonly IUsersRepository _usersRepository; // For validating user recipients
private readonly IGroupsRepository _groupsRepository; // For validating group recipients and fetching members private readonly IGroupsRepository _groupsRepository; // For validating group recipients and fetching members
private readonly IPrivateChatsRepository _privateChats;
private readonly IVerifyFriendship _verifyFriendship; // For private messages private readonly IVerifyFriendship _verifyFriendship; // For private messages
private readonly ILogger<MessageService> _logger; private readonly ILogger<MessageService> _logger;
@@ -22,17 +24,20 @@ public class MessageService : IMessageService
IUsersRepository usersRepository, IUsersRepository usersRepository,
IGroupsRepository groupsRepository, IGroupsRepository groupsRepository,
IVerifyFriendship verifyFriendship, IVerifyFriendship verifyFriendship,
IPrivateChatsRepository privateChats,
ILogger<MessageService> logger) ILogger<MessageService> logger)
{ {
_messagesRepository = messagesRepository; _messagesRepository = messagesRepository;
_usersRepository = usersRepository; _usersRepository = usersRepository;
_groupsRepository = groupsRepository; _groupsRepository = groupsRepository;
_verifyFriendship = verifyFriendship; _verifyFriendship = verifyFriendship;
_privateChats = privateChats;
_logger = logger; _logger = logger;
} }
public async Task<SendMessageResult> SendMessageAsync(SendMessage sendParams) public async Task<SendMessageResult> SendMessageAsync(SendMessage sendParams)
{ {
Guid recipientId;
try try
{ {
// Validate recipient // Validate recipient
@@ -45,6 +50,7 @@ public class MessageService : IMessageService
} }
// Verify friendship for private messages // Verify friendship for private messages
await _verifyFriendship.VerifyAsync(sendParams.FromUserId, sendParams.RecipientId); await _verifyFriendship.VerifyAsync(sendParams.FromUserId, sendParams.RecipientId);
recipientId = await GetPrivateChatsIdAsync(sendParams.FromUserId, sendParams.RecipientId);
} }
else if (sendParams.RecipientType == RecipientType.Group) else if (sendParams.RecipientType == RecipientType.Group)
{ {
@@ -54,25 +60,28 @@ public class MessageService : IMessageService
return new SendMessageResult(false, new KeyNotFoundException($"Recipient group {sendParams.RecipientId} not found."), default); 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);
// if (!isMember) if (!isMember)
// { {
// _logger.LogWarning("User {UserId} attempted to send message to group {GroupId} but is not a member", sendParams.FromUserId, sendParams.RecipientId); _logger.LogWarning("User {UserId} attempted to send message to group {GroupId} but is not a member", sendParams.FromUserId, sendParams.RecipientId);
// return new SendMessageResult(false, new UnauthorizedAccessException("Sender is not a member of the group."), Guid.Empty); return new SendMessageResult(false, new UnauthorizedAccessException("Sender is not a member of the group."), default);
// } }
recipientId = sendParams.RecipientId;
} }
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."), default); return new SendMessageResult(false, new ArgumentException("Invalid recipient type."), default);
} }
var messageId = Guid.NewGuid(); var messageId = Guid.NewGuid();
var message = new Message var message = new Message
{ {
Id = messageId, Id = messageId,
SenderId = sendParams.FromUserId, SenderId = sendParams.FromUserId,
RecipientId = sendParams.RecipientId, RecipientId = recipientId,
RecipientType = sendParams.RecipientType, RecipientType = sendParams.RecipientType,
EncryptedContent = sendParams.EncryptContent, EncryptedContent = sendParams.EncryptContent,
SentAt = sendParams.SendAt, SentAt = sendParams.SendAt,
@@ -97,6 +106,24 @@ public class MessageService : IMessageService
} }
} }
private async Task<Guid> GetPrivateChatsIdAsync(Guid userA, Guid userB)
{
Guid recipientId;
// Makes new PrivateChat if not exist
if (!_privateChats.Exist(userA, userB))
{
recipientId = Guid.NewGuid();
await _privateChats.AddAsync(new PrivateChat()
{ Id = recipientId, UserAId = userA, UserBId = userB });
}
else
{
recipientId = (await _privateChats.GetByMembersAsync(userA, userB)).Id;
}
return recipientId;
}
public async Task<EditMessageResult> EditMessageAsync(EditMessage editParams) public async Task<EditMessageResult> EditMessageAsync(EditMessage editParams)
{ {
try try
@@ -109,7 +136,8 @@ public class MessageService : IMessageService
return new EditMessageResult(false, new UnauthorizedAccessException("User is not authorized to edit this message."), null); return new EditMessageResult(false, new UnauthorizedAccessException("User is not authorized to edit this message."), null);
} }
// TODO: Add a time limit for editing messages? e.g., if (message.SentAt < DateTime.UtcNow.AddMinutes(-15)) throw new Exception("Edit time limit exceeded"); /*if (message.SentAt < DateTime.UtcNow.AddMinutes(-15))
throw new Exception("Edit time limit exceeded");*/
var originalMessageForNotification = new Message var originalMessageForNotification = new Message
{ {
@@ -0,0 +1,38 @@
using Govor.Core.Models;
namespace Govor.Core.Infrastructure.Validators;
public class PrivateChatValidator : IObjectValidator<PrivateChat>
{
public void Validate(PrivateChat chat)
{
try
{
if(chat is null)
throw new ArgumentNullException(nameof(chat));
if(chat.Id == Guid.Empty)
throw new ArgumentException("Id cannot be empty", nameof(chat));
if(chat.UserAId == Guid.Empty)
throw new ArgumentException("UserAId cannot be empty", nameof(chat.UserAId));
if(chat.UserBId == Guid.Empty)
throw new ArgumentException("UserBId cannot be empty", nameof(chat.UserBId));
}
catch (Exception ex)
{
throw new InvalidObjectException<PrivateChat>(ex);
}
}
public bool TryValidate(PrivateChat chat)
{
try
{
Validate(chat);
return true;
}
catch (Exception ex)
{
return false;
}
}
}
+3 -3
View File
@@ -6,9 +6,9 @@ public class ChatGroup
public string Name { get; set; } public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }
public Guid ImageId { get; set; } public Guid ImageId { get; set; }
public List<string> InviteCode { get; set; }
public bool IsChannel { get; set; } public bool IsChannel { get; set; }
public bool IsPrivate { get; set; } public bool IsPrivate { get; set; }
public List<Guid> Admins { get; set; } = new(); public List<GroupAdmins> Admins { get; set; } = new();
public List<GroupMembership> Members { get; set; } = new();
public List<GroupInvitation> InviteCodes { get; set; } = new();
} }
+15
View File
@@ -0,0 +1,15 @@
namespace Govor.Core.Models;
public class GroupInvitation
{
public Guid Id { get; set; }
public Guid GroupId { get; set; }
public Guid UserMakerId { get; set; }
public string InvitationCode { get; set; }
public string Description {get; set;}
public DateTime EndDate { get; set; }
public DateTime CreatedAt { get; set; }
public int MaxParticipants { get; set; }
public List<Guid> GroupMemberships { get; set; } = new();
public User? UserMaker { get; set; }
}
+1
View File
@@ -5,5 +5,6 @@ public class GroupMembership
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid GroupId { get; set; } public Guid GroupId { get; set; }
public Guid UserId { get; set; } public Guid UserId { get; set; }
public Guid InvitationId { get; set; }
public bool IsBanned { get; set; } public bool IsBanned { get; set; }
} }
@@ -0,0 +1,8 @@
using Govor.Core.Models;
namespace Govor.Core.Repositories.Groups;
public interface IGroupMessagesReader
{
public Task<IEnumerable<Message>> GetMessages(Guid chatId, Guid? startMessageId, int pageSize = 20);
}
@@ -1,13 +1,13 @@
using System.Text.RegularExpressions; using Govor.Core.Models;
namespace Govor.Core.Repositories.Groups; namespace Govor.Core.Repositories.Groups;
public interface IGroupsReader public interface IGroupsReader
{ {
public Task<List<Group>> GetAllAsync(); public Task<List<ChatGroup>> GetAllAsync();
public Task<Group> GetByIdAsync(Guid id); public Task<ChatGroup> GetByIdAsync(Guid id);
public Task<List<Group>> FindByNameAsync(string name); public Task<List<ChatGroup>> SearchByNameAsync(string name);
public Task<List<Group>> GetByAdminIdAsync(Guid adminId); public Task<List<ChatGroup>> GetByAdminIdAsync(Guid userId);
public Task<List<Group>> GetByUserIdAsync(Guid adminId); public Task<List<ChatGroup>> GetByUserIdAsync(Guid userId);
public bool IsUserMemberOfGroupAsync(Guid userId, Guid groupId); public Task<bool> IsUserMemberOfGroupAsync(Guid userId, Guid groupId);
} }
@@ -1,10 +1,10 @@
using System.Text.RegularExpressions; using Govor.Core.Models;
namespace Govor.Core.Repositories.Groups; namespace Govor.Core.Repositories.Groups;
public interface IGroupsWriter public interface IGroupsWriter
{ {
Task Add(Group group); Task AddAsync(ChatGroup group);
Task Update(Group group); Task UpdateAsync(ChatGroup group);
Task Remove(Guid groupId); Task RemoveAsync(Guid groupId);
} }
@@ -0,0 +1,7 @@
namespace Govor.Core.Repositories.PrivateChats;
public interface IPrivateChatsExist
{
bool Exist(Guid chatId);
bool Exist(Guid userAId, Guid userBId);
}
@@ -0,0 +1,10 @@
using Govor.Core.Models;
namespace Govor.Core.Repositories.PrivateChats;
public interface IPrivateChatsReader
{
Task<List<PrivateChat>> GetAllAsync();
Task<PrivateChat> GetByIdAsync(Guid id);
Task<PrivateChat> GetByMembersAsync(Guid memberAId, Guid memberBId);
}
@@ -0,0 +1,6 @@
namespace Govor.Core.Repositories.PrivateChats;
public interface IPrivateChatsRepository : IPrivateChatsReader, IPrivateChatsWriter, IPrivateChatsExist
{
}
@@ -0,0 +1,10 @@
using Govor.Core.Models;
namespace Govor.Core.Repositories.PrivateChats;
public interface IPrivateChatsWriter
{
Task AddAsync(PrivateChat chat);
Task UpdateAsync(PrivateChat chat);
Task RemoveAsync(Guid chatId);
}
+1
View File
@@ -21,6 +21,7 @@ public class GovorDbContext(DbContextOptions<GovorDbContext> options) : DbContex
public virtual DbSet<MediaFile> MediaFiles { 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<GroupInvitation> GroupInvitations { get; set; }
public virtual DbSet<GroupMembership> GroupMemberships { get; set; } public virtual DbSet<GroupMembership> GroupMemberships { get; set; }
public virtual DbSet<GroupAdmins> GroupAdmins { get; set; } public virtual DbSet<GroupAdmins> GroupAdmins { get; set; }
+128 -26
View File
@@ -1,62 +1,164 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Govor.Core.Infrastructure.Extensions;
using Govor.Core.Infrastructure.Validators;
using Govor.Core.Models; using Govor.Core.Models;
using Govor.Core.Repositories.Groups; using Govor.Core.Repositories.Groups;
using Govor.Data.Repositories.Exceptions;
using Microsoft.EntityFrameworkCore;
namespace Govor.Data.Repositories; namespace Govor.Data.Repositories;
public class GroupRepository : IGroupsRepository public class GroupRepository : IGroupsRepository
{ {
public Task<List<Group>> GetAllAsync() private GovorDbContext _context;
private IObjectValidator<ChatGroup> _validator;
public GroupRepository(GovorDbContext context, IObjectValidator<ChatGroup> validator)
{ {
throw new NotImplementedException(); _context = context;
} _validator = validator;
public Task<Group> GetByIdAsync(Guid id)
{
throw new NotImplementedException();
}
public Task<List<Group>> FindByNameAsync(string name)
{
throw new NotImplementedException();
} }
public Task<List<Group>> GetByAdminIdAsync(Guid adminId) public async Task<List<ChatGroup>> GetAllAsync()
{ {
throw new NotImplementedException(); return await _context.ChatGroups
.AsNoTracking()
.Include(g => g.Admins)
.Include(g => g.Members)
.Include(g => g.InviteCodes)
.AsSplitQuery()
.ToListOrThrowIfEmpty(new NotFoundException("Database is empty"));
} }
public Task<List<Group>> GetByUserIdAsync(Guid adminId) public async Task<ChatGroup> GetByIdAsync(Guid id)
{ {
throw new NotImplementedException(); return await _context.ChatGroups
.AsNoTracking()
.Include(g => g.Admins)
.Include(g => g.Members)
.Include(g => g.InviteCodes)
.AsSplitQuery()
.FirstOrDefaultAsync(g => g.Id == id)
?? throw new NotFoundByKeyException<Guid>(id, "Group with given id doesn't exist");
} }
public Task Add(Group group) public async Task<List<ChatGroup>> SearchByNameAsync(string name)
{ {
throw new NotImplementedException(); return await _context.ChatGroups
.AsNoTracking()
.Include(g => g.Admins)
.Include(g => g.Members)
.Include(g => g.InviteCodes)
.AsSplitQuery()
.Where(g => g.Name.ToLower().Contains(name.ToLower()))
.Take(20)
.ToListOrThrowIfEmpty(new NotFoundByKeyException<string>(name, "Group with name doesn't exist"));
}
public async Task<List<ChatGroup>> GetByAdminIdAsync(Guid userId)
{
return await _context.ChatGroups
.AsNoTracking()
.Include(g => g.Admins)
.Include(g => g.Members)
.Include(g => g.InviteCodes)
.AsSplitQuery()
.Where(g => g.Admins.Any(a => a.UserId == userId))
.ToListOrThrowIfEmpty(new NotFoundByKeyException<Guid>(userId, "Admin with given id doesn't exist"));
} }
public Task Update(Group group) public async Task<List<ChatGroup>> GetByUserIdAsync(Guid userId)
{ {
throw new NotImplementedException(); return await _context.ChatGroups
.AsNoTracking()
.Include(g => g.Admins)
.Include(g => g.Members)
.Include(g => g.InviteCodes)
.AsSplitQuery()
.Where(g => g.Members.Any(a => a.UserId == userId))
.ToListOrThrowIfEmpty(new NotFoundByKeyException<Guid>(userId, "Member with given id doesn't exist"));
} }
public Task Remove(Guid groupId) public async Task AddAsync(ChatGroup group)
{ {
throw new NotImplementedException(); try
{
_validator.Validate(group);
_context.ChatGroups.Add(group);
await _context.SaveChangesAsync();
}
catch (InvalidObjectException<ChatGroup> ex)
{
throw new AdditionException("Group with given data invalid", ex);
}
catch (Exception ex)
{
throw new AdditionException("Cannot add group", ex);
}
} }
public async Task UpdateAsync(ChatGroup group)
{
try
{
_validator.Validate(group);
var rowsAffected = await _context.ChatGroups
.Where(u => u.Id == group.Id)
.ExecuteUpdateAsync(g => g
.SetProperty(g => g.Name, group.Name)
.SetProperty(g => g.Description, group.Description)
.SetProperty(g => g.ImageId, group.ImageId)
.SetProperty(g => g.IsChannel, group.IsChannel)
.SetProperty(g => g.IsPrivate, group.IsPrivate)
.SetProperty(g => g.InviteCodes, group.InviteCodes)
);
if (rowsAffected == 0)
throw new UpdateException($"Not found group by given id {group.Id}");
}
catch (Exception ex)
{
throw new UpdateException($"Error when updating the group {group.Id}", ex);
}
}
public async Task RemoveAsync(Guid groupId)
{
try
{
var result = await GetByIdAsync(groupId);
_context.ChatGroups.Remove(result);
await _context.SaveChangesAsync();
}
catch (InvalidObjectException<User> ex)
{
throw new RemoveException("User with given data invalid", ex);
}
}
public bool Exists(Guid groupId) public bool Exists(Guid groupId)
{ {
throw new NotImplementedException(); return _context.ChatGroups.Any(g => g.Id == groupId);
} }
public bool Exists(ChatGroup chatGroup) public bool Exists(ChatGroup chatGroup)
{ {
throw new NotImplementedException(); return _context.ChatGroups.Any(g => g.Id == chatGroup.Id &&
g.IsChannel == chatGroup.IsChannel &&
g.Description == chatGroup.Description &&
g.IsPrivate == chatGroup.IsPrivate &&
g.Name == chatGroup.Name &&
g.ImageId == chatGroup.ImageId);
} }
public bool IsUserMemberOfGroupAsync(Guid userId, Guid groupId) public async Task<bool> IsUserMemberOfGroupAsync(Guid userId, Guid groupId)
{ {
throw new NotImplementedException(); return await _context.ChatGroups
.AsNoTracking()
.Include(g => g.Members)
.AnyAsync(g => g.Members.Any(a => a.UserId == userId && a.GroupId == groupId));
} }
} }
@@ -0,0 +1,115 @@
using Govor.Core.Infrastructure.Extensions;
using Govor.Core.Infrastructure.Validators;
using Govor.Core.Models;
using Govor.Core.Repositories.PrivateChats;
using Govor.Data.Repositories.Exceptions;
using Microsoft.EntityFrameworkCore;
namespace Govor.Data.Repositories;
public class PrivateChatsRepository : IPrivateChatsRepository
{
private GovorDbContext _context;
private IObjectValidator<PrivateChat> _validator;
public PrivateChatsRepository(GovorDbContext context, IObjectValidator<PrivateChat> validator)
{
_context = context;
_validator = validator;
}
public async Task<List<PrivateChat>> GetAllAsync()
{
return await _context.PrivateChats
.AsNoTracking()
.ToListOrThrowIfEmpty(new NotFoundException("Database is empty"));
}
public async Task<PrivateChat> GetByIdAsync(Guid id)
{
return await _context.PrivateChats
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == id)
?? throw new NotFoundByKeyException<Guid>(id, "Private Chat with given Id does not exist");
}
public async Task<PrivateChat> GetByMembersAsync(Guid memberAId, Guid memberBId)
{
return await _context.PrivateChats
.AsNoTracking()
.FirstOrDefaultAsync(f => (f.UserAId == memberAId && f.UserBId == memberBId) || (f.UserAId == memberBId && f.UserBId == memberAId))
?? throw new NotFoundByKeyException<(Guid, Guid)>((memberAId, memberBId), "Private Chat with given members Id's does not exist");
}
public async Task AddAsync(PrivateChat chat)
{
try
{
_validator.Validate(chat);
_context.PrivateChats.Add(chat);
await _context.SaveChangesAsync();
}
catch (InvalidObjectException<PrivateChat> ex)
{
throw new AdditionException("Private chat with given data invalid", ex);
}
catch (Exception ex)
{
throw new AdditionException("Cannot add private chat", ex);
}
}
public async Task UpdateAsync(PrivateChat chat)
{
try
{
_validator.Validate(chat);
var rowsAffected = await _context.PrivateChats
.Where(m => m.Id == chat.Id)
.ExecuteUpdateAsync(c => c
.SetProperty(c => c.UserAId, chat.UserAId)
.SetProperty(c => c.UserBId, chat.UserBId)
.SetProperty(c => c.Messages, chat.Messages)
);
if (rowsAffected == 0)
throw new UpdateException($"Not found private chat by given id {chat.Id}");
}
catch (Exception ex)
{
throw new UpdateException($"Error when updating the private chat {chat.Id}", ex);
}
}
public async Task RemoveAsync(Guid chatId)
{
try
{
var result = await GetByIdAsync(chatId);
_context.PrivateChats.Remove(result);
await _context.SaveChangesAsync();
}
catch (NotFoundByKeyException<Guid> ex)
{
throw new RemoveException($"Not found private chat by given id {chatId}", ex);
}
catch (Exception ex)
{
throw new RemoveException("Error when removing the private chat", ex);
}
}
public bool Exist(Guid chatId)
{
return _context.PrivateChats.Any(c => c.Id == chatId);
}
public bool Exist(Guid userAId, Guid userBId)
{
return _context.PrivateChats.Any(f => (f.UserAId == userAId && f.UserBId == userBId)
|| (f.UserAId == userBId && f.UserBId == userAId));
}
}