Skip to content

Commit

Permalink
Merge pull request #50 from davewalker5/MC-293-Equiment-Export
Browse files Browse the repository at this point in the history
MC-293 Implement Equipment Export
  • Loading branch information
davewalker5 committed Dec 9, 2023
2 parents 451e527 + e0758c9 commit 1911877
Show file tree
Hide file tree
Showing 41 changed files with 843 additions and 124 deletions.
Binary file modified diagrams/catalogue-export-requested.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/catalogue-export.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 added diagrams/music-catalogue-folder.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.24.0.0 /opt/musiccatalogue.api-1.24.0.0
WORKDIR /opt/musiccatalogue.api-1.24.0.0/bin
COPY musiccatalogue.api-1.25.0.0 /opt/musiccatalogue.api-1.25.0.0
WORKDIR /opt/musiccatalogue.api-1.25.0.0/bin
ENTRYPOINT [ "./MusicCatalogue.Api" ]
15 changes: 15 additions & 0 deletions src/MusicCatalogue.Api/Controllers/ExportController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ namespace MusicCatalogue.Api.Controllers
public class ExportController : Controller
{
private readonly IBackgroundQueue<CatalogueExportWorkItem> _catalogueQueue;
private readonly IBackgroundQueue<EquipmentExportWorkItem> _equipmentQueue;
private readonly IBackgroundQueue<ArtistStatisticsExportWorkItem> _artistStatisticsQueue;
private readonly IBackgroundQueue<GenreStatisticsExportWorkItem> _genreStatisticsQueue;
private readonly IBackgroundQueue<MonthlySpendExportWorkItem> _monthlySpendQueue;
private readonly IBackgroundQueue<RetailerStatisticsExportWorkItem> _retailerStatisticsQueue;

public ExportController(
IBackgroundQueue<CatalogueExportWorkItem> catalogueQueue,
IBackgroundQueue<EquipmentExportWorkItem> equipmentQueue,
IBackgroundQueue<ArtistStatisticsExportWorkItem> artistStatisticsQueue,
IBackgroundQueue<GenreStatisticsExportWorkItem> genreStatisticsQueue,
IBackgroundQueue<MonthlySpendExportWorkItem> monthlySpendQueue,
IBackgroundQueue<RetailerStatisticsExportWorkItem> retailerStatisticsQueue)
{
_catalogueQueue = catalogueQueue;
_equipmentQueue = equipmentQueue;
_artistStatisticsQueue = artistStatisticsQueue;
_genreStatisticsQueue = genreStatisticsQueue;
_monthlySpendQueue = monthlySpendQueue;
Expand All @@ -43,6 +46,18 @@ public IActionResult ExportCatalogue([FromBody] CatalogueExportWorkItem item)
return Accepted();
}

[HttpPost]
[Route("equipment")]
public IActionResult ExportEquipment([FromBody] EquipmentExportWorkItem item)
{
// Set the job name used in the job status record
item.JobName = "Equipment Export";

// Queue the work item
_equipmentQueue.Enqueue(item);
return Accepted();
}

[HttpPost]
[Route("artiststatistics")]
public IActionResult ExportArtistStatisticsReport([FromBody] ArtistStatisticsExportWorkItem item)
Expand Down
15 changes: 15 additions & 0 deletions src/MusicCatalogue.Api/Entities/EquipmentExportWorkItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Diagnostics.CodeAnalysis;

namespace MusicCatalogue.Api.Entities
{
[ExcludeFromCodeCoverage]
public class EquipmentExportWorkItem : BackgroundWorkItem
{
public string FileName { get; set; } = "";

public override string ToString()
{
return $"{base.ToString()}, FileName = {FileName}";
}
}
}
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.24.0.0</ReleaseVersion>
<FileVersion>1.24.0.0</FileVersion>
<ProductVersion>1.24.0</ProductVersion>
<ReleaseVersion>1.25.0.0</ReleaseVersion>
<FileVersion>1.25.0.0</FileVersion>
<ProductVersion>1.25.0</ProductVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
4 changes: 4 additions & 0 deletions src/MusicCatalogue.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ public static void Main(string[] args)
builder.Services.AddSingleton<IBackgroundQueue<CatalogueExportWorkItem>, BackgroundQueue<CatalogueExportWorkItem>>();
builder.Services.AddHostedService<CatalogueExportService>();

// Add the equipment exporter hosted service
builder.Services.AddSingleton<IBackgroundQueue<EquipmentExportWorkItem>, BackgroundQueue<EquipmentExportWorkItem>>();
builder.Services.AddHostedService<EquipmentExportService>();

// Add the artist statistics exporter hosted service
builder.Services.AddSingleton<IBackgroundQueue<ArtistStatisticsExportWorkItem>, BackgroundQueue<ArtistStatisticsExportWorkItem>>();
builder.Services.AddHostedService<ArtistStatisticsExportService>();
Expand Down
2 changes: 1 addition & 1 deletion src/MusicCatalogue.Api/Services/CatalogueExportService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ protected override async Task ProcessWorkItem(CatalogueExportWorkItem item, IMus

// Use the file extension to determine which exporter to use
var extension = Path.GetExtension(item.FileName).ToLower();
IExporter? exporter = extension == ".xlsx" ? factory.CatalogueXlsxExporter : factory.CatalogueCsvExporter;
ITrackExporter? exporter = extension == ".xlsx" ? factory.CatalogueXlsxExporter : factory.CatalogueCsvExporter;

// Construct the full path to the export file
var filePath = Path.Combine(_settings.CatalogueExportPath, item.FileName);
Expand Down
46 changes: 46 additions & 0 deletions src/MusicCatalogue.Api/Services/EquipmentExportService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.Extensions.Options;
using MusicCatalogue.Api.Entities;
using MusicCatalogue.Api.Interfaces;
using MusicCatalogue.Entities.Config;
using MusicCatalogue.Entities.Interfaces;
using System.Diagnostics.CodeAnalysis;

namespace MusicCatalogue.Api.Services
{
[ExcludeFromCodeCoverage]
public class EquipmentExportService : BackgroundQueueProcessor<EquipmentExportWorkItem>
{
private readonly MusicApplicationSettings _settings;
public EquipmentExportService(
ILogger<BackgroundQueueProcessor<EquipmentExportWorkItem>> logger,
IBackgroundQueue<EquipmentExportWorkItem> queue,
IServiceScopeFactory serviceScopeFactory,
IOptions<MusicApplicationSettings> settings)
: base(logger, queue, serviceScopeFactory)
{
_settings = settings.Value;
}

/// <summary>
/// Export the equipment register
/// </summary>
/// <param name="item"></param>
/// <param name="factory"></param>
/// <returns></returns>
protected override async Task ProcessWorkItem(EquipmentExportWorkItem item, IMusicCatalogueFactory factory)
{
MessageLogger.LogInformation("Retrieving equipment records for export");

// Use the file extension to determine which exporter to use
var extension = Path.GetExtension(item.FileName).ToLower();
IEquipmentExporter? exporter = extension == ".xlsx" ? factory.EquipmentXlsxExporter : factory.EquipmentCsvExporter;

// Construct the full path to the export file
var filePath = Path.Combine(_settings.CatalogueExportPath, item.FileName);

// Export the equipment register
await exporter.Export(filePath);
MessageLogger.LogInformation("Equipment register export completed");
}
}
}
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.22.0.0</PackageVersion>
<PackageVersion>1.23.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.22.0.0</ReleaseVersion>
<ReleaseVersion>1.23.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Diagnostics.CodeAnalysis;

namespace MusicCatalogue.Entities.DataExchange
{

[ExcludeFromCodeCoverage]
public class EquipmentDataExchangeEventArgs : EventArgs
{
public long RecordCount { get; set; }
public FlattenedEquipment? Equipment { get; set; }
}
}
134 changes: 134 additions & 0 deletions src/MusicCatalogue.Entities/DataExchange/FlattenedEquipment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using MusicCatalogue.Entities.Exceptions;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace MusicCatalogue.Entities.DataExchange
{
[ExcludeFromCodeCoverage]
public class FlattenedEquipment
{
protected const string DateTimeFormat = "dd/MM/yyyy";

public const int DescriptionField = 0;
public const int ModelField = 1;
private const int SerialNumberField = 2;
private const int EquipmentTypeField = 3;
private const int ManufacturerField = 4;
private const int WishlistItemField = 5;
private const int PurchasedField = 6;
private const int PriceField = 7;
private const int RetailerField = 8;
private const int NumberOfFields = 9;

public string Description { get; set; } = "";
public string? Model { get; set; }
public string? SerialNumber { get; set; }
public string EquipmentTypeName { get; set; } = "";
public string ManufacturerName { get; set; } = "";
public bool? IsWishListItem { get; set; }
public DateTime? Purchased { get; set; }
public decimal? Price { get; set; }
public string? RetailerName { get; set; }

/// <summary>
/// Purchase date formatted per the DateTimeFormat
/// </summary>
public string FormattedPurchaseDate
{
get
{
return Purchased != null ? (Purchased ?? DateTime.Now).ToString(DateTimeFormat) : "";
}
}


/// <summary>
/// Create a representation of the flattened equipment record in CSV format
/// </summary>
/// <returns></returns>
public string ToCsv()
{
var wishListString = (IsWishListItem ?? false).ToString();
var purchasedDateString = Purchased != null ? (Purchased ?? DateTime.Now).ToString(DateTimeFormat) : "";
var priceString = Price != null ? Price.ToString() : "";

StringBuilder builder = new StringBuilder();
AppendField(builder, Description);
AppendField(builder, Model ?? "");
AppendField(builder, SerialNumber ?? "");
AppendField(builder, EquipmentTypeName);
AppendField(builder, ManufacturerName);
AppendField(builder, wishListString);
AppendField(builder, purchasedDateString);
AppendField(builder, priceString);
AppendField(builder, RetailerName ?? "");

return builder.ToString();
}

/// <summary>
/// Create a flattened equipment record from a CSV string
/// </summary>
/// <param name="fields"></param>
/// <returns></returns>
public static FlattenedEquipment FromCsv(IList<string> fields)
{
// Check we have the required number of fields
if ((fields == null) || (fields.Count != NumberOfFields))
{
throw new InvalidRecordFormatException("Incorrect number of CSV fields");
}

// Get the model and serial number, both of which may be NULL
string? model = !string.IsNullOrEmpty(fields[ModelField]) ? fields[ModelField] : null;

Check warning on line 83 in src/MusicCatalogue.Entities/DataExchange/FlattenedEquipment.cs

View workflow job for this annotation

GitHub Actions / build

Remove the unused local variable 'model'.
string? serialNumber = !string.IsNullOrEmpty(fields[SerialNumberField]) ? fields[SerialNumberField] : null;

// Determine the purchase date
DateTime? purchasedDate = null;
if (!string.IsNullOrEmpty(fields[PurchasedField]))
{
purchasedDate = DateTime.ParseExact(fields[PurchasedField], DateTimeFormat, null);

Check warning on line 90 in src/MusicCatalogue.Entities/DataExchange/FlattenedEquipment.cs

View workflow job for this annotation

GitHub Actions / build

Use a format provider when parsing date and time.
}

// Determine the price
decimal? price = !string.IsNullOrEmpty(fields[PriceField]) ? decimal.Parse(fields[PriceField]) : null;

// Create a new "flattened" record containing equipment details
return new FlattenedEquipment
{
Description = fields[DescriptionField],
Model = fields[ModelField],
SerialNumber = fields[SerialNumberField],
EquipmentTypeName = fields[EquipmentTypeField],
ManufacturerName = fields[ManufacturerField],
IsWishListItem = bool.Parse(fields[WishlistItemField]),
Purchased = purchasedDate,
Price = price,
RetailerName = fields[RetailerField]
};
}

/// <summary>
/// Append a value to a string builder holding a representation of a flattened equpiment record in CSV format
/// </summary>
/// <param name="builder"></param>
/// <param name="value"></param>
private static void AppendField(StringBuilder builder, object? value)
{
// Add a separator if there are already fields in the line under construction
if (builder.Length > 0)
{
builder.Append(',');
}

// Convert the value to string and see if it contains the delimiter
var stringValue = (value?.ToString() ?? "").Replace('"', '\'');
var containsDelimiter = !string.IsNullOrEmpty(stringValue) && stringValue.Contains(',');

// Add the value to the builder, quoting it if needed
if (containsDelimiter) builder.Append('"');
builder.Append(stringValue);
if (containsDelimiter) builder.Append('"');
}
}
}
31 changes: 31 additions & 0 deletions src/MusicCatalogue.Entities/Database/EquipmentBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics.CodeAnalysis;

namespace MusicCatalogue.Entities.Database
{
[ExcludeFromCodeCoverage]
public abstract class EquipmentBase
{
[Key]
public int Id { get; set; }

[ForeignKey("EquipmentType.Id")]
public int EquipmentTypeId { get; set; }

[ForeignKey("Manufacturer.Id")]
public int ManufacturerId { get; set; }

[Required]
public string Description { get; set; } = "";

public string? Model { get; set; }

public string? SerialNumber { get; set; }
}
}
6 changes: 6 additions & 0 deletions src/MusicCatalogue.Entities/Database/TrackBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public abstract class TrackBase

public int? Duration { get; set; }

/// <summary>
/// Duration formatted as MM:SS
/// </summary>
public string? FormattedDuration
{
get
Expand All @@ -30,6 +33,9 @@ public abstract class TrackBase

public DateTime? Purchased { get; set; }

/// <summary>
/// Purchase date formatted per the DateTimeFormat
/// </summary>
public string FormattedPurchaseDate
{
get
Expand Down
11 changes: 11 additions & 0 deletions src/MusicCatalogue.Entities/Interfaces/IEquipmentExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using MusicCatalogue.Entities.DataExchange;

namespace MusicCatalogue.Entities.Interfaces
{
public interface IEquipmentExporter
{
event EventHandler<EquipmentDataExchangeEventArgs>? EquipmentExport;

Task Export(string file);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ public interface IMusicCatalogueFactory
IRetailerManager Retailers { get; }
IUserManager Users { get; }
IImporter Importer { get; }
IExporter CatalogueCsvExporter { get; }
IExporter CatalogueXlsxExporter { get; }
ITrackExporter CatalogueCsvExporter { get; }
ITrackExporter CatalogueXlsxExporter { get; }
IEquipmentExporter EquipmentCsvExporter { get; }
IEquipmentExporter EquipmentXlsxExporter { get; }
IJobStatusManager JobStatuses { get; }
ISearchManager Search { get; }
IWishListBasedReport<GenreStatistics> GenreStatistics { get; }
Expand Down
Loading

0 comments on commit 1911877

Please sign in to comment.