Skip to content

Commit

Permalink
Merge pull request #37 from davewalker5/MC-219-Track-Editor
Browse files Browse the repository at this point in the history
MC-219 Add a Track Editor
  • Loading branch information
davewalker5 authored Nov 24, 2023
2 parents 2f8a477 + ecadadf commit 6b3956c
Show file tree
Hide file tree
Showing 26 changed files with 596 additions and 55 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ MusicCatalogue.LookupTool --lookup "John Coltrane" "Blue Train" catalogue
<img src="diagrams/track-list.png" alt="Track List" width="600">

- Clicking on the artist name in any row in the track list or clicking on the "Back" button returns to the album list for that artist
- Clicking on the "Delete" icon will prompt for confirmation and, if the action is confirmed, will delete the track
- Clicking on the "Edit" icon opens the track editor to edit the track properties:

<img src="diagrams/track-editor.png" alt="Track List" width="600">

- Clicking on the "Add" button at the bottom of the track list will open a blank track editor to add and save a new track

#### Browsing By Genre

Expand Down
Binary file added diagrams/track-editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified diagrams/track-list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docker/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/core/aspnet:latest
COPY musiccatalogue.api-1.21.0.0 /opt/musiccatalogue.api-1.21.0.0
WORKDIR /opt/musiccatalogue.api-1.21.0.0/bin
COPY musiccatalogue.api-1.22.0.0 /opt/musiccatalogue.api-1.22.0.0
WORKDIR /opt/musiccatalogue.api-1.22.0.0/bin
ENTRYPOINT [ "./MusicCatalogue.Api" ]
4 changes: 2 additions & 2 deletions docker/ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM node:20-alpine
COPY musiccatalogue.ui-1.24.0.0 /opt/musiccatalogue.ui-1.24.0.0
WORKDIR /opt/musiccatalogue.ui-1.24.0.0
COPY musiccatalogue.ui-1.25.0.0 /opt/musiccatalogue.ui-1.25.0.0
WORKDIR /opt/musiccatalogue.ui-1.25.0.0
RUN npm install
RUN npm run build
ENTRYPOINT [ "npm", "start" ]
76 changes: 76 additions & 0 deletions src/MusicCatalogue.Api/Controllers/TracksController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MusicCatalogue.Entities.Database;
using MusicCatalogue.Entities.Interfaces;

namespace MusicCatalogue.Api.Controllers
{
[Authorize]
[ApiController]
[ApiConventionType(typeof(DefaultApiConventions))]
[Route("[controller]")]
public class TracksController : Controller
{
private readonly IMusicCatalogueFactory _factory;

public TracksController(IMusicCatalogueFactory factory)
{
_factory = factory;
}

/// <summary>
/// Add a track to the catalogue
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPost]
[Route("")]
public async Task<ActionResult<Track>> AddTrackAsync([FromBody] Track template)
{
var track = await _factory.Tracks.AddAsync(template.AlbumId, template.Title, template.Number, template.Duration);
return track;
}

/// <summary>
/// Update an existing track
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPut]
[Route("")]
public async Task<ActionResult<Track?>> UpdateTrackAsync([FromBody] Track template)
{
var track = await _factory.Tracks.UpdateAsync(
template.Id,
template.AlbumId,
template.Title,
template.Number,
template.Duration);
return track;
}

