Skip to content

Commit

Permalink
Merge pull request #58 from davewalker5/album-picker
Browse files Browse the repository at this point in the history
Implemented album picker
  • Loading branch information
davewalker5 authored Aug 23, 2024
2 parents cbfdc49 + 10a67bd commit 353453d
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 8 deletions.
2 changes: 1 addition & 1 deletion 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.32.0.0 /opt/musiccatalogue.api
COPY musiccatalogue.api-1.33.0.0 /opt/musiccatalogue.api
WORKDIR /opt/musiccatalogue.api/bin
ENTRYPOINT [ "./MusicCatalogue.Api" ]
2 changes: 1 addition & 1 deletion docker/ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM node:20-alpine
COPY musiccatalogue.ui-1.32.0.0 /opt/musiccatalogue.ui
COPY musiccatalogue.ui-1.33.0.0 /opt/musiccatalogue.ui
WORKDIR /opt/musiccatalogue.ui
RUN npm install
RUN npm run build
Expand Down
16 changes: 16 additions & 0 deletions src/MusicCatalogue.Api/Controllers/AlbumsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ public async Task<ActionResult<Album>> GetAlbumByIdAsync(int id)
return album;
}

[HttpGet]
[Route("random")]
public async Task<ActionResult<Album?>> GetRandomAlbum()
{
var album = await _factory.Albums.GetRandomAsync(x => !(x.IsWishListItem ?? false));
return album;
}

[HttpGet]
[Route("random/{genreId}")]
public async Task<ActionResult<Album?>> GetRandomAlbum(int genreId)
{
var album = await _factory.Albums.GetRandomAsync(x => !(x.IsWishListItem ?? false) && (x.GenreId == genreId));
return album;
}

/// <summary>
/// Return a list of albums for the specified artist, filtering for items that are on/not on
/// the wishlist based on the arguments
Expand Down
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>net8.0</TargetFramework>
<ReleaseVersion>1.32.0.0</ReleaseVersion>
<FileVersion>1.32.0.0</FileVersion>
<ProductVersion>1.32.0</ProductVersion>
<ReleaseVersion>1.33.0.0</ReleaseVersion>
<FileVersion>1.33.0.0</FileVersion>
<ProductVersion>1.33.0</ProductVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions src/MusicCatalogue.Entities/Interfaces/IAlbumManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Task<Album> AddAsync(
int? retailerId);

Task<Album> GetAsync(Expression<Func<Album, bool>> predicate);
Task<Album?> GetRandomAsync(Expression<Func<Album, bool>> predicate);
Task<List<Album>> ListAsync(Expression<Func<Album, bool>> predicate);
Task DeleteAsync(int albumId);
}
Expand Down
23 changes: 23 additions & 0 deletions src/MusicCatalogue.Logic/Database/AlbumManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,29 @@ public async Task<List<Album>> ListAsync(Expression<Func<Album, bool>> predicate
.Include(x => x.Tracks)
.ToListAsync();

/// <summary>
/// Return a randomly selected album from those matching the predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public async Task<Album?> GetRandomAsync(Expression<Func<Album, bool>> predicate)
{
Album? album = null;

// Get a list of albums matching the predicate
var albums = await ListAsync(predicate);

// If there are any, pick one at random
var numberOfAlbums = albums.Count;
if (numberOfAlbums > 0)
{
var index = new Random().Next(0, numberOfAlbums);
album = albums[index];
}

return album;
}

/// <summary>
/// Add an album, if it doesn't already exist
/// </summary>
Expand Down
104 changes: 104 additions & 0 deletions src/music-catalogue-ui/components/albums/albumPicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useCallback, useState } from "react";
import styles from "./albumPicker.module.css";
import { apiFetchRandomAlbum } from "@/helpers/api/apiAlbums";
import AlbumPickerAlbumRow from "./albumPickerAlbumRow";
import GenreSelector from "../genres/genreSelector";
import { apiFetchArtistById } from "@/helpers/api/apiArtists";

/**
* Component to pick a random album, optionally for a specified genre
* @param {*} logout
* @returns
*/
const AlbumPicker = ({ logout }) => {
const [genre, setGenre] = useState(null);
const [details, setDetails] = useState({ album: null, artist: null });

// Callback to request a random album from the API
const pickAlbumCallback = useCallback(
async (e) => {
// Prevent the default action associated with the click event
e.preventDefault();

// Request a random album, optionally filtering by the selected genre, and
// retrieve the artist details
const genreId = genre != null ? genre.id : null;
const fetchedAlbum = await apiFetchRandomAlbum(genreId, logout);
if (fetchedAlbum != null) {
const fetchedArtist = await apiFetchArtistById(
fetchedAlbum.artistId,
logout
);
setDetails({ album: fetchedAlbum, artist: fetchedArtist });
} else {
setDetails({ album: null, artist: null });
}
},
[genre, logout]
);

return (
<>
<div className="row mb-2 pageTitle">
<h5 className="themeFontColor text-center">Album Picker</h5>
</div>
<div className={styles.albumPickerFormContainer}>
<form className={styles.albumPickerForm}>
<div className="row" align="center">
<div className="mt-3">
<div className="d-inline-flex align-items-center">
<div className="col">
<label className={styles.albumPickerLabel}>
Genre to pick from:
</label>
</div>
<div className="col">
<div className={styles.alunmPickerGenreSelector}>
<GenreSelector
initialGenre={genre}
genreChangedCallback={setGenre}
/>
</div>
</div>
<div className="col">
<button
className="btn btn-primary"
onClick={(e) => pickAlbumCallback(e)}
>
Pick
</button>
</div>
</div>
</div>
</div>
</form>
</div>
<table className="table table-hover">
<thead>
<tr>
<th>Artist</th>
<th>Title</th>
<th>Genre</th>
<th>Released</th>
<th>Purchased</th>
<th>Price</th>
<th>Retailer</th>
</tr>
</thead>
{details.album != null && (
<tbody>
{
<AlbumPickerAlbumRow
key={details.album.id}
album={details.album}
artist={details.artist}
/>
}
</tbody>
)}
</table>
</>
);
};

export default AlbumPicker;
22 changes: 22 additions & 0 deletions src/music-catalogue-ui/components/albums/albumPicker.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.albumPickerFormContainer {
display: flex;
justify-content: center;
align-items: center;
}

.albumPickerForm {
width: 100vw;
padding-top: 20px;
padding-bottom: 20px;
}

.albumPickerLabel {
margin-right: 20px;
font-size: 14px;
font-weight: 600;
color: rgb(34, 34, 34);
}

.alunmPickerGenreSelector {
width: 200px;
}
39 changes: 39 additions & 0 deletions src/music-catalogue-ui/components/albums/albumPickerAlbumRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import CurrencyFormatter from "../common/currencyFormatter";
import DateFormatter from "../common/dateFormatter";

/**
* Component to render a row containing the details of a single album in a
* genre
* @param {*} album
* @param {*} artist
* @returns
*/
const AlbumPickerAlbumRow = ({ album, artist }) => {
const purchaseDate = new Date(album.purchased);

return (
<tr>
<td>{artist.name}</td>
<td>{album.title}</td>
<td>{album.genre.name}</td>
{album.released > 0 ? <td>{album.released}</td> : <td />}
{purchaseDate > 1900 ? (
<td>
<DateFormatter value={album.purchased} />
</td>
) : (
<td />
)}
{album.price > 0 ? (
<td>
<CurrencyFormatter value={album.price} />
</td>
) : (
<td />
)}
{album.retailer != null ? <td>{album.retailer.name}</td> : <td />}
</tr>
);
};

export default AlbumPickerAlbumRow;
3 changes: 3 additions & 0 deletions src/music-catalogue-ui/components/componentPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import ManufacturerEditor from "./manufacturers/manufacturerEditor";
import EquipmentList from "./equipment/equipmentList";
import EquipmentPurchaseDetails from "./equipment/equpimentPurchaseDetails";
import EquipmentEditor from "./equipment/equipmentEditor";
import AlbumPicker from "./albums/albumPicker";

/**
* Component using the current context to select and render the current page
Expand Down Expand Up @@ -84,6 +85,8 @@ const ComponentPicker = ({ context, navigate, logout }) => {
logout={logout}
/>
);
case pages.albumPicker:
return <AlbumPicker navigate={navigate} logout={logout} />;
case pages.tracks:
return (
<TrackList
Expand Down
3 changes: 3 additions & 0 deletions src/music-catalogue-ui/components/menuBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ const MenuBar = ({ navigate, logout }) => {
</div>
</button>
<div className={styles.dropdownContent}>
<a onClick={() => navigate({ page: pages.albumPicker })}>
Album Picker
</a>
<a onClick={() => navigate({ page: pages.artists, filter: "A" })}>
Artists
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useState } from "react";
import styles from "./reports.module.css";
import "react-datepicker/dist/react-datepicker.css";
// import "react-datepicker/dist/react-datepicker.css";
import { apiGenreAlbumsReport } from "@/helpers/api/apiReports";
import GenreAlbumRow from "./genreAlbumRow";
import ReportExportControls from "./reportExportControls";
Expand Down Expand Up @@ -91,7 +91,7 @@ const GenreAlbumsReport = ({ logout }) => {
<label className={styles.reportFormLabel}>Report For:</label>
</div>
<div className="col">
<div className={styles.genreSelector}>
<div className={styles.reportGenreSelector}>
<GenreSelector
initialGenre={genre}
genreChangedCallback={setGenre}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@
text-align: center;
}

.genreSelector {
.reportGenreSelector {
width: 200px;
}
21 changes: 21 additions & 0 deletions src/music-catalogue-ui/helpers/api/apiAlbums.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,26 @@ const apiSetAlbumPurchaseDetails = async (
return response;
};

/**
* Return the album details for a randomly selected album from the
* Music Catalogue REST API
* @param {*} albumId
* @param {*} logout
* @returns
*/
const apiFetchRandomAlbum = async (genreId, logout) => {
// Call the API to get the details for a randomly selected album
const baseUrl = `${config.api.baseUrl}/albums/random`;
const url = genreId != null ? `${baseUrl}/${genreId}` : baseUrl;
const response = await fetch(url, {
method: "GET",
headers: apiGetHeaders(),
});

const album = await apiReadResponseData(response, logout);
return album;
};

export {
apiCreateAlbum,
apiUpdateAlbum,
Expand All @@ -274,4 +294,5 @@ export {
apiLookupAlbum,
apiSetAlbumWishListFlag,
apiSetAlbumPurchaseDetails,
apiFetchRandomAlbum,
};
1 change: 1 addition & 0 deletions src/music-catalogue-ui/helpers/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const pages = {
albums: "Albums",
albumEditor: "AlbumEditor",
albumPurchaseDetails: "AlbumPurchaseDetails",
albumPicker: "AlbumPicker",
wishlistAlbums: "WishlistAlbums",
tracks: "Tracks",
trackEditor: "TrackEditor",
Expand Down

0 comments on commit 353453d

Please sign in to comment.