From 4ba60f4eff95599d4e2550c3eb30278a25c444bb Mon Sep 17 00:00:00 2001 From: Camilo Bernal Date: Tue, 4 Oct 2022 17:36:05 -0500 Subject: [PATCH 1/3] Add support for remove book from wishlist --- .../Exceptions/BookNotFoundInListException.cs | 8 ++ .../Store/IUserWishlistsRepository.cs | 3 + .../Store/UserWishlistsRepository.cs | 21 ++++++ .../Modules/UserWishlistsModule.cs | 75 ++++++++++++++++++- 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/Application/Exceptions/BookNotFoundInListException.cs diff --git a/src/Application/Exceptions/BookNotFoundInListException.cs b/src/Application/Exceptions/BookNotFoundInListException.cs new file mode 100644 index 0000000..a8580bf --- /dev/null +++ b/src/Application/Exceptions/BookNotFoundInListException.cs @@ -0,0 +1,8 @@ +namespace BooksWishlist.Application.Exceptions; + +public class BookNotFoundInListException : Exception +{ + public BookNotFoundInListException() : base("The book was not found in the collection.") + { + } +} diff --git a/src/Infrastructure/Store/IUserWishlistsRepository.cs b/src/Infrastructure/Store/IUserWishlistsRepository.cs index 752ff08..a6a2246 100644 --- a/src/Infrastructure/Store/IUserWishlistsRepository.cs +++ b/src/Infrastructure/Store/IUserWishlistsRepository.cs @@ -16,5 +16,8 @@ Task UpdateAsync(UserWishlists list, string listName, string owne Task AddBooksAsync(string listName, IEnumerable? books, string owner, CancellationToken cancellationToken = default); + Task RemoveBooksAsync(string listName, string bookId, string owner, + CancellationToken cancellationToken = default); + Task FindByNameAsync(string listName, string owner, CancellationToken cancellationToken = default); } diff --git a/src/Infrastructure/Store/UserWishlistsRepository.cs b/src/Infrastructure/Store/UserWishlistsRepository.cs index 59efa4f..e1fc6b4 100644 --- a/src/Infrastructure/Store/UserWishlistsRepository.cs +++ b/src/Infrastructure/Store/UserWishlistsRepository.cs @@ -93,6 +93,27 @@ join newBooks in books on registeredBooks.BookId equals newBooks.BookId return true; } + public async Task RemoveBooksAsync(string listName, string bookId, string owner, + CancellationToken cancellationToken = default) + { + var filterDefinition = GetFilterByNameAndOwner(listName, owner); + var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); + if (foundList is null) + { + throw new WishListNotFoundException(); + } + + var foundBook = foundList.Books?.FirstOrDefault(b => b?.BookId == bookId); + if (foundBook is null) + { + throw new BookNotFoundInListException(); + } + + foundList.Books = foundList?.Books?.Where(b => b?.BookId != bookId); + await _unitOfWork.UpdateAsync(filterDefinition, foundList, cancellationToken); + return true; + } + public Task FindByNameAsync(string listName, string owner, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/src/Presentation/Modules/UserWishlistsModule.cs b/src/Presentation/Modules/UserWishlistsModule.cs index 0b2c628..a92a00c 100644 --- a/src/Presentation/Modules/UserWishlistsModule.cs +++ b/src/Presentation/Modules/UserWishlistsModule.cs @@ -11,9 +11,82 @@ public static IEndpointRouteBuilder MapUserWishlistsEndpoints(this IEndpointRout MapDeleteWishlistsEndpoint(routes); MapAddBookToWishListEndpoint(routes); MapEditWishListEndpoint(routes); + MapRemoveBookToWishListEndpoint(routes); return routes; } + private static void MapRemoveBookToWishListEndpoint(IEndpointRouteBuilder routes) => + routes.MapDelete("/wishlists/{listName?}/books/{bookId?}", [Authorize] async ( + IUserWishlistsRepository wishlistsRepository, + IGoogleBooksService booksService, + ILoggerService log, HttpContext ctx, [FromRoute] string? listName, + [FromRoute] string? bookId, + CancellationToken cancellationToken) => + { + if (bookId is null) + { + return Utils.BuildBadRequestResult("Book Id for remove not provided", + "You must send the id of the book to remove from the list"); + } + + if (listName == null) + { + return Utils.BuildBadRequestResult("Wishlist name not provided", + "You must specify the name of the list that you want to delete."); + } + + try + { + var currentUser = ctx.User.Identity?.Name; + if (currentUser is null) + { + return Results.Unauthorized(); + } + + _ = await wishlistsRepository.RemoveBooksAsync(listName, bookId, currentUser, cancellationToken); + return Results.NoContent(); + } + catch (InvalidBookReferenceException invalidBookReferenceException) + { + log.LogError(invalidBookReferenceException.Message, invalidBookReferenceException); + return Utils.BuildBadRequestResult("Invalid Book Reference", invalidBookReferenceException.Message); + } + catch (WishListNotFoundException notFoundException) + { + log.LogError(notFoundException, notFoundException.Message); + return Results.NotFound(new ProblemDetails + { + Title = "Wishlist not found", + Detail = + "The specified Wishlist was not found in the database or is not associated with the current user", + Status = 404 + }); + } + catch (DuplicatedBookInListException duplicatedBookException) + { + log.LogError(duplicatedBookException.Message, duplicatedBookException); + return Results.Conflict(new ProblemDetails + { + Status = 409, + Detail = duplicatedBookException.Message, + Title = "Book duplicated in wishlist", + Type = Constants.ConflictResponseType + }); + } + catch (Exception e) + { + log.LogError(e.Message, e); + return Results.Problem(e.Message, title: "Error creating the wishlist"); + } + }) + .WithName("/wishlists/books/delete") + .WithTags("Business Endpoints") + .Produces(204) + .ProducesProblem(400) + .ProducesProblem(401) + .ProducesProblem(500) + .RequireAuthorization(); + private static void MapAddBookToWishListEndpoint(IEndpointRouteBuilder routes) => routes.MapPut("/wishlists/{listName?}/books/", [Authorize] async (IUserWishlistsRepository wishlistsRepository, @@ -318,7 +391,7 @@ await wishlistsDtoValidator.HandleModelValidationAsync(wishlist, log, }) .WithName("/wishlists/put") .WithTags("Business Endpoints") - .Produces(201) + .Produces(202) .ProducesProblem(400) .ProducesProblem(409) .ProducesProblem(401) From 7434715c6b69bea9fc69955cc39d978923be50c9 Mon Sep 17 00:00:00 2001 From: Camilo Bernal Date: Tue, 4 Oct 2022 17:52:38 -0500 Subject: [PATCH 2/3] List books in list --- .../Store/IUserWishlistsRepository.cs | 4 ++ .../Store/UserWishlistsRepository.cs | 13 ++++++ .../Modules/UserWishlistsModule.cs | 41 +++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/src/Infrastructure/Store/IUserWishlistsRepository.cs b/src/Infrastructure/Store/IUserWishlistsRepository.cs index a6a2246..cbe0fbd 100644 --- a/src/Infrastructure/Store/IUserWishlistsRepository.cs +++ b/src/Infrastructure/Store/IUserWishlistsRepository.cs @@ -19,5 +19,9 @@ Task UpdateAsync(UserWishlists list, string listName, string owne Task RemoveBooksAsync(string listName, string bookId, string owner, CancellationToken cancellationToken = default); + + Task?> GetListBooks(string listName, string owner, + CancellationToken cancellationToken = default); + Task FindByNameAsync(string listName, string owner, CancellationToken cancellationToken = default); } diff --git a/src/Infrastructure/Store/UserWishlistsRepository.cs b/src/Infrastructure/Store/UserWishlistsRepository.cs index e1fc6b4..8462e08 100644 --- a/src/Infrastructure/Store/UserWishlistsRepository.cs +++ b/src/Infrastructure/Store/UserWishlistsRepository.cs @@ -114,6 +114,19 @@ join newBooks in books on registeredBooks.BookId equals newBooks.BookId return true; } + public async Task?> GetListBooks(string listName, string owner, + CancellationToken cancellationToken = default) + { + var filterDefinition = GetFilterByNameAndOwner(listName, owner); + var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); + if (foundList is null) + { + throw new WishListNotFoundException(); + } + + return foundList.Books; + } + public Task FindByNameAsync(string listName, string owner, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/src/Presentation/Modules/UserWishlistsModule.cs b/src/Presentation/Modules/UserWishlistsModule.cs index a92a00c..9805059 100644 --- a/src/Presentation/Modules/UserWishlistsModule.cs +++ b/src/Presentation/Modules/UserWishlistsModule.cs @@ -12,9 +12,50 @@ public static IEndpointRouteBuilder MapUserWishlistsEndpoints(this IEndpointRout MapAddBookToWishListEndpoint(routes); MapEditWishListEndpoint(routes); MapRemoveBookToWishListEndpoint(routes); + MapListWishlistsBooksEndpoint(routes); return routes; } + + private static void MapListWishlistsBooksEndpoint(IEndpointRouteBuilder routes) => + routes.MapGet("/wishlists/{listName?}/books", [Authorize] async (HttpContext ctx, + IUserWishlistsRepository wishlistsRepository, + ILoggerService log, + [FromRoute] string? listName, + CancellationToken cancellationToken) => + { + if (listName == null) + { + return Utils.BuildBadRequestResult("Wishlist name not provided", + "You must specify the name of the list that you want to list."); + } + + var currentUser = ctx.User.Identity?.Name; + if (currentUser is null) + { + return Results.Unauthorized(); + } + + try + { + var booksInList = await wishlistsRepository.GetListBooks(listName, currentUser, cancellationToken); + return Results.Ok(booksInList); + } + catch (Exception e) + { + log.LogError(e.Message, e); + return Results.Problem(e.Message, title: "Error creating the wishlist"); + } + }) + .WithName("wishlists/books/get") + .WithTags("Business Endpoints") + .Produces>() + .ProducesProblem(400) + .ProducesProblem(401) + .ProducesProblem(500) + .RequireAuthorization(); + + private static void MapRemoveBookToWishListEndpoint(IEndpointRouteBuilder routes) => routes.MapDelete("/wishlists/{listName?}/books/{bookId?}", [Authorize] async ( IUserWishlistsRepository wishlistsRepository, From 6a906501cbebcb6e07236a3a42e3e338b498cc0b Mon Sep 17 00:00:00 2001 From: Camilo Bernal Date: Tue, 4 Oct 2022 18:17:35 -0500 Subject: [PATCH 3/3] Refactor repository --- .../Store/UserWishlistsRepository.cs | 91 +++++++------------ 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/src/Infrastructure/Store/UserWishlistsRepository.cs b/src/Infrastructure/Store/UserWishlistsRepository.cs index 8462e08..a91a5f9 100644 --- a/src/Infrastructure/Store/UserWishlistsRepository.cs +++ b/src/Infrastructure/Store/UserWishlistsRepository.cs @@ -31,28 +31,18 @@ public async Task CreateAsync(UserWishlists list, CancellationTok public async Task UpdateAsync(UserWishlists list, string listName, string owner, CancellationToken cancellationToken = default) { - var filterDefinition = GetFilterByNameAndOwner(listName, owner); - var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); - if (foundList is null) - { - throw new WishListNotFoundException(); - } - - foundList.Merge(list); - await _unitOfWork.UpdateAsync(filterDefinition, list, cancellationToken); - return foundList; + var foundList = await GetUserWishlist(listName, owner, cancellationToken: cancellationToken); + foundList.list.Merge(list); + await _unitOfWork.UpdateAsync(foundList.filterDefinition, list, cancellationToken); + return foundList.list; } public async Task DeleteAsync(string listName, string owner, CancellationToken cancellationToken = default) { - var filterDefinition = GetFilterByNameAndOwner(listName, owner); - var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); - if (foundList is null) - { - throw new WishListNotFoundException(); - } - - await _unitOfWork.RemoveAsync(filterDefinition, cancellationToken); + var filterDefinition = + await GetUserWishlist(listName, owner, + cancellationToken: cancellationToken); //For handle not found exception + await _unitOfWork.RemoveAsync(filterDefinition.filterDefinition, cancellationToken); _log.LogWarning($"The Wishlist with name {listName} associated with the user {owner} and was deleted."); return true; } @@ -60,21 +50,11 @@ public async Task DeleteAsync(string listName, string owner, CancellationT public async Task AddBooksAsync(string listName, IEnumerable? books, string owner, CancellationToken cancellationToken = default) { - if (books is null) + if (books is null) return null; + var foundList = await GetUserWishlist(listName, owner, cancellationToken: cancellationToken); + if (foundList.list.Books is not null && foundList.list.Books.Any()) { - return null; - } - - var filterDefinition = GetFilterByNameAndOwner(listName, owner); - var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); - if (foundList is null) - { - throw new WishListNotFoundException(); - } - - if (foundList.Books is not null && foundList.Books.Any()) - { - var conflictedBooks = (from registeredBooks in foundList.Books + var conflictedBooks = (from registeredBooks in foundList.list.Books join newBooks in books on registeredBooks.BookId equals newBooks.BookId select newBooks).ToList(); if (conflictedBooks.Any()) @@ -82,49 +62,33 @@ join newBooks in books on registeredBooks.BookId equals newBooks.BookId throw new DuplicatedBookInListException($"The book had already been added to the WishList {listName}"); } - foundList.Books = foundList.Books.Concat(books); + foundList.list.Books = foundList.list.Books.Concat(books); } else { - foundList.Books = books; + foundList.list.Books = books; } - await _unitOfWork.UpdateAsync(filterDefinition, foundList, cancellationToken); + await _unitOfWork.UpdateAsync(foundList.filterDefinition, foundList.list, cancellationToken); return true; } public async Task RemoveBooksAsync(string listName, string bookId, string owner, CancellationToken cancellationToken = default) { - var filterDefinition = GetFilterByNameAndOwner(listName, owner); - var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); - if (foundList is null) - { - throw new WishListNotFoundException(); - } - - var foundBook = foundList.Books?.FirstOrDefault(b => b?.BookId == bookId); - if (foundBook is null) - { - throw new BookNotFoundInListException(); - } - - foundList.Books = foundList?.Books?.Where(b => b?.BookId != bookId); - await _unitOfWork.UpdateAsync(filterDefinition, foundList, cancellationToken); + var foundList = await GetUserWishlist(listName, owner, cancellationToken: cancellationToken); + var foundBook = foundList.list.Books?.FirstOrDefault(b => b?.BookId == bookId); + if (foundBook is null) throw new BookNotFoundInListException(); + foundList.list.Books = foundList.list.Books?.Where(b => b?.BookId != bookId); + await _unitOfWork.UpdateAsync(foundList.filterDefinition, foundList.list, cancellationToken); return true; } public async Task?> GetListBooks(string listName, string owner, CancellationToken cancellationToken = default) { - var filterDefinition = GetFilterByNameAndOwner(listName, owner); - var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); - if (foundList is null) - { - throw new WishListNotFoundException(); - } - - return foundList.Books; + var foundList = await GetUserWishlist(listName, owner, cancellationToken: cancellationToken); + return foundList.list?.Books; } public Task FindByNameAsync(string listName, string owner, @@ -151,6 +115,17 @@ private static FilterDefinition GetFilterByNameAndOwner(string li } + private async Task<(UserWishlists list, FilterDefinition filterDefinition)> GetUserWishlist( + string listName, string owner, bool handleNotFoundException = true, + CancellationToken cancellationToken = default) + { + var filterDefinition = GetFilterByNameAndOwner(listName, owner); + var foundList = await _unitOfWork.GetOneAsync(filterDefinition, cancellationToken); + if (foundList == null && handleNotFoundException) throw new WishListNotFoundException(); + return (foundList, filterDefinition); + } + + private async Task WishListExists(string listName, string owner, CancellationToken cancellationToken = default) {