From 6fe5a5e5a3aeb737c5e0aa1b5de5a92ff107b249bee32ff1818c425b5cb52db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= <93217193+Daniel-Garmig@users.noreply.github.com> Date: Fri, 4 Sep 2026 22:33:01 +0200 Subject: [PATCH] feat StayExport - Refactor Export Services. - Created new service: IStayExportService" for common export operations. - Updated older services to be "IStayExportFormatProvider". This services will only implement the export format. - Renamed "IStayExportServiceFactory" to "IStayExportFormatProviderFactory" as this factory now is used to get the format providers for the given format. --- .../Pages/StayExport/StayExportList.razor | 19 ++-- .../Pages/StayExport/StayExportNew.razor | 29 +++--- src/Somnia.GuestLog/Program.cs | 3 +- .../StayExport/IStayExportFormatProvider.cs | 19 ++++ ...cs => IStayExportFormatProviderFactory.cs} | 4 +- .../Services/StayExport/IStayExportService.cs | 7 +- .../SpainParteViajerosStayExportService.cs | 17 ---- ....cs => StayExportFormatProviderFactory.cs} | 12 +-- .../StayExport/StayExportProviderJson.cs | 27 ++++++ .../StayExportProviderSpainParteViajeros.cs | 20 ++++ ...yExportService.cs => StayExportService.cs} | 91 +++++++++++-------- 11 files changed, 157 insertions(+), 91 deletions(-) create mode 100644 src/Somnia.GuestLog/Services/StayExport/IStayExportFormatProvider.cs rename src/Somnia.GuestLog/Services/StayExport/{IStayExportServiceFactory.cs => IStayExportFormatProviderFactory.cs} (61%) delete mode 100644 src/Somnia.GuestLog/Services/StayExport/SpainParteViajerosStayExportService.cs rename src/Somnia.GuestLog/Services/StayExport/{ConfigFileStaytExportServiceFactory.cs => StayExportFormatProviderFactory.cs} (56%) create mode 100644 src/Somnia.GuestLog/Services/StayExport/StayExportProviderJson.cs create mode 100644 src/Somnia.GuestLog/Services/StayExport/StayExportProviderSpainParteViajeros.cs rename src/Somnia.GuestLog/Services/StayExport/{JsonStayExportService.cs => StayExportService.cs} (74%) diff --git a/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportList.razor b/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportList.razor index f1b1621..617bace 100644 --- a/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportList.razor +++ b/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportList.razor @@ -1,12 +1,9 @@ @attribute [Route($"{GuestConsts.Page_StayExport_List}")] -@using FluentStorage; -@using FluentStorage.Storage; @using System.Text.Json; -@using Microsoft.Extensions.Options; @using Somnia.GuestLog.Models.DTOs; -@using Somnia.GuestLog.Models.Configuration; @using Somnia.GuestLog.Services; +@using Somnia.GuestLog.Services.StayExport; @inject IStayExportFileEntityService stayExportEntityService @inject NavigationManager Navigation @@ -15,7 +12,7 @@ @inject IDataListService dataListService; @inject ILocalizerService Localizer; -@inject IOptions exportConfig +@inject IStayExportService ExportService; @inject IJSRuntime JS @@ -126,7 +123,7 @@ private async Task BtnDownloadFile(StayExportFileDTO item) { - if(item.ExportResult == Enums.Models.StayExportFileResult.NoData) + if (item.ExportResult == Enums.Models.StayExportFileResult.NoData) { userMessage.ShowMessage("A Export without data can't be downloaded!", MessageKind.Warning); return; @@ -138,10 +135,14 @@ return; } - IStore store = StorageFactory.FromConnectionString(exportConfig.Value.ExportStorageConnectionString); - using Stream objectStream = new MemoryStream(); - await store.GetObject(item.ExportedPath, objectStream); + Result loadResult = await ExportService.GetExportedFileAsync(item, objectStream); + if (!loadResult.IsSuccess) + { + userMessage.ShowResultWithMessage(loadResult, "Exported File can't be downloaded"); + return; + } + objectStream.Seek(0, SeekOrigin.Begin); using var streamRef = new DotNetStreamReference(stream: objectStream); diff --git a/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportNew.razor b/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportNew.razor index d4f7640..8db0d03 100644 --- a/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportNew.razor +++ b/src/Somnia.GuestLog/Components/Pages/StayExport/StayExportNew.razor @@ -1,11 +1,7 @@ @attribute [Route($"{GuestConsts.Page_StayExport_New}")] @using FluentValidation; -@using FluentStorage; -@using FluentStorage.Storage; -@using Microsoft.Extensions.Options; @using Somnia.GuestLog.Models.DTOs; -@using Somnia.GuestLog.Models.Configuration; @using Somnia.GuestLog.Enums.Models; @using Somnia.GuestLog.Services; @using Somnia.GuestLog.Validators; @@ -17,9 +13,8 @@ @inject NavigationManager Navigation @inject IValidator ExportParamsValidator; @inject ILocalizerService Localizer; -@inject IStayExportServiceFactory ExportServiceFactory; +@inject IStayExportService ExportService; -@inject IOptions exportConfig @inject IJSRuntime JS @@ -121,9 +116,7 @@ var loadingDialog = await DialogService.ShowAsync(); - var exportService = ExportServiceFactory.GetService(exportFormat.Value); - var exportResult = await exportService.ExportStaysByParams(exportParams); - + var exportResult = await ExportService.ExportStaysByParamsAsync(exportFormat.Value, exportParams); exportedFile = exportResult.Value; loadingDialog.Close(); @@ -141,7 +134,6 @@ private async Task BtnDownloadExportedFile() { Debug.Assert(exportedFile != null); - Debug.Assert(exportConfig.Value.ExportStorageConnectionString != null); if (string.IsNullOrEmpty(exportedFile.ExportedPath)) { @@ -149,16 +141,19 @@ return; } - IStore store = StorageFactory.FromConnectionString(exportConfig.Value.ExportStorageConnectionString); + using Stream dataStream = new MemoryStream(); - using Stream objectStream = new MemoryStream(); - await store.GetObject(exportedFile.ExportedPath, objectStream); - objectStream.Seek(0, SeekOrigin.Begin); + Result loadResult = await ExportService.GetExportedFileAsync(exportedFile, dataStream); + if (!loadResult.IsSuccess) + { + userMessage.ShowResultWithMessage(loadResult, "Exported File can't be downloaded"); + return; + } + + dataStream.Seek(0, SeekOrigin.Begin); + using var streamRef = new DotNetStreamReference(stream: dataStream); var fileName = Path.GetFileName(exportedFile.ExportedPath); - - using var streamRef = new DotNetStreamReference(stream: objectStream); - await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef); userMessage.ShowMessage("Exported File downloaded!", MessageKind.Success); diff --git a/src/Somnia.GuestLog/Program.cs b/src/Somnia.GuestLog/Program.cs index 16f4aac..384639a 100644 --- a/src/Somnia.GuestLog/Program.cs +++ b/src/Somnia.GuestLog/Program.cs @@ -66,10 +66,11 @@ builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); // Add Factories (and it's services) builder.Services.AddFactoryDI(); -builder.Services.AddFactoryDI(); +builder.Services.AddFactoryDI(); // Add Options config builder.Services.AddOptions() diff --git a/src/Somnia.GuestLog/Services/StayExport/IStayExportFormatProvider.cs b/src/Somnia.GuestLog/Services/StayExport/IStayExportFormatProvider.cs new file mode 100644 index 0000000..60de696 --- /dev/null +++ b/src/Somnia.GuestLog/Services/StayExport/IStayExportFormatProvider.cs @@ -0,0 +1,19 @@ +using Somnia.GuestLog.Enums.Models; +using Somnia.GuestLog.Models.DTOs; + +namespace Somnia.GuestLog.Services.StayExport; + +public interface IStayExportFormatProvider +{ + public StayExportFormat GetFormat(); + public string GetFilename(DateTime exportTime); + + /// + /// Export Data with the corresponding format. + /// Data is copied to given stream (no other operations are performed to the stream). + /// + /// List of Stays to export + /// Stream to copy exported data. + public Task ExportDataAsync(IEnumerable stayList, Stream dataStream); + +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayExport/IStayExportServiceFactory.cs b/src/Somnia.GuestLog/Services/StayExport/IStayExportFormatProviderFactory.cs similarity index 61% rename from src/Somnia.GuestLog/Services/StayExport/IStayExportServiceFactory.cs rename to src/Somnia.GuestLog/Services/StayExport/IStayExportFormatProviderFactory.cs index b538f79..de79846 100644 --- a/src/Somnia.GuestLog/Services/StayExport/IStayExportServiceFactory.cs +++ b/src/Somnia.GuestLog/Services/StayExport/IStayExportFormatProviderFactory.cs @@ -2,10 +2,10 @@ using Somnia.GuestLog.Enums.Models; namespace Somnia.GuestLog.Services.StayExport; -public interface IStayExportServiceFactory +public interface IStayExportFormatProviderFactory { /// /// Returns the StayExport service implementation given current configuration. /// - public IStayExportService GetService(StayExportFormat exportFormat); + public IStayExportFormatProvider GetProvider(StayExportFormat exportFormat); } diff --git a/src/Somnia.GuestLog/Services/StayExport/IStayExportService.cs b/src/Somnia.GuestLog/Services/StayExport/IStayExportService.cs index e51fc65..d9fa631 100644 --- a/src/Somnia.GuestLog/Services/StayExport/IStayExportService.cs +++ b/src/Somnia.GuestLog/Services/StayExport/IStayExportService.cs @@ -5,7 +5,10 @@ namespace Somnia.GuestLog.Services.StayExport; public interface IStayExportService { - public StayExportFormat GetFormat(); + public Task> ExportStaysByParamsAsync(StayExportFormat format, StayExportParamsDTO exportParams); - public Task> ExportStaysByParams(StayExportParamsDTO exportParams); + + public Task GetExportedFileAsync(StayExportFileDTO item, Stream dataStream); + + public Task DeleteExportedFileAsync(StayExportFileDTO item); } \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayExport/SpainParteViajerosStayExportService.cs b/src/Somnia.GuestLog/Services/StayExport/SpainParteViajerosStayExportService.cs deleted file mode 100644 index 3bc58e7..0000000 --- a/src/Somnia.GuestLog/Services/StayExport/SpainParteViajerosStayExportService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Somnia.GuestLog.Enums.Models; -using Somnia.GuestLog.Models.DTOs; - -namespace Somnia.GuestLog.Services.StayExport; - -public class SpainParteViajerosStayExportService( - -) : IStayExportService -{ - public StayExportFormat GetFormat() => StayExportFormat.SpainParteViajeros; - - - public Task> ExportStaysByParams(StayExportParamsDTO exportParams) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayExport/ConfigFileStaytExportServiceFactory.cs b/src/Somnia.GuestLog/Services/StayExport/StayExportFormatProviderFactory.cs similarity index 56% rename from src/Somnia.GuestLog/Services/StayExport/ConfigFileStaytExportServiceFactory.cs rename to src/Somnia.GuestLog/Services/StayExport/StayExportFormatProviderFactory.cs index 7257f94..cecb6b7 100644 --- a/src/Somnia.GuestLog/Services/StayExport/ConfigFileStaytExportServiceFactory.cs +++ b/src/Somnia.GuestLog/Services/StayExport/StayExportFormatProviderFactory.cs @@ -5,26 +5,26 @@ using Somnia.GuestLog.Utils.DI; namespace Somnia.GuestLog.Services.StayExport; -public class ConfigFileStaytExportServiceFactory( +public class StayExportFormatProviderFactory( IServiceProvider serviceProvider, IOptions config -) : IStayExportServiceFactory, IFactoryDI +) : IStayExportFormatProviderFactory, IFactoryDI { public static void RegisterFactoryItems(IServiceCollection services) { - services.AddKeyedSingleton(StayExportFormat.GuestLogJson); - services.AddKeyedSingleton(StayExportFormat.SpainParteViajeros); + services.AddKeyedSingleton(StayExportFormat.GuestLogJson); + services.AddKeyedSingleton(StayExportFormat.SpainParteViajeros); } - public IStayExportService GetService(StayExportFormat exportFormat) + public IStayExportFormatProvider GetProvider(StayExportFormat exportFormat) { if (config.Value.EnableStayExport == false) { throw new InvalidOperationException("StayExport is disabled. Enable and configure it first!"); } - return serviceProvider.GetKeyedService(exportFormat) + return serviceProvider.GetKeyedService(exportFormat) ?? throw new InvalidOperationException($"StayExport service not found for ExportFormat: '{exportFormat}'"); } } \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayExport/StayExportProviderJson.cs b/src/Somnia.GuestLog/Services/StayExport/StayExportProviderJson.cs new file mode 100644 index 0000000..02ccf19 --- /dev/null +++ b/src/Somnia.GuestLog/Services/StayExport/StayExportProviderJson.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using Somnia.GuestLog.Enums.Models; +using Somnia.GuestLog.Models.DTOs; + +namespace Somnia.GuestLog.Services.StayExport; + +public class StayExportProviderJson : IStayExportFormatProvider +{ + private const string fileExtension = "json"; + + public StayExportFormat GetFormat() => StayExportFormat.GuestLogJson; + public string GetFilename(DateTime exportTime) + => $"{Enum.GetName(GetFormat())}_{exportTime.ToFileTimeUtc()}.{fileExtension}"; + + + public async Task ExportDataAsync(IEnumerable stayList, Stream dataStream) + { + if (!stayList.Any()) + { + return StayExportFileResult.NoData; + } + + // Convert to JSON. + await JsonSerializer.SerializeAsync(dataStream, stayList); + return StayExportFileResult.OK; + } +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayExport/StayExportProviderSpainParteViajeros.cs b/src/Somnia.GuestLog/Services/StayExport/StayExportProviderSpainParteViajeros.cs new file mode 100644 index 0000000..15c47ee --- /dev/null +++ b/src/Somnia.GuestLog/Services/StayExport/StayExportProviderSpainParteViajeros.cs @@ -0,0 +1,20 @@ +using Somnia.GuestLog.Enums.Models; +using Somnia.GuestLog.Models.DTOs; + +namespace Somnia.GuestLog.Services.StayExport; + +public class StayExportProviderSpainParteViajeros( + +) : IStayExportFormatProvider +{ + private const string fileExtension = "xml"; + + public StayExportFormat GetFormat() => StayExportFormat.SpainParteViajeros; + public string GetFilename(DateTime exportTime) + => $"{Enum.GetName(GetFormat())}_{exportTime.ToFileTimeUtc()}.{fileExtension}"; + + public Task ExportDataAsync(IEnumerable stayList, Stream dataStream) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Somnia.GuestLog/Services/StayExport/JsonStayExportService.cs b/src/Somnia.GuestLog/Services/StayExport/StayExportService.cs similarity index 74% rename from src/Somnia.GuestLog/Services/StayExport/JsonStayExportService.cs rename to src/Somnia.GuestLog/Services/StayExport/StayExportService.cs index a3f91de..f3247a6 100644 --- a/src/Somnia.GuestLog/Services/StayExport/JsonStayExportService.cs +++ b/src/Somnia.GuestLog/Services/StayExport/StayExportService.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Diagnostics; using FluentStorage; using FluentStorage.Storage; using Microsoft.Extensions.Options; @@ -11,23 +11,21 @@ using Somnia.GuestLog.Services.Entity; namespace Somnia.GuestLog.Services.StayExport; -public class JsonStayExportService( - ILogger logger, +public class StayExportService( + ILogger logger, + IOptions exportConfig, IStayExportFileEntityService stayExportEntityService, IStayEntryEntityService stayEntityService, - IOptions exportConfig + IStayExportFormatProviderFactory providerFactory ) : IStayExportService { - private const string fileExtension = "json"; private static readonly StayEntryQueryConfig queryConfig = new(IncludeGuests: true, UseSplitQueries: true, DisableTracking: true); private readonly StayExportConfig config = exportConfig.Value; - public StayExportFormat GetFormat() => StayExportFormat.GuestLogJson; - - public async Task> ExportStaysByParams(StayExportParamsDTO exportParams) + public async Task> ExportStaysByParamsAsync(StayExportFormat format, StayExportParamsDTO exportParams) { //Get data to export. IEnumerable? stayList; @@ -45,7 +43,7 @@ public class JsonStayExportService( DateTime exportTime = DateTime.UtcNow; StayExportFileDTO stayExport = new() { - ExportFormat = GetFormat(), + ExportFormat = format, ExportParams = exportParams, ExportTime = exportTime, }; @@ -54,13 +52,18 @@ public class JsonStayExportService( using Stream exportedDataStream = new MemoryStream(); //Generate data to export. - StayExportFileResult? exportResult; + StayExportFileResult exportResult; + string? exportFilename = null; try { - exportResult = await ExportDataToStream(stayList, exportedDataStream); + //Create Format provider and export data to stream. + var provider = providerFactory.GetProvider(format); + exportResult = await provider.ExportDataAsync(stayList, exportedDataStream); // "Rewind" stream so it can be read from the begining when uploading. exportedDataStream.Seek(0, SeekOrigin.Begin); + + exportFilename = provider.GetFilename(exportTime); } catch (Exception ex) { @@ -73,7 +76,14 @@ public class JsonStayExportService( { try { - string exportPath = GetExportPath(exportTime); + Debug.Assert(exportFilename != null); + + string exportPathSubdir = ""; + if (config.UseSubdirectoryByFormat) + { + exportPathSubdir = Enum.GetName(format) ?? ""; + } + string exportPath = Path.Combine(exportPathSubdir, exportFilename); //Store data. using IStore exportStore = GetExportStore(); @@ -128,6 +138,38 @@ public class JsonStayExportService( } + public async Task GetExportedFileAsync(StayExportFileDTO item, Stream dataStream) + { + if (string.IsNullOrEmpty(item.ExportedPath)) + { + logger.LogWarning("Trying to get exported file without path!! Exported file can't be loaded!"); + return Result.Fail(ResultCode.NotFound); + } + + try + { + IStore store = GetExportStore(); + await store.GetObject(item.ExportedPath, dataStream); + } + catch (Exception ex) + { + logger.LogError(ex, "Error getting exported file '{id}' from path '{path}'", item.Id, item.ExportedPath); + return Result.FailUnknownError(); + } + + return Result.Ok(); + } + + public Task DeleteExportedFileAsync(StayExportFileDTO item) + { + throw new NotImplementedException(); + + //TODO: + // - Delete file from "Store" if exists. + // - Delete entry from DB. + } + + #region Internal Methods private async Task> GetStaysToExportByParamsAsync(StayExportParamsDTO exportParams) @@ -152,31 +194,6 @@ public class JsonStayExportService( throw new InvalidOperationException($"StayExportFile - Error getting stays using params: export type invalid: {exportParams.ExportType}"); } - private async Task ExportDataToStream(IEnumerable stayList, Stream exportStream) - { - if (!stayList.Any()) - { - return StayExportFileResult.NoData; - } - - // Convert to JSON. - await JsonSerializer.SerializeAsync(exportStream, stayList); - return StayExportFileResult.OK; - } - - private string GetExportPath(DateTime time) - { - string formatName = GetFormatName(); - string exportPathSubdir = config.UseSubdirectoryByFormat ? formatName : ""; - string exportFileName = $"{formatName}_{time.ToFileTimeUtc()}.{fileExtension}"; - return Path.Combine(exportPathSubdir, exportFileName); - } - - private string GetFormatName() - { - return Enum.GetName(GetFormat()) ?? throw new InvalidOperationException("Can't get StayExportFormat name!"); - } - private IStore GetExportStore() { string? storageConnString = config.ExportStorageConnectionString;