From 0413abf5873852220e6b6fa9e8e6a43af396c977 Mon Sep 17 00:00:00 2001 From: Carl Tibule Date: Wed, 8 Feb 2023 18:56:06 -0600 Subject: [PATCH] Modified API returned data format to ditch use of GenericResponse wrapper --- YABA.API/Controllers/BookmarksController.cs | 60 ++++++------ YABA.API/Settings/AutoMapperProfile.cs | 5 + .../ViewModels/Bookmarks/BookmarkResponse.cs | 17 ++++ .../{ => Bookmarks}/DeleteBookmarksRequest.cs | 2 +- .../UpdateBookmarkTagRequest.cs | 2 +- YABA.API/ViewModels/Response.cs | 22 ----- YABA.API/ViewModels/Tags/TagResponse.cs | 8 ++ .../Bookmarks/UpdateBookmarkRequestDTO.cs | 4 +- YABA.Common/DTOs/CrudResultDTO.cs | 12 --- YABA.Service/BookmarkService.cs | 92 +++++++++---------- YABA.Service/Interfaces/IBookmarkService.cs | 16 ++-- 11 files changed, 114 insertions(+), 126 deletions(-) create mode 100644 YABA.API/ViewModels/Bookmarks/BookmarkResponse.cs rename YABA.API/ViewModels/{ => Bookmarks}/DeleteBookmarksRequest.cs (71%) rename YABA.API/ViewModels/{ => Bookmarks}/UpdateBookmarkTagRequest.cs (70%) delete mode 100644 YABA.API/ViewModels/Response.cs create mode 100644 YABA.API/ViewModels/Tags/TagResponse.cs delete mode 100644 YABA.Common/DTOs/CrudResultDTO.cs diff --git a/YABA.API/Controllers/BookmarksController.cs b/YABA.API/Controllers/BookmarksController.cs index 6bbdcad..4ed642c 100644 --- a/YABA.API/Controllers/BookmarksController.cs +++ b/YABA.API/Controllers/BookmarksController.cs @@ -2,9 +2,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Net; -using YABA.API.ViewModels; +using YABA.API.ViewModels.Bookmarks; +using YABA.API.ViewModels.Tags; using YABA.Common.DTOs.Bookmarks; -using YABA.Common.DTOs.Tags; using YABA.Service.Interfaces; namespace YABA.API.Controllers @@ -13,15 +13,17 @@ namespace YABA.API.Controllers [Authorize, Route("api/v{version:apiVersion}/[controller]")] public class BookmarksController : ControllerBase { + private readonly IMapper _mapper; private readonly IBookmarkService _bookmarkService; - public BookmarksController(IBookmarkService bookmarkService) + public BookmarksController(IMapper mapper, IBookmarkService bookmarkService) { + _mapper = mapper; _bookmarkService = bookmarkService; } [HttpPost] - [ProducesResponseType(typeof(GenericResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(BookmarkResponse), (int)HttpStatusCode.Created)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task Create([FromBody] CreateBookmarkRequestDTO request) { @@ -29,92 +31,94 @@ namespace YABA.API.Controllers var result = await _bookmarkService.CreateBookmark(request); - if(!result.IsSuccessful) return BadRequest(new GenericResponse(result)); + if(result == null) return BadRequest(); - return Ok(new GenericResponse(result)); + return CreatedAtAction(nameof(Create), _mapper.Map(result)); } [HttpPost("{id}/Tags")] - [ProducesResponseType(typeof(IEnumerable>),(int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(IEnumerable),(int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task UpdateBookmarkTags(int id, [FromBody] UpdateBookmarkTagRequest request) { - if (request.Tags == null) return BadRequest(); + if (request.Tags == null || !request.Tags.Any()) return BadRequest(); var result = await _bookmarkService.UpdateBookmarkTags(id, request.Tags); - if (result.All(x => !x.IsSuccessful)) return NotFound(); + if (result == null) return NotFound(); - return Ok(result.Select(x => new GenericResponse(x))); + return Ok(_mapper.Map>(result)); } [HttpPut("{id}")] - [ProducesResponseType(typeof(GenericResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(BookmarkResponse), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task UpdateBookmark(int id, [FromBody] UpdateBookmarkRequestDTO request) { // TODO: Add support for HTTP PATCH var result = await _bookmarkService.UpdateBookmark(id, request); - if (!result.IsSuccessful) return NotFound(); + if (result == null) return NotFound(); - return Ok(new GenericResponse(result)); + return Ok(_mapper.Map(result)); } [HttpGet] - [ProducesResponseType(typeof(GenericResponse>), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] public IActionResult GetAll() { var result = _bookmarkService.GetAll(); - return Ok(new GenericResponse>(result)); + return Ok(_mapper.Map>(result)); } [HttpGet("{id}")] - [ProducesResponseType(typeof(GenericResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(BookmarkResponse), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task Get(int id) { var result = await _bookmarkService.Get(id); - if (!result.IsSuccessful) return NotFound(); + if (result == null) return NotFound(); - return Ok(new GenericResponse(result)); + return Ok(_mapper.Map(result)); } [HttpGet("{id}/Tags")] - [ProducesResponseType(typeof(GenericResponse>), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public IActionResult GetBookmarkTags(int id) { var result = _bookmarkService.GetBookmarkTags(id); - if (!result.IsSuccessful) return NotFound(); + if (result == null) return NotFound(); - return Ok(new GenericResponse>(result)); + return Ok(_mapper.Map>(result)); } [HttpDelete("{id}")] - [ProducesResponseType(typeof(GenericResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task Delete(int id) { var result = await _bookmarkService.DeleteBookmark(id); - if (!result.IsSuccessful) return NotFound(); + if (!result.HasValue) return NotFound(); - return Ok(new GenericResponse(result)); + return NoContent(); } [HttpDelete] - [ProducesResponseType(typeof(IEnumerable>), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task DeleteBookmarks([FromBody] DeleteBookmarksRequest request) - { + { + if (request.Ids == null || !request.Ids.Any()) return BadRequest(); + var result = await _bookmarkService.DeleteBookmarks(request.Ids); - if(result.All(x => !x.IsSuccessful)) return NotFound(); + if(result == null) return NotFound(); - return Ok(result.Select((x) => new GenericResponse(x))); + return NoContent(); } } } \ No newline at end of file diff --git a/YABA.API/Settings/AutoMapperProfile.cs b/YABA.API/Settings/AutoMapperProfile.cs index 9b2d941..6e6ba9e 100644 --- a/YABA.API/Settings/AutoMapperProfile.cs +++ b/YABA.API/Settings/AutoMapperProfile.cs @@ -1,6 +1,9 @@ using AutoMapper; using YABA.API.ViewModels; +using YABA.API.ViewModels.Bookmarks; +using YABA.API.ViewModels.Tags; using YABA.Common.DTOs; +using YABA.Common.DTOs.Bookmarks; using YABA.Common.DTOs.Tags; namespace YABA.API.Settings @@ -10,6 +13,8 @@ namespace YABA.API.Settings public AutoMapperProfile() { CreateMap(); + CreateMap(); + CreateMap(); } } } diff --git a/YABA.API/ViewModels/Bookmarks/BookmarkResponse.cs b/YABA.API/ViewModels/Bookmarks/BookmarkResponse.cs new file mode 100644 index 0000000..7f69066 --- /dev/null +++ b/YABA.API/ViewModels/Bookmarks/BookmarkResponse.cs @@ -0,0 +1,17 @@ +using YABA.API.ViewModels.Tags; + +namespace YABA.API.ViewModels.Bookmarks +{ + public class BookmarkResponse + { + public int Id { get; set; } + public DateTimeOffset CreatedOn { get; set; } + public DateTimeOffset LastModified { get; set; } + public string Title { get; set; } + public string? Description { get; set; } + public string? Note { get; set; } + public bool IsHidden { get; set; } + public string Url { get; set; } + public IEnumerable Tags { get; set; } + } +} diff --git a/YABA.API/ViewModels/DeleteBookmarksRequest.cs b/YABA.API/ViewModels/Bookmarks/DeleteBookmarksRequest.cs similarity index 71% rename from YABA.API/ViewModels/DeleteBookmarksRequest.cs rename to YABA.API/ViewModels/Bookmarks/DeleteBookmarksRequest.cs index bf031e2..1fccf58 100644 --- a/YABA.API/ViewModels/DeleteBookmarksRequest.cs +++ b/YABA.API/ViewModels/Bookmarks/DeleteBookmarksRequest.cs @@ -1,4 +1,4 @@ -namespace YABA.API.ViewModels +namespace YABA.API.ViewModels.Bookmarks { public class DeleteBookmarksRequest { diff --git a/YABA.API/ViewModels/UpdateBookmarkTagRequest.cs b/YABA.API/ViewModels/Bookmarks/UpdateBookmarkTagRequest.cs similarity index 70% rename from YABA.API/ViewModels/UpdateBookmarkTagRequest.cs rename to YABA.API/ViewModels/Bookmarks/UpdateBookmarkTagRequest.cs index f8a3872..d7bcb60 100644 --- a/YABA.API/ViewModels/UpdateBookmarkTagRequest.cs +++ b/YABA.API/ViewModels/Bookmarks/UpdateBookmarkTagRequest.cs @@ -1,4 +1,4 @@ -namespace YABA.API.ViewModels +namespace YABA.API.ViewModels.Bookmarks { public class UpdateBookmarkTagRequest { diff --git a/YABA.API/ViewModels/Response.cs b/YABA.API/ViewModels/Response.cs deleted file mode 100644 index c649b83..0000000 --- a/YABA.API/ViewModels/Response.cs +++ /dev/null @@ -1,22 +0,0 @@ -using YABA.Common.DTOs; -using YABA.Common.Extensions; - -namespace YABA.API.ViewModels -{ - public class GenericResponse - { - public T Entry { get; set; } - public int StatusId { get; set; } - public string StatusName { get; set; } - public string StatusMessage { get; set; } - - public GenericResponse(CrudResultDTO value) - { - // TODO: Find a way to bring this to AutoMapper - Entry = value.Entry; - StatusId = (int)value.CrudResult; - StatusName = value.CrudResult.ToString(); - StatusMessage = value.CrudResult.GetDisplayName(); - } - } -} diff --git a/YABA.API/ViewModels/Tags/TagResponse.cs b/YABA.API/ViewModels/Tags/TagResponse.cs new file mode 100644 index 0000000..b8ad1cc --- /dev/null +++ b/YABA.API/ViewModels/Tags/TagResponse.cs @@ -0,0 +1,8 @@ +namespace YABA.API.ViewModels.Tags +{ + public class TagResponse + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/YABA.Common/DTOs/Bookmarks/UpdateBookmarkRequestDTO.cs b/YABA.Common/DTOs/Bookmarks/UpdateBookmarkRequestDTO.cs index 5fceba3..abbf12a 100644 --- a/YABA.Common/DTOs/Bookmarks/UpdateBookmarkRequestDTO.cs +++ b/YABA.Common/DTOs/Bookmarks/UpdateBookmarkRequestDTO.cs @@ -9,8 +9,6 @@ namespace YABA.Common.DTOs.Bookmarks public string? Description { get; set; } public string? Note { get; set; } public bool IsHidden { get; set; } - - [Required] - public string Url { get; set; } + public string? Url { get; set; } } } diff --git a/YABA.Common/DTOs/CrudResultDTO.cs b/YABA.Common/DTOs/CrudResultDTO.cs deleted file mode 100644 index 176d56a..0000000 --- a/YABA.Common/DTOs/CrudResultDTO.cs +++ /dev/null @@ -1,12 +0,0 @@ -using YABA.Common.Extensions; -using YABA.Common.Lookups; - -namespace YABA.Common.DTOs -{ - public class CrudResultDTO - { - public CrudResultLookup CrudResult { get; set; } - public T Entry { get; set; } - public bool IsSuccessful => CrudResult.IsCrudResultSuccessful(); - } -} diff --git a/YABA.Service/BookmarkService.cs b/YABA.Service/BookmarkService.cs index 56484bb..82c20b2 100644 --- a/YABA.Service/BookmarkService.cs +++ b/YABA.Service/BookmarkService.cs @@ -39,7 +39,7 @@ namespace YABA.Service _mapper = mapper; } - public CrudResultDTO> GetAll() + public IEnumerable GetAll() { var currentUserId = GetCurrentUserId(); @@ -70,60 +70,52 @@ namespace YABA.Service userBookmarkDTOs.Add(bookmarkDTO); } - return new CrudResultDTO> { Entry = userBookmarkDTOs, CrudResult = CrudResultLookup.RetrieveSuccessful }; + return userBookmarkDTOs; } - public async Task?> CreateBookmark(CreateBookmarkRequestDTO request) + public async Task CreateBookmark(CreateBookmarkRequestDTO request) { - var crudResult = new CrudResultDTO() { Entry = request, CrudResult = CrudResultLookup.CreateFailed }; var currentUserId = GetCurrentUserId(); - if (!_roContext.Users.UserExists(currentUserId)) return crudResult; - - if(await _roContext.Bookmarks.AnyAsync(x => x.UserId == currentUserId && x.Url == request.Url)) - { - crudResult.CrudResult = CrudResultLookup.CreateFailedEntryExists; - return crudResult; - } + if (!_roContext.Users.UserExists(currentUserId) + || await _roContext.Bookmarks.AnyAsync(x => x.UserId == currentUserId && x.Url == request.Url)) return null; var bookmark = _mapper.Map(request); UpdateBookmarkWithMetaData(bookmark); bookmark.UserId = currentUserId; - await _context.Bookmarks.AddAsync(bookmark); + var newEntity = await _context.Bookmarks.AddAsync(bookmark); - if (await _context.SaveChangesAsync() > 0) crudResult.CrudResult = CrudResultLookup.CreateSucceeded; + if (await _context.SaveChangesAsync() > 0) return _mapper.Map(newEntity.Entity); - return crudResult; + return null; } - public async Task> UpdateBookmark(int id, UpdateBookmarkRequestDTO request) + public async Task UpdateBookmark(int id, UpdateBookmarkRequestDTO request) { - var crudResult = new CrudResultDTO() { Entry = request, CrudResult = CrudResultLookup.UpdateFailed }; var currentUserId = GetCurrentUserId(); var bookmark = _context.Bookmarks.FirstOrDefault(x => x.UserId == currentUserId && x.Id == id); - if(bookmark == null) return crudResult; + if(bookmark == null) return null; - bookmark.Title = request.Title; - bookmark.Description = request.Description; - bookmark.Note = request.Note; + bookmark.Title = !string.IsNullOrEmpty(request.Title) ? request.Title : bookmark.Title; + bookmark.Description = !string.IsNullOrEmpty(request.Description) ? request.Description : bookmark.Description; + bookmark.Note = !string.IsNullOrEmpty(request.Note) ? request.Note : bookmark.Note; bookmark.IsHidden = request.IsHidden; - bookmark.Url = request.Url; + bookmark.Url = !string.IsNullOrEmpty(request.Url) ? request.Url : bookmark.Url; UpdateBookmarkWithMetaData(bookmark); - if (await _context.SaveChangesAsync() > 0) crudResult.CrudResult = CrudResultLookup.UpdateSucceeded; + if (await _context.SaveChangesAsync() > 0) return _mapper.Map(bookmark); - return crudResult; + return null; } - public async Task>> UpdateBookmarkTags(int id, IEnumerable tags) + public async Task?> UpdateBookmarkTags(int id, IEnumerable tags) { - var crudResults = tags.Select((x) => new CrudResultDTO { Entry = x, CrudResult = CrudResultLookup.UpdateFailed }).ToList(); var currentUserId = GetCurrentUserId(); - if (!_roContext.Bookmarks.Any(x => x.Id == id && x.UserId == currentUserId)) return crudResults; + if (!_roContext.Bookmarks.Any(x => x.Id == id && x.UserId == currentUserId)) return null; // Add tags that are not yet in the database var savedUserTags = _context.Tags.Where(x => x.UserId == currentUserId).ToList(); @@ -148,17 +140,25 @@ namespace YABA.Service _context.BookmarkTags.RemoveRange(bookmarkTagsToRemove); await _context.BookmarkTags.AddRangeAsync(bookmarkTagsToAdd); - if (await _context.SaveChangesAsync() >= 0) crudResults.ForEach(x => x.CrudResult = CrudResultLookup.UpdateSucceeded); + if (await _context.SaveChangesAsync() >= 0) + { + var updatedBookmarkTags = _roContext.BookmarkTags + .Include(x => x.Tag) + .Where(x => x.BookmarkId == id) + .Select(x => x.Tag); - return crudResults; + return _mapper.Map>(updatedBookmarkTags); + } + + return null; } - public async Task> Get(int id) + public async Task Get(int id) { int.TryParse(_httpContextAccessor.HttpContext.User.Identity.GetUserId(), out int userId); var bookmark = await _roContext.Bookmarks.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId); - if (bookmark == null) return new CrudResultDTO { CrudResult = CrudResultLookup.RetrieveFailed, Entry = null }; + if (bookmark == null) return null; var bookmarkTags = _roContext.BookmarkTags .Include(x => x.Tag) @@ -169,13 +169,13 @@ namespace YABA.Service var bookmarkDTO = _mapper.Map(bookmark); bookmarkDTO.Tags = _mapper.Map>(bookmarkTags); - return new CrudResultDTO { CrudResult = CrudResultLookup.RetrieveSuccessful, Entry = bookmarkDTO }; + return bookmarkDTO; } - public CrudResultDTO> GetBookmarkTags(int id) + public IEnumerable? GetBookmarkTags(int id) { int.TryParse(_httpContextAccessor.HttpContext.User.Identity.GetUserId(), out int userId); - if (!_roContext.Bookmarks.Any(x => x.Id == id && x.UserId == userId)) return new CrudResultDTO> { Entry = null, CrudResult = CrudResultLookup.RetrieveFailed }; + if (!_roContext.Bookmarks.Any(x => x.Id == id && x.UserId == userId)) return null; var bookmarkTags = _roContext.BookmarkTags .Include(x => x.Tag) @@ -183,38 +183,28 @@ namespace YABA.Service .Select(x => x.Tag) .ToList(); - var bookmarkTagDTOs = _mapper.Map>(bookmarkTags); - - return new CrudResultDTO> { Entry = bookmarkTagDTOs, CrudResult = CrudResultLookup.RetrieveSuccessful }; + return _mapper.Map>(bookmarkTags); } - public async Task> DeleteBookmark(int id) + public async Task DeleteBookmark(int id) { - var crudResults = await DeleteBookmarks(new List { id }); - return crudResults.FirstOrDefault(); + var result = await DeleteBookmarks(new List { id }); + return result.FirstOrDefault(); } - public async Task>> DeleteBookmarks(IEnumerable ids) + public async Task?> DeleteBookmarks(IEnumerable ids) { - var crudResults = ids.Select(x => new CrudResultDTO { Entry = x, CrudResult = CrudResultLookup.DeleteFailed }).ToList(); var currentUserId = GetCurrentUserId(); - if (!await _roContext.Users.UserExistsAsync(currentUserId)) return crudResults; + if (!await _roContext.Users.UserExistsAsync(currentUserId)) return null; var entriesToDelete = _context.Bookmarks.Where(x => x.UserId == currentUserId && ids.Contains(x.Id)).ToList(); var entryIdsToDelete = entriesToDelete.Select(x => x.Id); _context.Bookmarks.RemoveRange(entriesToDelete); - if (await _context.SaveChangesAsync() <= 0) return crudResults; + if (await _context.SaveChangesAsync() <= 0) return null; - // Update crudResults that were found in the entriesToDelete to success - foreach(var crudResult in crudResults) - { - if (entryIdsToDelete.Contains(crudResult.Entry)) - crudResult.CrudResult = CrudResultLookup.DeleteSucceeded; - } - - return crudResults; + return ids; } private int GetCurrentUserId() diff --git a/YABA.Service/Interfaces/IBookmarkService.cs b/YABA.Service/Interfaces/IBookmarkService.cs index 924a9fe..b2aa9c9 100644 --- a/YABA.Service/Interfaces/IBookmarkService.cs +++ b/YABA.Service/Interfaces/IBookmarkService.cs @@ -8,14 +8,14 @@ namespace YABA.Service.Interfaces { public interface IBookmarkService { - Task> CreateBookmark(CreateBookmarkRequestDTO request); - Task> UpdateBookmark(int id, UpdateBookmarkRequestDTO request); - Task>> UpdateBookmarkTags(int id, IEnumerable tags); - CrudResultDTO> GetAll(); - Task> Get(int id); - CrudResultDTO> GetBookmarkTags(int id); - Task> DeleteBookmark(int id); - Task>> DeleteBookmarks(IEnumerable ids); + Task CreateBookmark(CreateBookmarkRequestDTO request); + Task UpdateBookmark(int id, UpdateBookmarkRequestDTO request); + Task?> UpdateBookmarkTags(int id, IEnumerable tags); + IEnumerable GetAll(); + Task Get(int id); + IEnumerable? GetBookmarkTags(int id); + Task DeleteBookmark(int id); + Task?> DeleteBookmarks(IEnumerable ids); } }