feat: Add race upload functionality and file transfer endpoints
- Implemented IRaceUploadCommunicationClient with methods for saving races, creating race points, indexing race points, retrieving race details, and uploading files to the receiver. - Added ReceiveFilePath option to CatalogCommunicationOptions for file transfer configuration. - Enhanced CatalogCommunicationServiceCollectionExtensions to validate ReceiveFilePath. - Developed RaceUploadCommunicationClient to handle race-related API interactions, including saving race data and uploading processed images. - Updated API documentation to reflect new race upload and file transfer endpoints. - Modified Avalonia UI to support race creation and processed image uploads, including new input fields and buttons. - Introduced RaceSaveRequest and ReceiveFileUploadRequest models for structured data handling.
This commit is contained in:
parent
4a0973b681
commit
15b1da4371
11 changed files with 675 additions and 97 deletions
|
|
@ -14,6 +14,16 @@ public interface IRaceUploadCommunicationClient
|
|||
|
||||
Task<UploadFileResponse?> UploadRaceFileAsync(RaceFileUploadRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<RawEndpointResponse> SaveRaceAsync(RaceSaveRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<RawEndpointResponse> CreateRacePointsAsync(long raceId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<RawEndpointResponse> IndexRacePointAsync(long pointId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<RawEndpointResponse> GetRaceDetailAsync(long raceId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<RawEndpointResponse> UploadFileToReceiverAsync(ReceiveFileUploadRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<RawEndpointResponse> ExecuteGaraCommandAsync(IReadOnlyDictionary<string, string?> formFields, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<RawEndpointResponse> ExecuteAdminPhotoCommandAsync(AdminPhotoEndpoint endpoint, IReadOnlyDictionary<string, string?> formFields, CancellationToken cancellationToken = default);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ public sealed class CatalogCommunicationOptions
|
|||
|
||||
public string AdminPageBasePath { get; set; } = "admin/pg";
|
||||
|
||||
public string ReceiveFilePath { get; set; } = "ReceiveFile.abl";
|
||||
|
||||
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
public int RetryCount { get; set; } = 2;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public static class CatalogCommunicationServiceCollectionExtensions
|
|||
.Configure(configure)
|
||||
.Validate(o => o.BaseUri is not null, "CatalogCommunicationOptions.BaseUri is required.")
|
||||
.Validate(o => !string.IsNullOrWhiteSpace(o.AdminPageBasePath), "AdminPageBasePath is required.")
|
||||
.Validate(o => !string.IsNullOrWhiteSpace(o.ReceiveFilePath), "ReceiveFilePath is required.")
|
||||
.Validate(o => o.RequestTimeout > TimeSpan.Zero, "RequestTimeout must be greater than zero.")
|
||||
.Validate(o => o.RetryCount >= 0 && o.RetryCount <= 10, "RetryCount must be between 0 and 10.")
|
||||
.Validate(o => o.RetryBaseDelay > TimeSpan.Zero, "RetryBaseDelay must be greater than zero.");
|
||||
|
|
|
|||
26
Catalog.Communication/Models/RaceSaveRequest.cs
Normal file
26
Catalog.Communication/Models/RaceSaveRequest.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
namespace Catalog.Communication.Models;
|
||||
|
||||
public sealed class RaceSaveRequest
|
||||
{
|
||||
public long IdGara { get; init; }
|
||||
|
||||
public required string Description { get; init; }
|
||||
|
||||
public required DateOnly StartDate { get; init; }
|
||||
|
||||
public DateOnly? EndDate { get; init; }
|
||||
|
||||
public required long TipoGaraId { get; init; }
|
||||
|
||||
public int EventoInLinea { get; init; }
|
||||
|
||||
public int TipoIndicizzazione { get; init; }
|
||||
|
||||
public int FreeEvent { get; init; }
|
||||
|
||||
public string? PathBase { get; init; }
|
||||
|
||||
public string? Localita { get; init; }
|
||||
|
||||
public long? CodGara { get; init; }
|
||||
}
|
||||
16
Catalog.Communication/Models/ReceiveFileUploadRequest.cs
Normal file
16
Catalog.Communication/Models/ReceiveFileUploadRequest.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
namespace Catalog.Communication.Models;
|
||||
|
||||
public sealed class ReceiveFileUploadRequest
|
||||
{
|
||||
public required string FileName { get; init; }
|
||||
|
||||
public required Stream FileStream { get; init; }
|
||||
|
||||
public required string DestinationPath { get; init; }
|
||||
|
||||
public bool OverwriteRemoteFile { get; init; }
|
||||
|
||||
public int? BufferSize { get; init; }
|
||||
|
||||
public string? ContentType { get; init; }
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using System.Net;
|
|||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Globalization;
|
||||
using Catalog.Communication.Abstractions;
|
||||
using Catalog.Communication.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -123,6 +124,97 @@ public sealed class RaceUploadCommunicationClient : IRaceUploadCommunicationClie
|
|||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task<RawEndpointResponse> SaveRaceAsync(RaceSaveRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var formFields = new Dictionary<string, string?>
|
||||
{
|
||||
["cmd"] = "asq",
|
||||
["act"] = "save",
|
||||
["id_gara"] = request.IdGara.ToString(CultureInfo.InvariantCulture),
|
||||
["descrizione"] = request.Description,
|
||||
["dataGaraInizio"] = request.StartDate.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture),
|
||||
["dataGaraFine"] = (request.EndDate ?? request.StartDate).ToString("dd/MM/yyyy", CultureInfo.InvariantCulture),
|
||||
["id_tipoGara"] = request.TipoGaraId.ToString(CultureInfo.InvariantCulture),
|
||||
["flgEventoInLinea"] = request.EventoInLinea.ToString(CultureInfo.InvariantCulture),
|
||||
["flgTipoIndex"] = request.TipoIndicizzazione.ToString(CultureInfo.InvariantCulture),
|
||||
["flgFree"] = request.FreeEvent.ToString(CultureInfo.InvariantCulture),
|
||||
["pathBase"] = request.PathBase,
|
||||
["localita"] = request.Localita,
|
||||
["codGara"] = request.CodGara?.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
return PostFormAsync(GetAdminPagePath("Gara"), formFields, "gara-save", cancellationToken);
|
||||
}
|
||||
|
||||
public Task<RawEndpointResponse> CreateRacePointsAsync(long raceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var formFields = new Dictionary<string, string?>
|
||||
{
|
||||
["cmd"] = "creaPuntiFoto",
|
||||
["id_gara"] = raceId.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
return PostFormAsync(GetAdminPagePath("Gara"), formFields, "gara-create-points", cancellationToken);
|
||||
}
|
||||
|
||||
public Task<RawEndpointResponse> IndexRacePointAsync(long pointId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var formFields = new Dictionary<string, string?>
|
||||
{
|
||||
["cmd"] = "indexFoto",
|
||||
["id_puntoFotoIdx"] = pointId.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
return PostFormAsync(GetAdminPagePath("Gara"), formFields, "gara-index-point", cancellationToken);
|
||||
}
|
||||
|
||||
public Task<RawEndpointResponse> GetRaceDetailAsync(long raceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var formFields = new Dictionary<string, string?>
|
||||
{
|
||||
["cmd"] = "search",
|
||||
["id_gara"] = raceId.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
return PostFormAsync(GetAdminPagePath("Gara"), formFields, "gara-detail", cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<RawEndpointResponse> UploadFileToReceiverAsync(ReceiveFileUploadRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(request.FileName);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(request.DestinationPath);
|
||||
ArgumentNullException.ThrowIfNull(request.FileStream);
|
||||
|
||||
var payload = await ReadAllBytesAsync(request.FileStream, cancellationToken).ConfigureAwait(false);
|
||||
var query = new Dictionary<string, string?>
|
||||
{
|
||||
["name"] = request.FileName,
|
||||
["path"] = request.DestinationPath,
|
||||
["overwriteRemoteFile"] = request.OverwriteRemoteFile ? "true" : "false",
|
||||
["bs"] = request.BufferSize?.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
var path = AppendQuery(GetReceiveFilePath(), query);
|
||||
|
||||
return await ExecuteWithResilienceAsync(
|
||||
() =>
|
||||
{
|
||||
var byteContent = new ByteArrayContent(payload);
|
||||
byteContent.Headers.ContentType = new MediaTypeHeaderValue(request.ContentType ?? "application/octet-stream");
|
||||
|
||||
return new HttpRequestMessage(HttpMethod.Post, path)
|
||||
{
|
||||
Content = byteContent,
|
||||
};
|
||||
},
|
||||
ToRawResponseAsync,
|
||||
"receiver-upload",
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<RawEndpointResponse> ExecuteGaraCommandAsync(IReadOnlyDictionary<string, string?> formFields, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return PostFormAsync(GetAdminPagePath("Gara"), formFields, "gara-command", cancellationToken);
|
||||
|
|
@ -503,4 +595,15 @@ public sealed class RaceUploadCommunicationClient : IRaceUploadCommunicationClie
|
|||
var basePath = _options.Value.AdminPageBasePath.Trim('/');
|
||||
return $"{basePath}/{pageName}.abl";
|
||||
}
|
||||
|
||||
private string GetReceiveFilePath()
|
||||
{
|
||||
var value = _options.Value.ReceiveFilePath;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return "ReceiveFile.abl";
|
||||
}
|
||||
|
||||
return value.Trim();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue