diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile
index 0966fa8..67582d1 100644
--- a/docker/api/Dockerfile
+++ b/docker/api/Dockerfile
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/core/aspnet:latest
-COPY musiccatalogue.api-1.17.0.0 /opt/musiccatalogue.api-1.17.0.0
-WORKDIR /opt/musiccatalogue.api-1.17.0.0/bin
+COPY musiccatalogue.api-1.18.0.0 /opt/musiccatalogue.api-1.18.0.0
+WORKDIR /opt/musiccatalogue.api-1.18.0.0/bin
ENTRYPOINT [ "./MusicCatalogue.Api" ]
diff --git a/docker/ui/Dockerfile b/docker/ui/Dockerfile
index aca9622..1fcf5c6 100644
--- a/docker/ui/Dockerfile
+++ b/docker/ui/Dockerfile
@@ -1,6 +1,6 @@
FROM node:20-alpine
-COPY musiccatalogue.ui-1.17.0.0 /opt/musiccatalogue.ui-1.17.0.0
-WORKDIR /opt/musiccatalogue.ui-1.17.0.0
+COPY musiccatalogue.ui-1.18.0.0 /opt/musiccatalogue.ui-1.18.0.0
+WORKDIR /opt/musiccatalogue.ui-1.18.0.0
RUN npm install
RUN npm run build
ENTRYPOINT [ "npm", "start" ]
diff --git a/src/MusicCatalogue.Api/Controllers/GenresController.cs b/src/MusicCatalogue.Api/Controllers/GenresController.cs
index 8aac318..a3240af 100644
--- a/src/MusicCatalogue.Api/Controllers/GenresController.cs
+++ b/src/MusicCatalogue.Api/Controllers/GenresController.cs
@@ -3,6 +3,7 @@
using MusicCatalogue.Entities.Database;
using MusicCatalogue.Entities.Exceptions;
using MusicCatalogue.Entities.Interfaces;
+using MusicCatalogue.Entities.Search;
namespace MusicCatalogue.Api.Controllers
{
@@ -20,18 +21,22 @@ public GenresController(IMusicCatalogueFactory factory)
}
///
- /// Return a list of all the genres in the catalogue
+ /// Return a list of genres matching the specified criteria
///
///
- [HttpGet]
- [Route("")]
- public async Task>> GetGenresAsync()
+ [HttpPost]
+ [Route("search")]
+ public async Task>> GetGenresAsync(GenreSearchCriteria criteria)
{
- // Get a list of all artists in the catalogue
- List genres = await _factory.Genres.ListAsync(x => true);
+ // Ideally, this method would use the GET verb but as more filtering criteria are added that leads
+ // to an increasing number of query string parameters and a very messy URL. So the filter criteria
+ // are POSTed in the request body, instead, and bound into a strongly typed criteria object
+
+ // Retrieve a list of matching genres
+ var genres = await _factory.Search.GenreSearchAsync(criteria);
// If there are no genres, return a no content response
- if (!genres.Any())
+ if (genres == null)
{
return NoContent();
}
diff --git a/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj b/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj
index c88a9f5..e63ac14 100644
--- a/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj
+++ b/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj
@@ -2,9 +2,9 @@
net7.0
- 1.17.0.0
- 1.17.0.0
- 1.17.0
+ 1.18.0.0
+ 1.18.0.0
+ 1.18.0
enable
enable
diff --git a/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj b/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj
index b428021..bc01c2f 100644
--- a/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj
+++ b/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj
@@ -5,7 +5,7 @@
enable
enable
MusicCatalogue.Data
- 1.16.0.0
+ 1.17.0.0
Dave Walker
Copyright (c) Dave Walker 2023
Dave Walker
@@ -17,7 +17,7 @@
https://github.com/davewalker5/MusicCatalogue
MIT
false
- 1.16.0.0
+ 1.17.0.0
diff --git a/src/MusicCatalogue.Entities/Interfaces/ISearchManager.cs b/src/MusicCatalogue.Entities/Interfaces/ISearchManager.cs
index cf5a709..8e1d7ea 100644
--- a/src/MusicCatalogue.Entities/Interfaces/ISearchManager.cs
+++ b/src/MusicCatalogue.Entities/Interfaces/ISearchManager.cs
@@ -7,5 +7,6 @@ public interface ISearchManager
{
Task?> AlbumSearchAsync(AlbumSearchCriteria criteria);
Task?> ArtistSearchAsync(ArtistSearchCriteria criteria);
+ Task?> GenreSearchAsync(GenreSearchCriteria criteria);
}
}
\ No newline at end of file
diff --git a/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj b/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj
index 927891c..2e91ad9 100644
--- a/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj
+++ b/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj
@@ -5,7 +5,7 @@
enable
enable
MusicCatalogue.Entities
- 1.16.0.0
+ 1.17.0.0
Dave Walker
Copyright (c) Dave Walker 2023
Dave Walker
@@ -17,7 +17,7 @@
https://github.com/davewalker5/MusicCatalogue
MIT
false
- 1.16.0.0
+ 1.17.0.0
diff --git a/src/MusicCatalogue.Entities/Search/GenreSearchCriteria.cs b/src/MusicCatalogue.Entities/Search/GenreSearchCriteria.cs
new file mode 100644
index 0000000..d61bd02
--- /dev/null
+++ b/src/MusicCatalogue.Entities/Search/GenreSearchCriteria.cs
@@ -0,0 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace MusicCatalogue.Entities.Search
+{
+ [ExcludeFromCodeCoverage]
+ public class GenreSearchCriteria
+ {
+ public bool? WishList { get; set; }
+ }
+}
diff --git a/src/MusicCatalogue.Logic/Database/SearchManager.cs b/src/MusicCatalogue.Logic/Database/SearchManager.cs
index 520c501..996d9a9 100644
--- a/src/MusicCatalogue.Logic/Database/SearchManager.cs
+++ b/src/MusicCatalogue.Logic/Database/SearchManager.cs
@@ -1,7 +1,6 @@
using MusicCatalogue.Entities.Database;
using MusicCatalogue.Entities.Interfaces;
using MusicCatalogue.Entities.Search;
-using System.Runtime.InteropServices;
namespace MusicCatalogue.Logic.Database
{
@@ -30,29 +29,29 @@ internal SearchManager(IMusicCatalogueFactory factory) : base(factory)
// as "return artists who have produced an album in the Jazz genre". So, start by retrieving a
// list of albums matching the criteria then derive the artists from that
var albums = await Factory.Albums
- .ListAsync(x => (
+ .ListAsync(x => (
(criteria.WishList == null) ||
((criteria.WishList == false) && (x.IsWishListItem == null)) ||
(x.IsWishListItem == criteria.WishList)
- ) &&
- (
+ ) &&
+ (
(criteria.GenreId == null) ||
(x.GenreId == criteria.GenreId)
- ));
+ ));
// If there are no albums, there can't be any matching artists
if (albums.Any())
{
- // Compile a list of artist IDs and load the matching artists
- var artistIds = albums.Select(x => x.ArtistId).ToList();
+ // Compile a list of unique artist IDs and load the matching artists
+ var artistIds = albums.Select(x => x.ArtistId).Distinct().ToList();
artists = await Factory.Artists
- .ListAsync(x => artistIds.Contains(x.Id) &&
- (
+ .ListAsync(x => artistIds.Contains(x.Id) &&
+ (
(prefix == null) ||
((x.SearchableName != null) && x.SearchableName.StartsWith(prefix)) ||
((x.SearchableName == null) && x.Name.StartsWith(prefix))
- ),
- false);
+ ),
+ false);
// Now map the albums onto their associated artists
foreach (var artist in artists)
@@ -90,5 +89,32 @@ internal SearchManager(IMusicCatalogueFactory factory) : base(factory)
return albums.Any() ? albums : null;
}
+ ///
+ /// Return the genres matching the specified criteria
+ ///
+ ///
+ ///
+ public async Task?> GenreSearchAsync(GenreSearchCriteria criteria)
+ {
+ List? genres = null;
+
+ // Retrieve a list of albums, as they're the entity that's tagged with a genre
+ var albums = await Factory.Albums
+ .ListAsync(x => (
+ (criteria.WishList == null) ||
+ ((criteria.WishList == false) && (x.IsWishListItem == null)) ||
+ (x.IsWishListItem == criteria.WishList)
+ ));
+
+ // If there are no albums, there can't be any matching artists
+ if (albums.Any())
+ {
+ // Get a list of unique genre IDs and load the matching genres
+ var genreIds = albums.Select(x => x.GenreId).Distinct().ToList();
+ genres = await Factory.Genres.ListAsync(x => genreIds.Contains(x.Id));
+ }
+
+ return genres;
+ }
}
}
diff --git a/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj b/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj
index bdfa29a..fe415bd 100644
--- a/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj
+++ b/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj
@@ -5,7 +5,7 @@
enable
enable
MusicCatalogue.Logic
- 1.16.0.0
+ 1.17.0.0
Dave Walker
Copyright (c) Dave Walker 2023
Dave Walker
@@ -17,7 +17,7 @@
https://github.com/davewalker5/MusicCatalogue
MIT
false
- 1.16.0.0
+ 1.17.0.0
diff --git a/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj b/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj
index dd4ae0d..aa44384 100644
--- a/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj
+++ b/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj
@@ -3,9 +3,9 @@
Exe
net7.0
- 1.16.0.0
- 1.16.0.0
- 1.16.0
+ 1.17.0.0
+ 1.17.0.0
+ 1.17.0
enable
enable
false
diff --git a/src/MusicCatalogue.Tests/AlbumSearchManagerTest.cs b/src/MusicCatalogue.Tests/AlbumSearchManagerTest.cs
index a3ed1fa..119d756 100644
--- a/src/MusicCatalogue.Tests/AlbumSearchManagerTest.cs
+++ b/src/MusicCatalogue.Tests/AlbumSearchManagerTest.cs
@@ -29,7 +29,7 @@ public void TestInitialize()
// Add the artists
_jazzArtistId = Task.Run(() => _factory.Artists.AddAsync("Diana Krall")).Result.Id;
- _popGenreId = Task.Run(() => _factory.Artists.AddAsync("Katie Melua")).Result.Id;
+ _popArtistId = Task.Run(() => _factory.Artists.AddAsync("Katie Melua")).Result.Id;
// Add the albums, one on the wishlist and one not
Task.Run(() => _factory.Albums.AddAsync(_jazzArtistId, _jazzGenreId, JazzAlbumTitle, 2002, null, false, null, null, null)).Wait();
diff --git a/src/MusicCatalogue.Tests/GenreSearchManagerTest.cs b/src/MusicCatalogue.Tests/GenreSearchManagerTest.cs
new file mode 100644
index 0000000..1a48d35
--- /dev/null
+++ b/src/MusicCatalogue.Tests/GenreSearchManagerTest.cs
@@ -0,0 +1,82 @@
+using MusicCatalogue.Data;
+using MusicCatalogue.Entities.Interfaces;
+using MusicCatalogue.Entities.Search;
+using MusicCatalogue.Logic.Factory;
+
+namespace MusicCatalogue.Tests
+{
+ [TestClass]
+ public class GenreSearchManagerTest
+ {
+ private const string JazzGenre = "Jazz";
+ private const string PopGenre = "Pop";
+
+ private IMusicCatalogueFactory? _factory;
+ private int _jazzGenreId;
+ private int _popGenreId;
+ private int _jazzArtistId;
+ private int _popArtistId;
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ MusicCatalogueDbContext context = MusicCatalogueDbContextFactory.CreateInMemoryDbContext();
+ _factory = new MusicCatalogueFactory(context);
+
+ // Add the genres
+ _jazzGenreId = Task.Run(() => _factory.Genres.AddAsync("Jazz")).Result.Id;
+ _popGenreId = Task.Run(() => _factory.Genres.AddAsync("Pop")).Result.Id;
+
+ // Add the artists
+ _jazzArtistId = Task.Run(() => _factory.Artists.AddAsync("Diana Krall")).Result.Id;
+ _popArtistId = Task.Run(() => _factory.Artists.AddAsync("Katie Melua")).Result.Id;
+ }
+
+ [TestMethod]
+ public async Task SearchForAllGenresTest()
+ {
+ // Add the albums, one on the wishlist and one not
+ Task.Run(() => _factory!.Albums.AddAsync(_jazzArtistId, _jazzGenreId, "Live In Paris", 2002, null, false, null, null, null)).Wait();
+ Task.Run(() => _factory!.Albums.AddAsync(_popArtistId, _popGenreId, "Album No. 8", 2020, null, true, null, null, null)).Wait();
+
+ var genres = await _factory!.Search.GenreSearchAsync(new GenreSearchCriteria());
+ Assert.IsNotNull(genres);
+ Assert.AreEqual(2, genres.Count);
+ }
+
+ [TestMethod]
+ public async Task SearchMainCatalogueGenresTest()
+ {
+ // Add the albums, one on the wishlist and one not
+ Task.Run(() => _factory!.Albums.AddAsync(_jazzArtistId, _jazzGenreId, "Live In Paris", 2002, null, false, null, null, null)).Wait();
+ Task.Run(() => _factory!.Albums.AddAsync(_popArtistId, _popGenreId, "Album No. 8", 2020, null, true, null, null, null)).Wait();
+
+ var criteria = new GenreSearchCriteria { WishList = false };
+ var genres = await _factory!.Search.GenreSearchAsync(criteria);
+ Assert.IsNotNull(genres);
+ Assert.AreEqual(1, genres.Count);
+ Assert.AreEqual(JazzGenre, genres[0].Name);
+ }
+
+ [TestMethod]
+ public async Task SearchWishlistGenresTest()
+ {
+ // Add the albums, one on the wishlist and one not
+ Task.Run(() => _factory!.Albums.AddAsync(_jazzArtistId, _jazzGenreId, "Live In Paris", 2002, null, false, null, null, null)).Wait();
+ Task.Run(() => _factory!.Albums.AddAsync(_popArtistId, _popGenreId, "Album No. 8", 2020, null, true, null, null, null)).Wait();
+
+ var criteria = new GenreSearchCriteria { WishList = true };
+ var genres = await _factory!.Search.GenreSearchAsync(criteria);
+ Assert.IsNotNull(genres);
+ Assert.AreEqual(1, genres.Count);
+ Assert.AreEqual(PopGenre, genres[0].Name);
+ }
+
+ [TestMethod]
+ public async Task NoMatchesTest()
+ {
+ var genres = await _factory!.Search.GenreSearchAsync(new GenreSearchCriteria());
+ Assert.IsNull(genres);
+ }
+ }
+}