/// <summary>
/// Delete an existing track
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Route("{id}")]
public async Task<IActionResult> DeleteTrackAsync(int id)
{
// Make sure the track exists
var track = await _factory.Tracks.GetAsync(x => x.Id == id);

// If the track doesn't exist, return a 404
if (track == null)
{
return NotFound();
}

// Delete the track
await _factory.Tracks.DeleteAsync(id);

return Ok();
}
}
}
6 changes: 3 additions & 3 deletions src/MusicCatalogue.Api/MusicCatalogue.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ReleaseVersion>1.20.0.0</ReleaseVersion>
<FileVersion>1.20.0.0</FileVersion>
<ProductVersion>1.20.0</ProductVersion>
<ReleaseVersion>1.21.0.0</ReleaseVersion>
<FileVersion>1.21.0.0</FileVersion>
<ProductVersion>1.21.0</ProductVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/MusicCatalogue.Data/MusicCatalogue.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>MusicCatalogue.Data</PackageId>
<PackageVersion>1.19.0.0</PackageVersion>
<PackageVersion>1.20.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2023</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -17,7 +17,7 @@
<PackageProjectUrl>https://github.com/davewalker5/MusicCatalogue</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.19.0.0</ReleaseVersion>
<ReleaseVersion>1.20.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 4 additions & 2 deletions src/MusicCatalogue.Entities/Interfaces/ITrackManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ namespace MusicCatalogue.Entities.Interfaces
public interface ITrackManager
{
Task<Track> AddAsync(int albumId, string title, int? number, int? duration);
Task<Track> GetAsync(Expression<Func<Track, bool>> predicate);
Task<Track?> GetAsync(Expression<Func<Track, bool>> predicate);
Task<List<Track>> ListAsync(Expression<Func<Track, bool>> predicate);
Task DeleteAsync(int albumId);
Task<Track?> UpdateAsync(int trackId, int albumId, string title, int? number, int? duration);
Task DeleteAsync(int trackId);
Task DeleteAllTracksForAlbumAsync(int albumId);
}
}
4 changes: 2 additions & 2 deletions src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>MusicCatalogue.Entities</PackageId>
<PackageVersion>1.19.0.0</PackageVersion>
<PackageVersion>1.20.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2023</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -17,7 +17,7 @@
<PackageProjectUrl>https://github.com/davewalker5/MusicCatalogue</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.19.0.0</ReleaseVersion>
<ReleaseVersion>1.20.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/MusicCatalogue.Logic/Database/AlbumManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public async Task DeleteAsync(int albumId)
if (album != null)
{
// Delete the associated tracks
await Factory.Tracks.DeleteAsync(albumId);
await Factory.Tracks.DeleteAllTracksForAlbumAsync(albumId);

// Delete the album record and save changes
Factory.Context.Remove(album);
Expand Down
50 changes: 43 additions & 7 deletions src/MusicCatalogue.Logic/Database/TrackManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@ internal TrackManager(IMusicCatalogueFactory factory) : base(factory)
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public async Task<Track> GetAsync(Expression<Func<Track, bool>> predicate)
public async Task<Track?> GetAsync(Expression<Func<Track, bool>> predicate)
{
List<Track> tracks = await ListAsync(predicate);

#pragma warning disable CS8603
var tracks = await ListAsync(predicate);
return tracks.FirstOrDefault();
#pragma warning restore CS8603
}

/// <summary>
Expand Down Expand Up @@ -65,14 +62,53 @@ public async Task<Track> AddAsync(int albumId, string title, int? number, int? d
return track;
}

/// <summary>
/// Update an existing track
/// </summary>
/// <param name="trackId"></param>
/// <param name="albumId"></param>
/// <param name="title"></param>
/// <param name="number"></param>
/// <param name="duration"></param>
/// <returns></returns>
public async Task<Track?> UpdateAsync(int trackId, int albumId, string title, int? number, int? duration)
{
var track = Context.Tracks.FirstOrDefault(x => x.Id == trackId);
if (track != null)
{
track.AlbumId = albumId;
track.Title = title;
track.Number = number;
track.Duration = duration;
await Context.SaveChangesAsync();
}

return track;
}

/// <summary>
/// Delete the track with the specified Id
/// </summary>
/// <param name="trackId"></param>
/// <returns></returns>
public async Task DeleteAsync(int trackId)
{
var track = Context.Tracks.FirstOrDefault(x => x.Id == trackId);
if (track != null)
{
Context.Tracks.Remove(track);
await Context.SaveChangesAsync();
}
}

/// <summary>
/// Delete the tracks associated with an album, given its ID
/// </summary>
/// <param name="albumId"></param>
/// <returns></returns>
public async Task DeleteAsync(int albumId)
public async Task DeleteAllTracksForAlbumAsync(int albumId)
{
List<Track> tracks = await ListAsync(x => x.AlbumId == albumId);
var tracks = await ListAsync(x => x.AlbumId == albumId);
if (tracks.Any())
{
Context.Tracks.RemoveRange(tracks);
Expand Down
4 changes: 2 additions & 2 deletions src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>MusicCatalogue.Logic</PackageId>
<PackageVersion>1.19.0.0</PackageVersion>
<PackageVersion>1.20.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2023</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -17,7 +17,7 @@
<PackageProjectUrl>https://github.com/davewalker5/MusicCatalogue</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.19.0.0</ReleaseVersion>
<ReleaseVersion>1.20.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ReleaseVersion>1.19.0.0</ReleaseVersion>
<FileVersion>1.19.0.0</FileVersion>
<ProductVersion>1.19.0</ProductVersion>
<ReleaseVersion>1.20.0.0</ReleaseVersion>
<FileVersion>1.20.0.0</FileVersion>
<ProductVersion>1.20.0</ProductVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
38 changes: 32 additions & 6 deletions src/MusicCatalogue.Tests/TrackManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ public class TrackManagerTest
private const int TrackNumber = 1;
private const int TrackDuration = 643200;

private const string UpdatedTrackTitle = "Moment's Notice";
private const int UpdatedTrackNumber = 2;
private const int UpdatedTrackDuration = 550760;

private ITrackManager? _manager = null;
private int _artistId;
private int _albumId;
private int _trackId;

[TestInitialize]
public void TestInitialize()
Expand All @@ -33,7 +38,7 @@ public void TestInitialize()

// Create a track manager and add a test track
_manager = factory.Tracks;
Task.Run(() => _manager.AddAsync(_albumId, TrackTitle, TrackNumber, TrackDuration)).Wait();
_trackId = Task.Run(() => _manager.AddAsync(_albumId, TrackTitle, TrackNumber, TrackDuration)).Result.Id;
}

[TestMethod]
Expand All @@ -47,7 +52,7 @@ public async Task AddDuplicateTest()
[TestMethod]
public async Task AddAndGetTest()
{
var track = await _manager!.GetAsync(a => a.Title == TrackTitle);
var track = await _manager!.GetAsync(t => t.Title == TrackTitle);
Assert.IsNotNull(track);
Assert.IsTrue(track.Id > 0);
Assert.AreEqual(_albumId, track.AlbumId);
Expand All @@ -59,7 +64,7 @@ public async Task AddAndGetTest()
[TestMethod]
public async Task GetMissingTest()
{
var track = await _manager!.GetAsync(a => a.Title == "Missing");
var track = await _manager!.GetAsync(t => t.Title == "Missing");
Assert.IsNull(track);
}

Expand All @@ -74,15 +79,36 @@ public async Task ListAllTest()
[TestMethod]
public async Task ListMissingTest()
{
var tracks = await _manager!.ListAsync(e => e.Title == "Missing");
var tracks = await _manager!.ListAsync(t => t.Title == "Missing");
Assert.AreEqual(0, tracks!.Count);
}

[TestMethod]
public async Task UpdateTest()
{
await _manager!.UpdateAsync(_trackId, _albumId, UpdatedTrackTitle, UpdatedTrackNumber, UpdatedTrackDuration);
var track = await _manager!.GetAsync(t => t.Id == _trackId);
Assert.IsNotNull(track);
Assert.AreEqual(_trackId, track.Id);
Assert.AreEqual(_albumId, track.AlbumId);
Assert.AreEqual(UpdatedTrackTitle, track.Title);
Assert.AreEqual(UpdatedTrackNumber, track.Number);
Assert.AreEqual(UpdatedTrackDuration, track.Duration);
}

[TestMethod]
public async Task DeleteTest()
{
await _manager!.DeleteAsync(_albumId);
var tracks = await _manager!.ListAsync(e => e.AlbumId == _albumId);
await _manager!.DeleteAsync(_trackId);
var tracks = await _manager!.ListAsync(t => true);
Assert.AreEqual(0, tracks!.Count);
}

[TestMethod]
public async Task DeleteAllForAlbumTest()
{
await _manager!.DeleteAllTracksForAlbumAsync(_albumId);
var tracks = await _manager!.ListAsync(t => t.AlbumId == _albumId);
Assert.AreEqual(0, tracks!.Count);

}
Expand Down
5 changes: 4 additions & 1 deletion src/music-catalogue-ui/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ const defaultContext = {
// Current page
page: pages.artists,

// Artist, album and retailer context
// Artist, album, track and retailer context
artist: null,
album: null,
track: null,
retailer: null,

// Data retrieval/filering criteria
Expand All @@ -38,6 +39,7 @@ const App = () => {
page = pages.artists,
artist = null,
album = null,
track = null,
retailer = null,
genre = null,
filter = "A",
Expand All @@ -48,6 +50,7 @@ const App = () => {
page: page,
artist: typeof artist != "undefined" ? artist : null,
album: typeof album != "undefined" ? album : null,
track: typeof track != "undefined" ? track : null,
retailer: typeof retailer != "undefined" ? retailer : null,
genre: typeof genre != "undefined" ? genre : null,
filter: typeof filter != "undefined" ? filter : "A",
Expand Down
Loading

0 comments on commit 6b3956c

Please sign in to comment.