diff --git a/Govor.API.Tests/IntegrationTests/EF/Repositories/AdminsRepositoryTests.cs b/Govor.API.Tests/IntegrationTests/EF/Repositories/AdminsRepositoryTests.cs index 42a85d6..0790904 100644 --- a/Govor.API.Tests/IntegrationTests/EF/Repositories/AdminsRepositoryTests.cs +++ b/Govor.API.Tests/IntegrationTests/EF/Repositories/AdminsRepositoryTests.cs @@ -96,7 +96,7 @@ public class AdminsRepositoryTests var repository = new AdminsRepository(context, _validator); // Act & Assert - Assert.ThrowsAsync>(async () => await repository.GetByIdAsync(Guid.Empty)); + Assert.ThrowsAsync>(async () => await repository.GetByIdAsync(_fixture.Create())); } [Test] diff --git a/Govor.API.Tests/IntegrationTests/EF/Repositories/FriendshipsRepositoryTests.cs b/Govor.API.Tests/IntegrationTests/EF/Repositories/FriendshipsRepositoryTests.cs new file mode 100644 index 0000000..6394447 --- /dev/null +++ b/Govor.API.Tests/IntegrationTests/EF/Repositories/FriendshipsRepositoryTests.cs @@ -0,0 +1,191 @@ +using AutoFixture; +using Govor.Core.Infrastructure.Validators; +using Govor.Core.Models; +using Govor.Data; +using Govor.Data.Repositories; +using Govor.Data.Repositories.Exceptions; +using Microsoft.EntityFrameworkCore; + +namespace Govor.API.Tests.IntegrationTests.EF.Repositories; + +[TestFixture] +public class FriendshipsRepositoryTests +{ + private Fixture _fixture; + private DbContextOptions _options; + private IObjectValidator _validator = new FriendshipValidator(); + + private int _testIteration = 0; + + [SetUp] + public void Setup() + { + _fixture = new Fixture(); + + _fixture.Behaviors + .OfType() + .ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: $"DbGovor_{nameof(FriendshipsRepositoryTests)}_{_testIteration}") + .Options; + _testIteration += 1; + } + + + [Test] + public async Task Given_NotEmptyDbSet_When_GetAllAsync_Then_Friendships_Are_Returned() + { + // Arrange + var random = new Random(); + var friendships = _fixture.CreateMany(random.Next(3, 10)).ToList(); + + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + context.AddRange(friendships); + await context.SaveChangesAsync(); + + // Act + var result = await repository.GetAllAsync(); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Count(), Is.EqualTo(friendships.Count())); + Assert.That(result.Select(r => r.Id), Is.EquivalentTo(friendships.Select(r => r.Id))); + } + + [Test] + public async Task Given_EmptyDbSet_When_GetAllAsync_Should_Throw_NotFoundException() + { + // Arrange + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + // Act & Assert + Assert.ThrowsAsync(async () => await repository.GetAllAsync()); + } + + [Test] + public async Task Given_ValidFriendshipId_When_GetByIdAsync_Then_Friendships_Are_Returned() + { + // Arrange + var random = new Random(); + var friendships = _fixture.CreateMany(random.Next(3, 10)).ToList(); + + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + context.AddRange(friendships); + await context.SaveChangesAsync(); + + // Act + var result = await repository.GetByIdAsync(friendships.First().Id); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Id, Is.EqualTo(friendships.First().Id)); + } + + [Test] + public async Task Given_InvalidFriendshipId_When_GetByIdAsync_Should_Throw_NotFoundByKeyException() + { + // Arrange + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + // Act & Assert + Assert.ThrowsAsync>(async () => await repository.GetByIdAsync(_fixture.Create())); + } + + [Test] + public async Task Given_ValidUserId_When_GetByIdAsync_Then_Friendships_Are_Returned() + { + // Arrange + var random = new Random(); + var friendships = _fixture.CreateMany(random.Next(3, 10)).ToList(); + + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + context.AddRange(friendships); + await context.SaveChangesAsync(); + + // Act + var result = await repository.FindByUserIdAsync(friendships.First().RequesterId); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.First().Id, Is.EqualTo(friendships.First().Id)); + } + + [Test] + public async Task Given_InvalidUserId_When_GetByIdAsync_Should_Throw_NotFoundByKeyException() + { + // Arrange + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + // Act & Assert + Assert.ThrowsAsync>(async () => await repository.FindByUserIdAsync(_fixture.Create())); + } + + [Test] + public async Task Given_ValidFriendship_When_AddAsync_Then_Added() + { + // Arrange + var friendship = _fixture.Create(); + + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + // Act + await repository.AddAsync(friendship); + + // Assert + Assert.That(context.Friendships.Count, Is.EqualTo(1)); + Assert.That(context.Friendships.First().Id, Is.EqualTo(friendship.Id)); + } + + [Test] + public async Task Given_InvalidFriendship_When_AddAsync_Should_Throw_AdditionException() + { + // Arrange + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + // Act & Assert + Assert.ThrowsAsync(async () => await repository.AddAsync(default)); + } + + [Test] + public async Task Given_ExistFriendship_When_Exist_Then_True() + { + // Arrange + var friendship = _fixture.Create(); + + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + + context.Friendships.Add(friendship); + await context.SaveChangesAsync(); + + // Act + var result = repository.Exist(friendship.RequesterId, friendship.AddresseeId); + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task Given_NotExistFriendship_When_Exist_Then_False() + { + // Arrange + await using var context = new GovorDbContext(_options); + var repository = new FriendshipsRepository(context, _validator); + // Act & Assert + Assert.That(repository.Exist(_fixture.Create(), _fixture.Create()), Is.False); + } +} \ No newline at end of file diff --git a/Govor.API.Tests/UnitTests/Infrastructure/Validators/FriendshipValidatorTests.cs b/Govor.API.Tests/UnitTests/Infrastructure/Validators/FriendshipValidatorTests.cs new file mode 100644 index 0000000..cb42ea2 --- /dev/null +++ b/Govor.API.Tests/UnitTests/Infrastructure/Validators/FriendshipValidatorTests.cs @@ -0,0 +1,70 @@ +using AutoFixture; +using Govor.Core.Infrastructure.Validators; +using Govor.Core.Models; + +namespace Govor.API.Tests.UnitTests.Infrastructure.Validators; + +[TestFixture] +public class FriendshipValidatorTests +{ + private IObjectValidator _friendshipValidator; + private Fixture _fixture; + + public FriendshipValidatorTests() + { + _friendshipValidator = new FriendshipValidator(); + + _fixture = new Fixture(); + + _fixture.Behaviors + .OfType() + .ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + } + + + [Test] + public void Given_ValidFriendship_When_Validate_Then_Returns_True() + { + var friendship = _fixture.Create(); + + // Act & Assert + Assert.DoesNotThrow( () => _friendshipValidator.Validate(friendship)); + Assert.That(_friendshipValidator.TryValidate(friendship), Is.True); + } + + [Test] + public void Given_EmptyFriendshipId_When_Validate_Then_Returns_False() + { + var friendship = _fixture.Create(); + friendship.Id = Guid.Empty; + + // Act & Assert + Assert.Throws>( () => _friendshipValidator.Validate(friendship)); + Assert.That(_friendshipValidator.TryValidate(friendship), Is.False); + } + + [Test] + public void Given_EmptyRequesterId_When_Validate_Then_Returns_False() + { + var friendship = _fixture.Create(); + friendship.RequesterId = Guid.Empty; + + // Act & Assert + Assert.Throws>( () => _friendshipValidator.Validate(friendship)); + Assert.That(_friendshipValidator.TryValidate(friendship), Is.False); + } + + [Test] + public void Given_AddresseeId_When_Validate_Then_Returns_False() + { + var friendship = _fixture.Create(); + friendship.AddresseeId = Guid.Empty; + + // Act & Assert + Assert.Throws>( () => _friendshipValidator.Validate(friendship)); + Assert.That(_friendshipValidator.TryValidate(friendship), Is.False); + } +} \ No newline at end of file diff --git a/Govor.API.Tests/UnitTests/Infrastructure/Validators/InvitationValidatorTests.cs b/Govor.API.Tests/UnitTests/Infrastructure/Validators/InvitationValidatorTests.cs index 78c3ef5..38a7775 100644 --- a/Govor.API.Tests/UnitTests/Infrastructure/Validators/InvitationValidatorTests.cs +++ b/Govor.API.Tests/UnitTests/Infrastructure/Validators/InvitationValidatorTests.cs @@ -25,7 +25,7 @@ public class InvitationValidatorTests } [Test] - public void Given_ValidInvitation_When_Exist_Then_Returns_True() + public void Given_ValidInvitation_When_Validate_Then_Returns_True() { var invitation = _fixture.Create(); @@ -35,7 +35,7 @@ public class InvitationValidatorTests } [Test] - public void Given_NullInvitation_When_Exist_Then_Returns_False() + public void Given_NullInvitation_When_Validate_Then_Returns_False() { // Act & Assert Assert.Throws>( () => _messageValidator.Validate(default)); @@ -43,7 +43,7 @@ public class InvitationValidatorTests } [Test] - public void Given_EmptyInvitationId_When_Exist_Then_Returns_False() + public void Given_EmptyInvitationId_When_Validate_Then_Returns_False() { // Arrange var invitation = _fixture.Create(); @@ -55,7 +55,7 @@ public class InvitationValidatorTests } [Test] - public void Given_EmptyInvitationCreationDate_When_Exist_Then_Returns_False() + public void Given_EmptyInvitationCreationDate_When_Validate_Then_Returns_False() { // Arrange var invitation = _fixture.Create(); @@ -67,7 +67,7 @@ public class InvitationValidatorTests } [Test] - public void Given_InvalidEndDatesAndIsActive_When_Exist_Then_Returns_False() + public void Given_InvalidEndDatesAndIsActive_When_Validate_Then_Returns_False() { // Arrange var invitation = _fixture.Create(); @@ -79,7 +79,7 @@ public class InvitationValidatorTests } [Test] - public void Given_InvalidMaxParticipantsAndIsActive_When_Exist_Then_Returns_False() + public void Given_InvalidMaxParticipantsAndIsActive_When_Validate_Then_Returns_False() { // Arrange var invitation = _fixture.Create(); @@ -92,7 +92,7 @@ public class InvitationValidatorTests } [Test] - public void Given_InvalidCode_When_Exist_Then_Returns_False() + public void Given_InvalidCode_When_Validate_Then_Returns_False() { // Arrange var invitation = _fixture.Create(); diff --git a/Govor.API/Controllers/AdminStuff/InviteUserController.cs b/Govor.API/Controllers/AdminStuff/InviteUserController.cs index 82a3a8a..0e2724b 100644 --- a/Govor.API/Controllers/AdminStuff/InviteUserController.cs +++ b/Govor.API/Controllers/AdminStuff/InviteUserController.cs @@ -9,7 +9,7 @@ namespace Govor.API.Controllers.AdminStuff; [Route("api/[controller]")] [ApiController] -[Authorize] +[Authorize(Roles = "Admin")] public class InviteUserController : Controller { private readonly IInvitesRepository _repository; @@ -43,20 +43,22 @@ public class InviteUserController : Controller return BadRequest($"An error occured: {e.Message}"); } } - - [HttpGet] - public async Task GetAllInvitations() + + [HttpGet("[action]")] + public async Task GetAllActiveInvitations() { try { - _logger.LogInformation("Getting all invitations by administrator"); - var read = await _repository.GetAllAsync(); + _logger.LogInformation("Getting all active invitations by administrator"); - List dto = new List(); + var read = await _repository.GetAllAsync(); + var result = read.Where(x => x.IsActive == true).ToList(); - foreach (var inv in read) + List dtos = new List(); + + foreach (var inv in result) { - dto.Add(new InvitationDto(){ + dtos.Add(new InvitationDto(){ Id = inv.Id, Description = inv.Description, IsAdmin = inv.IsAdmin, @@ -68,7 +70,69 @@ public class InviteUserController : Controller }); } - return Ok(dto); + return Ok(dtos); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + return BadRequest($"An error occured: {e.Message}"); + } + } + + [HttpGet] + public async Task GetAllInvitations() + { + try + { + _logger.LogInformation("Getting all invitations by administrator"); + var read = await _repository.GetAllAsync(); + + List dtos = new List(); + + foreach (var inv in read) + { + dtos.Add(new InvitationDto(){ + Id = inv.Id, + Description = inv.Description, + IsAdmin = inv.IsAdmin, + MaxParticipants = inv.MaxParticipants, + Code = inv.Code, + CreatedAt = inv.DateCreated, + EndAt = inv.EndDate, + IsActive = inv.IsActive, + }); + } + + return Ok(dtos); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + return BadRequest($"An error occured: {e.Message}"); + } + } + + [HttpGet("{id:guid}")] + public async Task GetInvitationById(Guid id) + { + try + { + _logger.LogInformation("Getting invitations {id} by administrator"); + var read = await _repository.FindByIdAsync(id); + + var response = new InvitationDto(){ + Id = read.Id, + Description = read.Description, + IsAdmin = read.IsAdmin, + MaxParticipants = read.MaxParticipants, + Code = read.Code, + CreatedAt = read.DateCreated, + EndAt = read.EndDate, + IsActive = read.IsActive, + }; + + + return Ok(response); } catch (Exception e) { diff --git a/Govor.API/Govor.API.csproj b/Govor.API/Govor.API.csproj index 8eb3e48..d855102 100644 --- a/Govor.API/Govor.API.csproj +++ b/Govor.API/Govor.API.csproj @@ -24,8 +24,6 @@ - - diff --git a/Govor.Core/Infrastructure/Validators/FriendshipValidator.cs b/Govor.Core/Infrastructure/Validators/FriendshipValidator.cs new file mode 100644 index 0000000..3b11e00 --- /dev/null +++ b/Govor.Core/Infrastructure/Validators/FriendshipValidator.cs @@ -0,0 +1,38 @@ +using Govor.Core.Models; + +namespace Govor.Core.Infrastructure.Validators; + +public class FriendshipValidator : IObjectValidator +{ + public void Validate(Friendship friendship) + { + try + { + if(friendship is null) + throw new ArgumentNullException(nameof(friendship)); + if(friendship.Id == Guid.Empty) + throw new ArgumentException("Id cannot be empty", nameof(friendship.Id)); + if(friendship.AddresseeId == Guid.Empty) + throw new ArgumentException("Addresses cannot be empty", nameof(friendship.AddresseeId)); + if(friendship.RequesterId == Guid.Empty) + throw new ArgumentException("Requester cannot be empty", nameof(friendship.RequesterId)); + } + catch (Exception e) + { + throw new InvalidObjectException(e); + } + } + + public bool TryValidate(Friendship friendship) + { + try + { + Validate(friendship); + return true; + } + catch + { + return false; + } + } +} \ No newline at end of file diff --git a/Govor.Core/Models/Friendship.cs b/Govor.Core/Models/Friendship.cs new file mode 100644 index 0000000..6a7fe25 --- /dev/null +++ b/Govor.Core/Models/Friendship.cs @@ -0,0 +1,20 @@ +namespace Govor.Core.Models; + +public class Friendship +{ + public Guid Id { get; set; } + public Guid RequesterId { get; set; } + public Guid AddresseeId { get; set; } + public FriendshipStatus Status { get; set; } + + public User Requester { get; set; } + public User Addressee { get; set; } +} + +public enum FriendshipStatus +{ + Pending, + Accepted, + Rejected, + Blocked +} diff --git a/Govor.Core/Models/PrivateChat.cs b/Govor.Core/Models/PrivateChat.cs new file mode 100644 index 0000000..3dce79b --- /dev/null +++ b/Govor.Core/Models/PrivateChat.cs @@ -0,0 +1,11 @@ +namespace Govor.Core.Models; + +public class PrivateChat +{ + public Guid Id { get; set; } + public Guid UserAId { get; set; } + public Guid UserBId { get; set; } + public User UserA { get; set; } + public User UserB { get; set; } + public List Messages { get; set; } = new List(); +} \ No newline at end of file diff --git a/Govor.Core/Repositories/Friendships/IFriendshipsExist.cs b/Govor.Core/Repositories/Friendships/IFriendshipsExist.cs new file mode 100644 index 0000000..3715a2d --- /dev/null +++ b/Govor.Core/Repositories/Friendships/IFriendshipsExist.cs @@ -0,0 +1,6 @@ +namespace Govor.Core.Repositories.Friendships; + +public interface IFriendshipsExist +{ + bool Exist(Guid requesterId, Guid addresseeId); +} \ No newline at end of file diff --git a/Govor.Core/Repositories/Friendships/IFriendshipsReader.cs b/Govor.Core/Repositories/Friendships/IFriendshipsReader.cs new file mode 100644 index 0000000..dc1943b --- /dev/null +++ b/Govor.Core/Repositories/Friendships/IFriendshipsReader.cs @@ -0,0 +1,10 @@ +using Govor.Core.Models; + +namespace Govor.Core.Repositories.Friendships; + +public interface IFriendshipsReader +{ + Task> GetAllAsync(); + Task GetByIdAsync(Guid id); + Task> FindByUserIdAsync(Guid userId); +} \ No newline at end of file diff --git a/Govor.Core/Repositories/Friendships/IFriendshipsRepository.cs b/Govor.Core/Repositories/Friendships/IFriendshipsRepository.cs new file mode 100644 index 0000000..b88e805 --- /dev/null +++ b/Govor.Core/Repositories/Friendships/IFriendshipsRepository.cs @@ -0,0 +1,6 @@ +namespace Govor.Core.Repositories.Friendships; + +public interface IFriendshipsRepository : IFriendshipsReader, IFriendshipsWriter, IFriendshipsExist +{ + +} \ No newline at end of file diff --git a/Govor.Core/Repositories/Friendships/IFriendshipsWriter.cs b/Govor.Core/Repositories/Friendships/IFriendshipsWriter.cs new file mode 100644 index 0000000..2b1cfa4 --- /dev/null +++ b/Govor.Core/Repositories/Friendships/IFriendshipsWriter.cs @@ -0,0 +1,10 @@ +using Govor.Core.Models; + +namespace Govor.Core.Repositories.Friendships; + +public interface IFriendshipsWriter +{ + Task AddAsync(Friendship friendship); + Task UpdateAsync(Friendship friendship); + Task RemoveAsync(Friendship friendship); +} \ No newline at end of file diff --git a/Govor.Data/GovorDbContext.cs b/Govor.Data/GovorDbContext.cs index 48a1bb5..2ce583e 100644 --- a/Govor.Data/GovorDbContext.cs +++ b/Govor.Data/GovorDbContext.cs @@ -8,6 +8,8 @@ namespace Govor.Data; public class GovorDbContext(DbContextOptions options) : DbContext(options) { public virtual DbSet Users { get; set; } + public virtual DbSet Friendships { get; set; } + public virtual DbSet PrivateChats { get; set; } public virtual DbSet Admins { get; set; } public virtual DbSet Invitations { get; set; } diff --git a/Govor.Data/Repositories/FriendshipsRepository.cs b/Govor.Data/Repositories/FriendshipsRepository.cs new file mode 100644 index 0000000..1e7eed6 --- /dev/null +++ b/Govor.Data/Repositories/FriendshipsRepository.cs @@ -0,0 +1,119 @@ +using Govor.Core.Infrastructure.Extensions; +using Govor.Core.Infrastructure.Validators; +using Govor.Core.Models; +using Govor.Core.Repositories.Friendships; +using Govor.Data.Repositories.Exceptions; +using Microsoft.EntityFrameworkCore; + +namespace Govor.Data.Repositories; + +public class FriendshipsRepository : IFriendshipsRepository +{ + private GovorDbContext _context; + private IObjectValidator _validator; + + public FriendshipsRepository(GovorDbContext context, IObjectValidator validator) + { + _context = context; + _validator = validator; + } + + public async Task> GetAllAsync() + { + return await _context.Friendships + .AsNoTracking() + .Include(x => x.Requester) + .Include(x => x.Addressee) + .ToListOrThrowIfEmpty(new NotFoundException("Database is empty")); + } + + public async Task GetByIdAsync(Guid id) + { + return await _context.Friendships + .AsNoTracking() + .Include(x => x.Requester) + .Include(x => x.Addressee) + .FirstOrDefaultAsync(x => x.Id == id) + ?? throw new NotFoundByKeyException(id, "Friendship with given id was not found"); + } + + public async Task> FindByUserIdAsync(Guid userId) + { + return await _context.Friendships + .AsNoTracking() + .Include(x => x.Requester) + .Include(x => x.Addressee) + .Where(x => x.RequesterId == userId) + .ToListOrThrowIfEmpty(new NotFoundByKeyException(userId, "Friendship with given user id was not found")); + } + + public async Task AddAsync(Friendship friendship) + { + try + { + _validator.Validate(friendship); + + _context.Friendships.Add(friendship); + await _context.SaveChangesAsync(); + } + catch (InvalidObjectException ex) + { + throw new AdditionException("Admin with given data invalid", ex); + } + catch (Exception ex) + { + throw new AdditionException("Cannot add admin", ex); + } + } + + public async Task UpdateAsync(Friendship friendship) + { + try + { + _validator.Validate(friendship); + + var rowsAffected = await _context.Friendships + .Where(a => a.Id == friendship.Id) + .ExecuteUpdateAsync(u => u + .SetProperty(a => a.RequesterId, friendship.RequesterId) + .SetProperty(a => a.AddresseeId, friendship.AddresseeId) + .SetProperty(a => a.Status, friendship.Status) + ); + + if (rowsAffected == 0) + throw new NotFoundByKeyException(friendship.Id); + } + catch (NotFoundByKeyException ex) + { + throw new UpdateException($"Not found friendship by given id {friendship.Id}", ex); + } + catch (Exception ex) + { + throw new UpdateException($"Error when updating the friendship {friendship.Id}", ex); + } + } + + public async Task RemoveAsync(Friendship friendship) + { + try + { + var result = await GetByIdAsync(friendship.Id); + + _context.Friendships.Remove(result); + await _context.SaveChangesAsync(); + } + catch (NotFoundByKeyException ex) + { + throw new RemoveException($"Not found friendship by given id {friendship.Id}", ex); + } + catch (Exception ex) + { + throw new RemoveException("Error when removing the friendship", ex); + } + } + + public bool Exist(Guid requesterId, Guid addresseeId) + { + return _context.Friendships.AsNoTracking().Any(x => (x.RequesterId == requesterId && x.AddresseeId == addresseeId)); + } +} \ No newline at end of file