feat(Assets): update asset services and background workers
This commit is contained in:
@@ -6,7 +6,7 @@ using FinlyticAssets.Util;
|
||||
namespace FinlyticAssets.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for generating and maintaining the indexed asset reference file used for pre-filtering.
|
||||
/// Provides methods for generating the indexed asset reference file and downloading local asset logos strictly on demand.
|
||||
/// </summary>
|
||||
public interface IAssetsIndexService
|
||||
{
|
||||
@@ -15,16 +15,22 @@ public interface IAssetsIndexService
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task ReCreateIndexFileAsync(CancellationToken cancellationToken);
|
||||
public Task ReCreateIndexFileAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Strictly On Demand: Downloads and saves the logo SVG for a requested ISIN into the local assets/logos folder.
|
||||
/// </summary>
|
||||
public Task<string?> DownloadAndSaveLogoAsync(string isin, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the <see cref="IAssetsIndexService"/> to maintain local asset index references.
|
||||
/// Implements the <see cref="IAssetsIndexService"/> to maintain local asset index references and logo file storage.
|
||||
/// </summary>
|
||||
public class AssetsIndexService : IAssetsIndexService
|
||||
{
|
||||
private readonly ILogger<AssetsIndexService> _logger;
|
||||
private readonly IAssetsDbService _assetsDbService;
|
||||
private static readonly HttpClient _httpClient = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetsIndexService"/> class.
|
||||
@@ -37,19 +43,26 @@ public class AssetsIndexService : IAssetsIndexService
|
||||
_assetsDbService = assetsDbService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ReCreateIndexFileAsync(CancellationToken cancellationToken)
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task ReCreateIndexFileAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assets = await _assetsDbService.GetAllValidAssetsAsync();
|
||||
if (assets == null || !assets.Any())
|
||||
{
|
||||
_logger.LogWarning("No valid assets found in the database to index.");
|
||||
_logger.LogWarning("[{Channel}] No valid assets found in the database to index.", "AssetsChannel");
|
||||
return;
|
||||
}
|
||||
|
||||
var indexAssets = assets.Select(a => new AssetIndex(a.Isin, a.Name)).ToList();
|
||||
var indexAssets = assets
|
||||
.DistinctBy(a => a.Isin)
|
||||
.Select(a => {
|
||||
string cleanIsin = a.Isin.Trim().ToUpperInvariant();
|
||||
// Point directly to our own local backend logo endpoint
|
||||
string imageUrl = $"/api/v1/logo/{cleanIsin}";
|
||||
return new AssetIndex(cleanIsin, a.Name, imageUrl);
|
||||
}).ToList();
|
||||
|
||||
var directoryPath = Volumes.IndexRelativePath;
|
||||
var filePath = Path.Combine(directoryPath, "index.json");
|
||||
@@ -65,23 +78,68 @@ public class AssetsIndexService : IAssetsIndexService
|
||||
await JsonSerializer.SerializeAsync(fileStream, indexAssets, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Successfully recreated asset index file with {Count} entries at {Path}",
|
||||
indexAssets.Count, filePath);
|
||||
_logger.LogInformation("[{Channel}] Successfully recreated asset index file with {Count} entries pointing to local logos at {Path}",
|
||||
"AssetsChannel", indexAssets.Count, filePath);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Disk I/O error occurred while writing the asset index file.");
|
||||
_logger.LogError(ex, "[{Channel}] Disk I/O error occurred while writing the asset index file.", "AssetsChannel");
|
||||
throw;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to serialize the asset index data to JSON.");
|
||||
_logger.LogError(ex, "[{Channel}] Failed to serialize the asset index data to JSON.", "AssetsChannel");
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An unexpected error occurred while recreating the asset index file.");
|
||||
_logger.LogError(ex, "[{Channel}] An unexpected error occurred while recreating the asset index file.", "AssetsChannel");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<string?> DownloadAndSaveLogoAsync(string isin, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(isin)) return null;
|
||||
|
||||
string cleanIsin = isin.Trim().ToUpperInvariant();
|
||||
string directoryPath = Volumes.LogosRelativePath;
|
||||
string filePath = Path.Combine(directoryPath, $"{cleanIsin}.svg");
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
string targetUrl = $"https://assets.traderepublic.com/img/logos/{cleanIsin}/v2/dark.min.svg";
|
||||
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, targetUrl);
|
||||
request.Headers.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
request.Headers.TryAddWithoutValidation("Accept", "image/svg+xml,image/*,*/*");
|
||||
request.Headers.TryAddWithoutValidation("Referer", "https://traderepublic.com/");
|
||||
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
byte[] data = await response.Content.ReadAsByteArrayAsync(cancellationToken);
|
||||
await File.WriteAllBytesAsync(filePath, data, cancellationToken);
|
||||
_logger.LogInformation("[{Channel}] Successfully saved logo SVG for ISIN {Isin} to {Path} on demand", "AssetsChannel", cleanIsin, filePath);
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[{Channel}] Failed to download logo for ISIN {Isin} from {Url}", "AssetsChannel", cleanIsin, targetUrl);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user