SHA256
feat StayExport - Initial Implementation
- Created models for DB and DTOs: StayExportFile - Added required db migrations. - Added StayExportService and StayExportServiceFactory for stay exporting services. - Added configuration options required for StayExport.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
|
||||
namespace Somnia.GuestLog.Services;
|
||||
|
||||
public interface IStayExportFileService
|
||||
{
|
||||
public Task<Result> CreateStayExportAsync(StayExportFileDTO dto);
|
||||
public Task<Result> UpdateStayExportAsync(StayExportFileDTO dto);
|
||||
|
||||
public Task<StayExportFileDTO?> GetDtoByIdAsync(long id);
|
||||
//public Task<IEnumerable<StayExportFileDTO>> GetDtoListByFilterAsync(StayExportFileFilterDTO filter, int limit = 0);
|
||||
public Task<IEnumerable<StayExportFileDTO>> GetDtoListAsync();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Somnia.GuestLog.Enums.Models;
|
||||
using Somnia.GuestLog.Models.Configuration;
|
||||
using Somnia.GuestLog.Utils.DI;
|
||||
|
||||
namespace Somnia.GuestLog.Services.StayExport;
|
||||
|
||||
public class ConfigFileStaytExportServiceFactory(
|
||||
IServiceProvider serviceProvider,
|
||||
IOptions<StayExportConfig> config
|
||||
) : IStayExportServiceFactory, IFactoryDI
|
||||
{
|
||||
public static void RegisterFactoryItems(IServiceCollection services)
|
||||
{
|
||||
services.AddKeyedSingleton<IStayExportService, JsonStayExportService>(StayExportFormat.GuestLogJson);
|
||||
services.AddKeyedSingleton<IStayExportService, SpainParteViajerosStayExportService>(StayExportFormat.SpainParteViajeros);
|
||||
}
|
||||
|
||||
|
||||
public IStayExportService GetService(StayExportFormat exportFormat)
|
||||
{
|
||||
if (config.Value.EnableStayExport == false)
|
||||
{
|
||||
throw new InvalidOperationException("StayExport is disabled. Enable and configure it first!");
|
||||
}
|
||||
|
||||
return serviceProvider.GetKeyedService<IStayExportService>(exportFormat)
|
||||
?? throw new InvalidOperationException($"StayExport service not found for ExportFormat: '{exportFormat}'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Somnia.GuestLog.Enums.Models;
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
|
||||
namespace Somnia.GuestLog.Services.StayExport;
|
||||
|
||||
public interface IStayExportService
|
||||
{
|
||||
public StayExportFormat GetFormat();
|
||||
|
||||
public Task<Result<StayExportFileDTO>> ExportStaysByParams(StayExportParamsDTO exportParams);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Somnia.GuestLog.Enums.Models;
|
||||
|
||||
namespace Somnia.GuestLog.Services.StayExport;
|
||||
|
||||
public interface IStayExportServiceFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the StayExport service implementation given current configuration.
|
||||
/// </summary>
|
||||
public IStayExportService GetService(StayExportFormat exportFormat);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System.Text.Json;
|
||||
using FluentStorage;
|
||||
using FluentStorage.Storage;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Somnia.GuestLog.Enums.Models;
|
||||
using Somnia.GuestLog.Enums.Services;
|
||||
using Somnia.GuestLog.Models.Configuration;
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
using Somnia.GuestLog.Models.QueryConfigs;
|
||||
using Somnia.GuestLog.Services.Entity;
|
||||
|
||||
namespace Somnia.GuestLog.Services.StayExport;
|
||||
|
||||
public class JsonStayExportService(
|
||||
ILogger<JsonStayExportService> logger,
|
||||
IStayExportFileService stayExportEntityService,
|
||||
IStayEntryEntityService stayEntityService,
|
||||
IOptions<StayExportConfig> exportConfig
|
||||
) : 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<Result<StayExportFileDTO>> ExportStaysByParams(StayExportParamsDTO exportParams)
|
||||
{
|
||||
//Get data to export.
|
||||
IEnumerable<StayEntryDTO>? stayList;
|
||||
try
|
||||
{
|
||||
stayList = await GetStaysToExportByParamsAsync(exportParams);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error retriving stays to export!");
|
||||
return Result<StayExportFileDTO>.Fail(ResultCode.UnknownError);
|
||||
}
|
||||
|
||||
//Create initial StayExportFile
|
||||
DateTime exportTime = DateTime.UtcNow;
|
||||
StayExportFileDTO stayExport = new()
|
||||
{
|
||||
ExportFormat = GetFormat(),
|
||||
ExportParams = exportParams,
|
||||
ExportTime = exportTime,
|
||||
};
|
||||
|
||||
//Export data.
|
||||
using Stream exportedDataStream = new MemoryStream();
|
||||
|
||||
//Generate data to export.
|
||||
StayExportFileResult? exportResult;
|
||||
try
|
||||
{
|
||||
exportResult = await ExportDataToStream(stayList, exportedDataStream);
|
||||
|
||||
// "Rewind" stream so it can be read from the begining when uploading.
|
||||
exportedDataStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error exporting data!");
|
||||
exportResult = StayExportFileResult.ExportError;
|
||||
}
|
||||
|
||||
//If data exported OK, save to file.
|
||||
if (exportResult == StayExportFileResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
string exportPath = GetExportPath(exportTime);
|
||||
|
||||
//Store data.
|
||||
using IStore exportStore = GetExportStore();
|
||||
await exportStore.SetObject(objectPath: exportPath, dataStream: exportedDataStream);
|
||||
|
||||
//Update path in item.
|
||||
stayExport.ExportedPath = exportPath;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error Uploading exported data!");
|
||||
exportResult = StayExportFileResult.UploadError;
|
||||
}
|
||||
}
|
||||
|
||||
//Store StayExport in DB.
|
||||
stayExport.ExportResult = exportResult;
|
||||
Result? createResult = null;
|
||||
try
|
||||
{
|
||||
createResult = await stayExportEntityService.CreateStayExportAsync(stayExport);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error storing StayExportFile entity to DB: {stayExport}", stayExport);
|
||||
}
|
||||
|
||||
//If everything is OK, return OK.
|
||||
if (createResult != null && createResult.ResultCode == ResultCode.Ok)
|
||||
{
|
||||
return Result<StayExportFileDTO>.Ok(stayExport);
|
||||
}
|
||||
|
||||
//Saving export to DB failed. Delete exported file (if exported)
|
||||
if (!string.IsNullOrEmpty(stayExport.ExportedPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using IStore exportStore = GetExportStore();
|
||||
await exportStore.DeleteObject(objectPath: stayExport.ExportedPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "JsonStayExportService - ExportStaysByParams: Error deleting exported file after db saving error: {exportedPath}", stayExport.ExportedPath);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogError(
|
||||
"JsonStayExportService - ExportStaysByDateIn: Error (code '{code}') storing StayExportFile entity to DB: {stayExport}",
|
||||
createResult?.ResultCode, stayExport);
|
||||
return Result<StayExportFileDTO>.Fail(ResultCode.UnknownError);
|
||||
}
|
||||
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
private async Task<IEnumerable<StayEntryDTO>> GetStaysToExportByParamsAsync(StayExportParamsDTO exportParams)
|
||||
{
|
||||
//Todo - Improve type selection (when there are more types).
|
||||
if (exportParams.ExportType == StayExportParamType.PeriodDateIn)
|
||||
{
|
||||
if (exportParams.ExportPeriodStart == null || exportParams.ExportPeriodEnd == null)
|
||||
{
|
||||
throw new InvalidOperationException($"StayExportFile - ExportType {exportParams.ExportType} require date start/end. Null dates found");
|
||||
}
|
||||
|
||||
//Get Stays in period.
|
||||
StayEntryFilterDTO filter = new()
|
||||
{
|
||||
DateInStart = exportParams.ExportPeriodStart,
|
||||
DateInEnd = exportParams.ExportPeriodEnd
|
||||
};
|
||||
return await stayEntityService.GetDtoListByFilterAsync(filter, limit: 0, queryConfig: queryConfig);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"StayExportFile - Error getting stays using params: export type invalid: {exportParams.ExportType}");
|
||||
}
|
||||
|
||||
private async Task<StayExportFileResult> ExportDataToStream(IEnumerable<StayEntryDTO> 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;
|
||||
if (string.IsNullOrEmpty(storageConnString))
|
||||
{
|
||||
throw new InvalidOperationException("No Stay Export Storage configured. Stays can't be exported");
|
||||
}
|
||||
|
||||
return StorageFactory.FromConnectionString(storageConnString);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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<Result<StayExportFileDTO>> ExportStaysByParams(StayExportParamsDTO exportParams)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Somnia.GuestLog.Infrastructure;
|
||||
using Somnia.GuestLog.Models;
|
||||
using Somnia.GuestLog.Models.DTOs;
|
||||
using Somnia.GuestLog.Models.Entities;
|
||||
|
||||
namespace Somnia.GuestLog.Services;
|
||||
|
||||
public class StayExportFileService(
|
||||
ILogger<StayExportFileService> logger,
|
||||
IDbContextFactory<GuestLogContext> contextFactory
|
||||
) : IStayExportFileService
|
||||
{
|
||||
public async Task<Result> CreateStayExportAsync(StayExportFileDTO dto)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dto, nameof(dto));
|
||||
|
||||
//Check for null values in the DTO.
|
||||
if (dto.ExportFormat == null || dto.ExportResult == null || dto.ExportTime == null)
|
||||
{
|
||||
throw new InvalidOperationException("StayExportFile dto contains null fields");
|
||||
}
|
||||
|
||||
if (dto.ExportParams == null)
|
||||
{
|
||||
throw new InvalidOperationException("StayExportFile dto contains null ExportParams");
|
||||
}
|
||||
|
||||
StayExportParams exportParams = GetExportParamsFromDTO(dto.ExportParams);
|
||||
StayExportFile dbItem = new()
|
||||
{
|
||||
ExportFormat = dto.ExportFormat.Value,
|
||||
ExportParams = exportParams,
|
||||
ExportResult = dto.ExportResult.Value,
|
||||
ExportedPath = dto.ExportedPath,
|
||||
ExportTime = dto.ExportTime?.ToUniversalTime() ?? DateTime.UtcNow,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var dbContext = contextFactory.CreateDbContext();
|
||||
await dbContext.AddAsync(dbItem);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
logger.LogInformation("StayExportFileService - CreateStayExportAsync: GuestDocument Created: '{id}'", dbItem.GetFullKey());
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "StayExportFileService - CreateStayExportAsync: Error creating GuestDocument");
|
||||
return Result.FailUnknownError();
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Result> UpdateStayExportAsync(StayExportFileDTO dto)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public async Task<StayExportFileDTO?> GetDtoByIdAsync(long id)
|
||||
{
|
||||
using var dbContext = contextFactory.CreateDbContext();
|
||||
StayExportFile? dbItem = await dbContext.StayExportFiles
|
||||
.Where(e => e.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return dbItem is not null ? GetDtoFromDb(dbItem) : null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<StayExportFileDTO>> GetDtoListAsync()
|
||||
{
|
||||
using var dbContext = contextFactory.CreateDbContext();
|
||||
|
||||
var dbList = await dbContext.StayExportFiles
|
||||
.OrderByDescending(e => e.ExportTime)
|
||||
.ToListAsync();
|
||||
|
||||
return dbList.Select(e => GetDtoFromDb(e));
|
||||
}
|
||||
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
private static StayExportFileDTO GetDtoFromDb(StayExportFile dbItem)
|
||||
{
|
||||
return EntityMapping.MapStayExportFileEntityToDTO(dbItem);
|
||||
}
|
||||
|
||||
private static StayExportParams GetExportParamsFromDTO(StayExportParamsDTO dto)
|
||||
{
|
||||
if (dto.ExportType == null)
|
||||
{
|
||||
throw new InvalidOperationException("Dto with invalid data");
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
ExportType = dto.ExportType.Value,
|
||||
ExportPeriodStart = dto.ExportPeriodStart?.ToUniversalTime(),
|
||||
ExportPeriodEnd = dto.ExportPeriodEnd?.ToUniversalTime()
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user