diff --git a/Govor.API.Tests/IntegrationTests/EF/Repositories/PrivateChatsRepositoryTests.cs b/Govor.API.Tests/IntegrationTests/EF/Repositories/PrivateChatsRepositoryTests.cs new file mode 100644 index 0000000..41467ef --- /dev/null +++ b/Govor.API.Tests/IntegrationTests/EF/Repositories/PrivateChatsRepositoryTests.cs @@ -0,0 +1,7 @@ +namespace Govor.API.Tests.IntegrationTests.EF.Repositories; + +[TestFixture] +public class PrivateChatsRepositoryTests +{ + +} \ No newline at end of file diff --git a/Govor.API.Tests/IntegrationTests/Hubs/ChatsHubTests.cs b/Govor.API.Tests/IntegrationTests/Hubs/ChatsHubTests.cs new file mode 100644 index 0000000..4f2ebe1 --- /dev/null +++ b/Govor.API.Tests/IntegrationTests/Hubs/ChatsHubTests.cs @@ -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> _loggerMock; + private Mock _messageServiceMock; + private Fixture _fixture; + private ChatsHub _chatsHub; + + [SetUp] + public void SetUp() + { + _fixture = new Fixture(); + _fixture.Behaviors.OfType().ToList().ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _messageServiceMock = new Mock(); + _loggerMock = new Mock>(); + + _chatsHub = new ChatsHub( + _loggerMock.Object, + _messageServiceMock.Object + ); + } + + // Test for Send action + +} \ No newline at end of file diff --git a/Govor.API.Tests/UnitTests/Infrastructure/Validators/PrivateChatValidatorTests.cs b/Govor.API.Tests/UnitTests/Infrastructure/Validators/PrivateChatValidatorTests.cs new file mode 100644 index 0000000..8abd1fd --- /dev/null +++ b/Govor.API.Tests/UnitTests/Infrastructure/Validators/PrivateChatValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Govor.API.Tests.UnitTests.Infrastructure.Validators; + +[TestFixture] +public class PrivateChatValidatorTests +{ + +} \ No newline at end of file diff --git a/Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs b/Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs index d789064..3260d5b 100644 --- a/Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs +++ b/Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs @@ -5,6 +5,7 @@ using Govor.Application.Services; using Govor.Core.Models; using Govor.Core.Repositories.Groups; using Govor.Core.Repositories.Messages; +using Govor.Core.Repositories.PrivateChats; using Govor.Core.Repositories.Users; using Govor.Data.Repositories.Exceptions; using Microsoft.Extensions.Logging; @@ -19,6 +20,7 @@ public class MessageServiceTests private Mock _mockUsersRepo; private Mock _mockGroupsRepo; private Mock _mockVerifyFriendship; + private Mock _mockPrivateChats; private Mock> _mockLogger; private MessageService _messageService; @@ -29,6 +31,7 @@ public class MessageServiceTests _mockUsersRepo = new Mock(); _mockGroupsRepo = new Mock(); _mockVerifyFriendship = new Mock(); + _mockPrivateChats = new Mock(); _mockLogger = new Mock>(); _messageService = new MessageService( @@ -36,6 +39,7 @@ public class MessageServiceTests _mockUsersRepo.Object, _mockGroupsRepo.Object, _mockVerifyFriendship.Object, + _mockPrivateChats.Object, _mockLogger.Object); } @@ -59,7 +63,8 @@ public class MessageServiceTests _mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true); _mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).Returns(Task.CompletedTask); _mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny())).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 var result = await _messageService.SendMessageAsync(sendMessageParams); // Assert @@ -90,7 +95,7 @@ public class MessageServiceTests new List()); _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())).Returns(Task.CompletedTask); // Act @@ -130,7 +135,7 @@ public class MessageServiceTests Assert.That(result.IsSuccess, Is.EqualTo(false)); Assert.That(result.Exception, Is.Not.Null); Assert.That(result.Exception,Is.TypeOf()); - Assert.That(result.MessageId, Is.EqualTo(Guid.Empty)); + Assert.That(result.Message, Is.Null); } [Test] @@ -160,9 +165,11 @@ public class MessageServiceTests Assert.That(result.IsSuccess, Is.EqualTo(false)); Assert.That(result.Exception, Is.Not.Null); Assert.That(result.Exception,Is.TypeOf()); - Assert.That(result.MessageId, Is.EqualTo(Guid.Empty)); + Assert.That(result.Message, Is.Null); } + + // Test for EditMessageAsync action [Test] public async Task EditMessageAsync_Success() diff --git a/Govor.API/Extensions/ConfigurationProgramExtensions.cs b/Govor.API/Extensions/ConfigurationProgramExtensions.cs index 44d19da..7e45361 100644 --- a/Govor.API/Extensions/ConfigurationProgramExtensions.cs +++ b/Govor.API/Extensions/ConfigurationProgramExtensions.cs @@ -17,6 +17,7 @@ using Govor.Core.Repositories.Groups; using Govor.Core.Repositories.Invaites; using Govor.Core.Repositories.MediasAttachments; using Govor.Core.Repositories.Messages; +using Govor.Core.Repositories.PrivateChats; using Govor.Core.Repositories.Users; using Govor.Data; using Govor.Data.Repositories; @@ -61,6 +62,7 @@ public static class ConfigurationProgramExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); } @@ -72,6 +74,7 @@ public static class ConfigurationProgramExtensions services.AddScoped, AdminValidator>(); services.AddScoped, InvitationValidator>(); services.AddScoped, FriendshipValidator>(); + services.AddScoped, PrivateChatValidator>(); } public static void AddGovorDbContext(this IServiceCollection services, IConfiguration configuration) diff --git a/Govor.Application/Interfaces/Messages/IMessageService.cs b/Govor.Application/Interfaces/Messages/IMessageService.cs index 5493bbf..f4c269e 100644 --- a/Govor.Application/Interfaces/Messages/IMessageService.cs +++ b/Govor.Application/Interfaces/Messages/IMessageService.cs @@ -18,7 +18,7 @@ public interface IMessageService // Define specific result types for clarity, including original message for notifications if needed 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) : Result(IsSuccess, Exception, OriginalMessage?.Id ?? Guid.Empty) diff --git a/Govor.Application/Services/MessageService.cs b/Govor.Application/Services/MessageService.cs index cf4469e..f894666 100644 --- a/Govor.Application/Services/MessageService.cs +++ b/Govor.Application/Services/MessageService.cs @@ -4,6 +4,7 @@ using Govor.Application.Interfaces.Messages.Parameters; using Govor.Core.Models; using Govor.Core.Repositories.Groups; using Govor.Core.Repositories.Messages; +using Govor.Core.Repositories.PrivateChats; using Govor.Core.Repositories.Users; using Microsoft.Extensions.Logging; @@ -14,6 +15,7 @@ public class MessageService : IMessageService private readonly IMessagesRepository _messagesRepository; private readonly IUsersRepository _usersRepository; // For validating user recipients private readonly IGroupsRepository _groupsRepository; // For validating group recipients and fetching members + private readonly IPrivateChatsRepository _privateChats; private readonly IVerifyFriendship _verifyFriendship; // For private messages private readonly ILogger _logger; @@ -22,17 +24,20 @@ public class MessageService : IMessageService IUsersRepository usersRepository, IGroupsRepository groupsRepository, IVerifyFriendship verifyFriendship, + IPrivateChatsRepository privateChats, ILogger logger) { _messagesRepository = messagesRepository; _usersRepository = usersRepository; _groupsRepository = groupsRepository; _verifyFriendship = verifyFriendship; + _privateChats = privateChats; _logger = logger; } public async Task SendMessageAsync(SendMessage sendParams) { + Guid recipientId; try { // Validate recipient @@ -45,6 +50,7 @@ public class MessageService : IMessageService } // Verify friendship for private messages await _verifyFriendship.VerifyAsync(sendParams.FromUserId, sendParams.RecipientId); + recipientId = await GetPrivateChatsIdAsync(sendParams.FromUserId, sendParams.RecipientId); } 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); } // TODO: Optionally, verify if sender is a member of the group - // bool isMember = await _groupsRepository.IsUserMemberOfGroupAsync(sendParams.FromUserId, sendParams.RecipientId); - // if (!isMember) - // { - // _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); - // } + bool isMember = await _groupsRepository.IsUserMemberOfGroupAsync(sendParams.FromUserId, sendParams.RecipientId); + if (!isMember) + { + _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."), default); + } + + recipientId = sendParams.RecipientId; } else { _logger.LogError("Invalid recipient type specified: {RecipientType}", sendParams.RecipientType); return new SendMessageResult(false, new ArgumentException("Invalid recipient type."), default); } - + + var messageId = Guid.NewGuid(); var message = new Message { Id = messageId, SenderId = sendParams.FromUserId, - RecipientId = sendParams.RecipientId, + RecipientId = recipientId, RecipientType = sendParams.RecipientType, EncryptedContent = sendParams.EncryptContent, SentAt = sendParams.SendAt, @@ -97,6 +106,24 @@ public class MessageService : IMessageService } } + private async Task 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 EditMessageAsync(EditMessage editParams) { try @@ -109,7 +136,8 @@ public class MessageService : IMessageService 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 { diff --git a/Govor.Core/Infrastructure/Validators/PrivateChatValidator.cs b/Govor.Core/Infrastructure/Validators/PrivateChatValidator.cs new file mode 100644 index 0000000..184d442 --- /dev/null +++ b/Govor.Core/Infrastructure/Validators/PrivateChatValidator.cs @@ -0,0 +1,38 @@ +using Govor.Core.Models; + +namespace Govor.Core.Infrastructure.Validators; + +public class PrivateChatValidator : IObjectValidator +{ + 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(ex); + } + } + + public bool TryValidate(PrivateChat chat) + { + try + { + Validate(chat); + return true; + } + catch (Exception ex) + { + return false; + } + } +} \ No newline at end of file diff --git a/Govor.Core/Models/ChatGroup.cs b/Govor.Core/Models/ChatGroup.cs index b1ae3c2..7954c97 100644 --- a/Govor.Core/Models/ChatGroup.cs +++ b/Govor.Core/Models/ChatGroup.cs @@ -6,9 +6,9 @@ public class ChatGroup public string Name { get; set; } public string Description { get; set; } public Guid ImageId { get; set; } - - public List InviteCode { get; set; } public bool IsChannel { get; set; } public bool IsPrivate { get; set; } - public List Admins { get; set; } = new(); + public List Admins { get; set; } = new(); + public List Members { get; set; } = new(); + public List InviteCodes { get; set; } = new(); } \ No newline at end of file diff --git a/Govor.Core/Models/GroupInvitation.cs b/Govor.Core/Models/GroupInvitation.cs new file mode 100644 index 0000000..57f44c9 --- /dev/null +++ b/Govor.Core/Models/GroupInvitation.cs @@ -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 GroupMemberships { get; set; } = new(); + public User? UserMaker { get; set; } +} \ No newline at end of file diff --git a/Govor.Core/Models/GroupMembership.cs b/Govor.Core/Models/GroupMembership.cs index d33f40d..d70341e 100644 --- a/Govor.Core/Models/GroupMembership.cs +++ b/Govor.Core/Models/GroupMembership.cs @@ -5,5 +5,6 @@ public class GroupMembership public Guid Id { get; set; } public Guid GroupId { get; set; } public Guid UserId { get; set; } + public Guid InvitationId { get; set; } public bool IsBanned { get; set; } } \ No newline at end of file diff --git a/Govor.Core/Repositories/Groups/IGroupMessagesReader.cs b/Govor.Core/Repositories/Groups/IGroupMessagesReader.cs new file mode 100644 index 0000000..3f5d9ab --- /dev/null +++ b/Govor.Core/Repositories/Groups/IGroupMessagesReader.cs @@ -0,0 +1,8 @@ +using Govor.Core.Models; + +namespace Govor.Core.Repositories.Groups; + +public interface IGroupMessagesReader +{ + public Task> GetMessages(Guid chatId, Guid? startMessageId, int pageSize = 20); +} \ No newline at end of file diff --git a/Govor.Core/Repositories/Groups/IGroupsReader.cs b/Govor.Core/Repositories/Groups/IGroupsReader.cs index 752817d..26829d0 100644 --- a/Govor.Core/Repositories/Groups/IGroupsReader.cs +++ b/Govor.Core/Repositories/Groups/IGroupsReader.cs @@ -1,13 +1,13 @@ -using System.Text.RegularExpressions; +using Govor.Core.Models; namespace Govor.Core.Repositories.Groups; public interface IGroupsReader { - public Task> GetAllAsync(); - public Task GetByIdAsync(Guid id); - public Task> FindByNameAsync(string name); - public Task> GetByAdminIdAsync(Guid adminId); - public Task> GetByUserIdAsync(Guid adminId); - public bool IsUserMemberOfGroupAsync(Guid userId, Guid groupId); + public Task> GetAllAsync(); + public Task GetByIdAsync(Guid id); + public Task> SearchByNameAsync(string name); + public Task> GetByAdminIdAsync(Guid userId); + public Task> GetByUserIdAsync(Guid userId); + public Task IsUserMemberOfGroupAsync(Guid userId, Guid groupId); } \ No newline at end of file diff --git a/Govor.Core/Repositories/Groups/IGroupsWriter.cs b/Govor.Core/Repositories/Groups/IGroupsWriter.cs index dbb2ce4..4cc38f4 100644 --- a/Govor.Core/Repositories/Groups/IGroupsWriter.cs +++ b/Govor.Core/Repositories/Groups/IGroupsWriter.cs @@ -1,10 +1,10 @@ -using System.Text.RegularExpressions; +using Govor.Core.Models; namespace Govor.Core.Repositories.Groups; public interface IGroupsWriter { - Task Add(Group group); - Task Update(Group group); - Task Remove(Guid groupId); + Task AddAsync(ChatGroup group); + Task UpdateAsync(ChatGroup group); + Task RemoveAsync(Guid groupId); } \ No newline at end of file diff --git a/Govor.Core/Repositories/PrivateChats/IPrivateChatsExist.cs b/Govor.Core/Repositories/PrivateChats/IPrivateChatsExist.cs new file mode 100644 index 0000000..af72f01 --- /dev/null +++ b/Govor.Core/Repositories/PrivateChats/IPrivateChatsExist.cs @@ -0,0 +1,7 @@ +namespace Govor.Core.Repositories.PrivateChats; + +public interface IPrivateChatsExist +{ + bool Exist(Guid chatId); + bool Exist(Guid userAId, Guid userBId); +} \ No newline at end of file diff --git a/Govor.Core/Repositories/PrivateChats/IPrivateChatsReader.cs b/Govor.Core/Repositories/PrivateChats/IPrivateChatsReader.cs new file mode 100644 index 0000000..bcc2f83 --- /dev/null +++ b/Govor.Core/Repositories/PrivateChats/IPrivateChatsReader.cs @@ -0,0 +1,10 @@ +using Govor.Core.Models; + +namespace Govor.Core.Repositories.PrivateChats; + +public interface IPrivateChatsReader +{ + Task> GetAllAsync(); + Task GetByIdAsync(Guid id); + Task GetByMembersAsync(Guid memberAId, Guid memberBId); +} \ No newline at end of file diff --git a/Govor.Core/Repositories/PrivateChats/IPrivateChatsRepository.cs b/Govor.Core/Repositories/PrivateChats/IPrivateChatsRepository.cs new file mode 100644 index 0000000..de632d6 --- /dev/null +++ b/Govor.Core/Repositories/PrivateChats/IPrivateChatsRepository.cs @@ -0,0 +1,6 @@ +namespace Govor.Core.Repositories.PrivateChats; + +public interface IPrivateChatsRepository : IPrivateChatsReader, IPrivateChatsWriter, IPrivateChatsExist +{ + +} \ No newline at end of file diff --git a/Govor.Core/Repositories/PrivateChats/IPrivateChatsWriter.cs b/Govor.Core/Repositories/PrivateChats/IPrivateChatsWriter.cs new file mode 100644 index 0000000..d2afa33 --- /dev/null +++ b/Govor.Core/Repositories/PrivateChats/IPrivateChatsWriter.cs @@ -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); +} \ No newline at end of file diff --git a/Govor.Data/GovorDbContext.cs b/Govor.Data/GovorDbContext.cs index c07d72a..b49c3e8 100644 --- a/Govor.Data/GovorDbContext.cs +++ b/Govor.Data/GovorDbContext.cs @@ -21,6 +21,7 @@ public class GovorDbContext(DbContextOptions options) : DbContex public virtual DbSet MediaFiles { get; set; } public virtual DbSet ChatGroups { get; set; } + public virtual DbSet GroupInvitations { get; set; } public virtual DbSet GroupMemberships { get; set; } public virtual DbSet GroupAdmins { get; set; } diff --git a/Govor.Data/Repositories/GroupRepository.cs b/Govor.Data/Repositories/GroupRepository.cs index cfbc592..daaf32a 100644 --- a/Govor.Data/Repositories/GroupRepository.cs +++ b/Govor.Data/Repositories/GroupRepository.cs @@ -1,62 +1,164 @@ using System.Text.RegularExpressions; +using Govor.Core.Infrastructure.Extensions; +using Govor.Core.Infrastructure.Validators; using Govor.Core.Models; using Govor.Core.Repositories.Groups; +using Govor.Data.Repositories.Exceptions; +using Microsoft.EntityFrameworkCore; namespace Govor.Data.Repositories; public class GroupRepository : IGroupsRepository { - public Task> GetAllAsync() + private GovorDbContext _context; + private IObjectValidator _validator; + + public GroupRepository(GovorDbContext context, IObjectValidator validator) { - throw new NotImplementedException(); - } - - public Task GetByIdAsync(Guid id) - { - throw new NotImplementedException(); - } - - public Task> FindByNameAsync(string name) - { - throw new NotImplementedException(); + _context = context; + _validator = validator; } - public Task> GetByAdminIdAsync(Guid adminId) + public async Task> 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> GetByUserIdAsync(Guid adminId) + public async Task 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(id, "Group with given id doesn't exist"); } - public Task Add(Group group) + public async Task> 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(name, "Group with name doesn't exist")); + } + + public async Task> 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(userId, "Admin with given id doesn't exist")); } - public Task Update(Group group) + public async Task> 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(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 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 ex) + { + throw new RemoveException("User with given data invalid", ex); + } + } + public bool Exists(Guid groupId) { - throw new NotImplementedException(); + return _context.ChatGroups.Any(g => g.Id == groupId); } 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 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)); } } diff --git a/Govor.Data/Repositories/PrivateChatsRepository.cs b/Govor.Data/Repositories/PrivateChatsRepository.cs new file mode 100644 index 0000000..07836ec --- /dev/null +++ b/Govor.Data/Repositories/PrivateChatsRepository.cs @@ -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 _validator; + + public PrivateChatsRepository(GovorDbContext context, IObjectValidator validator) + { + _context = context; + _validator = validator; + } + + public async Task> GetAllAsync() + { + return await _context.PrivateChats + .AsNoTracking() + .ToListOrThrowIfEmpty(new NotFoundException("Database is empty")); + } + + public async Task GetByIdAsync(Guid id) + { + return await _context.PrivateChats + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == id) + ?? throw new NotFoundByKeyException(id, "Private Chat with given Id does not exist"); + } + + public async Task 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 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 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)); + } +} \ No newline at end of file