feat(Core): update DTOs and shared models
This commit is contained in:
@@ -1,16 +1,13 @@
|
||||
using FinlyticCore.Dtos.Assets;
|
||||
using FinlyticCore.Dtos.Assets;
|
||||
using FinlyticCore.Entities.Assets;
|
||||
|
||||
namespace FinlyticCore.Util;
|
||||
|
||||
public static class AssetMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Mappt eine AssetEntity (Datenbank) sicher auf ein zyklusfreies AssetDto (MQTT Payload).
|
||||
/// </summary>
|
||||
|
||||
public static AssetDto ToDto(this AssetEntity entity)
|
||||
{
|
||||
// 1. Tags zyklusfrei mappen
|
||||
var dtoTags = entity.Tags.Select(t => new TagDto
|
||||
{
|
||||
Id = t.Id,
|
||||
@@ -18,46 +15,91 @@ public static class AssetMapper
|
||||
Type = t.Type
|
||||
}).ToList();
|
||||
|
||||
// 2. Polymorphes Mapping basierend auf dem Laufzeittyp
|
||||
return entity switch
|
||||
{
|
||||
StockEntity stock => new StockDto
|
||||
{
|
||||
Isin = stock.Isin, Name = stock.Name, Type = stock.Type, InstrumentCategory = stock.InstrumentCategory, HasCfd = stock.HasCfd, ImageId = stock.ImageId, UpdateAt = stock.UpdateAt, LastUpdatedAt = stock.LastUpdatedAt, Tags = dtoTags,
|
||||
Isin = stock.Isin,
|
||||
Name = stock.Name,
|
||||
Type = stock.Type,
|
||||
InstrumentCategory = stock.InstrumentCategory,
|
||||
HasCfd = stock.HasCfd,
|
||||
ImageId = stock.ImageId,
|
||||
LastUpdatedAt = stock.LastUpdatedAt,
|
||||
Tags = dtoTags,
|
||||
DerivativeProductCategories = stock.DerivativeProductCategories
|
||||
},
|
||||
EtfEntity etf => new EtfDto
|
||||
{
|
||||
Isin = etf.Isin, Name = etf.Name, Type = etf.Type, InstrumentCategory = etf.InstrumentCategory, HasCfd = etf.HasCfd, ImageId = etf.ImageId, UpdateAt = etf.UpdateAt, LastUpdatedAt = etf.LastUpdatedAt, Tags = dtoTags,
|
||||
DerivativeProductCategories = etf.DerivativeProductCategories, EtfDescription = etf.EtfDescription, MappedEtfIndexName = etf.MappedEtfIndexName, Subtitle = etf.Subtitle, SearchSubtitle = etf.SearchSubtitle
|
||||
Isin = etf.Isin,
|
||||
Name = etf.Name,
|
||||
Type = etf.Type,
|
||||
InstrumentCategory = etf.InstrumentCategory,
|
||||
HasCfd = etf.HasCfd,
|
||||
ImageId = etf.ImageId,
|
||||
LastUpdatedAt = etf.LastUpdatedAt,
|
||||
Tags = dtoTags,
|
||||
DerivativeProductCategories = etf.DerivativeProductCategories,
|
||||
EtfDescription = etf.EtfDescription,
|
||||
MappedEtfIndexName = etf.MappedEtfIndexName,
|
||||
Subtitle = etf.Subtitle,
|
||||
SearchSubtitle = etf.SearchSubtitle
|
||||
},
|
||||
CryptoEntity crypto => new CryptoDto
|
||||
{
|
||||
Isin = crypto.Isin, Name = crypto.Name, Type = crypto.Type, InstrumentCategory = crypto.InstrumentCategory, HasCfd = crypto.HasCfd, ImageId = crypto.ImageId, UpdateAt = crypto.UpdateAt, LastUpdatedAt = crypto.LastUpdatedAt, Tags = dtoTags,
|
||||
Subtitle = crypto.Subtitle, SearchSubtitle = crypto.SearchSubtitle
|
||||
Isin = crypto.Isin,
|
||||
Name = crypto.Name,
|
||||
Type = crypto.Type,
|
||||
InstrumentCategory = crypto.InstrumentCategory,
|
||||
HasCfd = crypto.HasCfd,
|
||||
ImageId = crypto.ImageId,
|
||||
LastUpdatedAt = crypto.LastUpdatedAt,
|
||||
Tags = dtoTags,
|
||||
Subtitle = crypto.Subtitle,
|
||||
SearchSubtitle = crypto.SearchSubtitle
|
||||
},
|
||||
BondEntity bond => new BondDto
|
||||
{
|
||||
Isin = bond.Isin, Name = bond.Name, Type = bond.Type, InstrumentCategory = bond.InstrumentCategory, HasCfd = bond.HasCfd, ImageId = bond.ImageId, UpdateAt = bond.UpdateAt, LastUpdatedAt = bond.LastUpdatedAt, Tags = dtoTags,
|
||||
BondIssuerName = bond.BondIssuerName, SearchSubtitle = bond.SearchSubtitle
|
||||
Isin = bond.Isin,
|
||||
Name = bond.Name,
|
||||
Type = bond.Type,
|
||||
InstrumentCategory = bond.InstrumentCategory,
|
||||
HasCfd = bond.HasCfd,
|
||||
ImageId = bond.ImageId,
|
||||
LastUpdatedAt = bond.LastUpdatedAt,
|
||||
Tags = dtoTags,
|
||||
BondIssuerName = bond.BondIssuerName,
|
||||
SearchSubtitle = bond.SearchSubtitle
|
||||
},
|
||||
DerivativeEntity deriv => new DerivativeDto
|
||||
{
|
||||
Isin = deriv.Isin, Name = deriv.Name, Type = deriv.Type, InstrumentCategory = deriv.InstrumentCategory, HasCfd = deriv.HasCfd, ImageId = deriv.ImageId, UpdateAt = deriv.UpdateAt, LastUpdatedAt = deriv.LastUpdatedAt, Tags = dtoTags,
|
||||
DerivativeProductCategories = deriv.DerivativeProductCategories, UnderlyingIsin = deriv.UnderlyingIsin
|
||||
Isin = deriv.Isin,
|
||||
Name = deriv.Name,
|
||||
Type = deriv.Type,
|
||||
InstrumentCategory = deriv.InstrumentCategory,
|
||||
HasCfd = deriv.HasCfd,
|
||||
ImageId = deriv.ImageId,
|
||||
LastUpdatedAt = deriv.LastUpdatedAt,
|
||||
Tags = dtoTags,
|
||||
DerivativeProductCategories = deriv.DerivativeProductCategories,
|
||||
UnderlyingIsin = deriv.UnderlyingIsin
|
||||
},
|
||||
SyntheticEntity synth => new SyntheticDto
|
||||
{
|
||||
Isin = synth.Isin, Name = synth.Name, Type = synth.Type, InstrumentCategory = synth.InstrumentCategory, HasCfd = synth.HasCfd, ImageId = synth.ImageId, UpdateAt = synth.UpdateAt, LastUpdatedAt = synth.LastUpdatedAt, Tags = dtoTags,
|
||||
Isin = synth.Isin,
|
||||
Name = synth.Name,
|
||||
Type = synth.Type,
|
||||
InstrumentCategory = synth.InstrumentCategory,
|
||||
HasCfd = synth.HasCfd,
|
||||
ImageId = synth.ImageId,
|
||||
LastUpdatedAt = synth.LastUpdatedAt,
|
||||
Tags = dtoTags,
|
||||
DerivativeProductCategories = synth.DerivativeProductCategories
|
||||
},
|
||||
_ => throw new NotSupportedException($"Mapping for type {entity.GetType().Name} is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mappt direkt eine ganze Liste von AssetEntities.
|
||||
/// </summary>
|
||||
|
||||
public static List<AssetDto> ToDtoList(this IEnumerable<AssetEntity> entities)
|
||||
{
|
||||
return entities.Select(e => e.ToDto()).ToList();
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using FinlyticCore.Dtos;
|
||||
using FinlyticCore.Dtos.Fundamentals;
|
||||
using FinlyticCore.Dtos.News;
|
||||
using FinlyticCore.Dtos.Sentiment;
|
||||
using FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
using FinlyticCore.Dtos.Yahoo;
|
||||
using FinlyticCore.Models.Trades;
|
||||
using FinlyticCore.Models.Analyzer;
|
||||
using System.Collections.Generic;
|
||||
using FinlyticAssets.Models;
|
||||
|
||||
namespace FinlyticCore.Util;
|
||||
|
||||
[JsonSourceGenerationOptions(
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(TradeProposalDto))]
|
||||
[JsonSerializable(typeof(List<TradeProposalDto>))]
|
||||
[JsonSerializable(typeof(TradeAcceptanceDto))]
|
||||
[JsonSerializable(typeof(List<TradeAcceptanceDto>))]
|
||||
[JsonSerializable(typeof(CloseTradeRequest))]
|
||||
[JsonSerializable(typeof(ManualAnalysisResponseDto))]
|
||||
[JsonSerializable(typeof(N8nAnalysisResponseDto))]
|
||||
[JsonSerializable(typeof(TradeHourlyUpdateDto))]
|
||||
[JsonSerializable(typeof(TradeFeedbackRecord))]
|
||||
[JsonSerializable(typeof(List<TradeFeedbackRecord>))]
|
||||
[JsonSerializable(typeof(NewsArticleDto))]
|
||||
[JsonSerializable(typeof(List<NewsArticleDto>))]
|
||||
[JsonSerializable(typeof(DiscoveredArticle))]
|
||||
[JsonSerializable(typeof(List<DiscoveredArticle>))]
|
||||
[JsonSerializable(typeof(MatchedAssetDto))]
|
||||
[JsonSerializable(typeof(List<MatchedAssetDto>))]
|
||||
[JsonSerializable(typeof(FinBertResultDto))]
|
||||
[JsonSerializable(typeof(UpdateNewsStatusRequest))]
|
||||
[JsonSerializable(typeof(UpdateNewsStatusResponse))]
|
||||
[JsonSerializable(typeof(N8nRequestPayload))]
|
||||
[JsonSerializable(typeof(N8nResponsePayload))]
|
||||
[JsonSerializable(typeof(N8nMatchedAssetPayload))]
|
||||
[JsonSerializable(typeof(FilteredAssetPayload))]
|
||||
[JsonSerializable(typeof(AssetFundamentalsDto))]
|
||||
[JsonSerializable(typeof(List<AssetFundamentalsDto>))]
|
||||
[JsonSerializable(typeof(CorporateEventDto))]
|
||||
[JsonSerializable(typeof(List<CorporateEventDto>))]
|
||||
[JsonSerializable(typeof(IsinSentimentSummaryDto))]
|
||||
[JsonSerializable(typeof(IsinAnalysisEntry))]
|
||||
[JsonSerializable(typeof(SectorSentimentSummaryDto))]
|
||||
|
||||
[JsonSerializable(typeof(CandleDto))]
|
||||
[JsonSerializable(typeof(List<CandleDto>))]
|
||||
[JsonSerializable(typeof(ChartPatternDto))]
|
||||
[JsonSerializable(typeof(List<ChartPatternDto>))]
|
||||
[JsonSerializable(typeof(IndicatorValuesDto))]
|
||||
[JsonSerializable(typeof(List<IndicatorValuesDto>))]
|
||||
[JsonSerializable(typeof(MarketRegimeDto))]
|
||||
[JsonSerializable(typeof(StrategySignalDto))]
|
||||
[JsonSerializable(typeof(List<StrategySignalDto>))]
|
||||
[JsonSerializable(typeof(TechnicalAnalysisDto))]
|
||||
[JsonSerializable(typeof(LivePriceDto))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(int))]
|
||||
[JsonSerializable(typeof(double))]
|
||||
[JsonSerializable(typeof(bool))]
|
||||
[JsonSerializable(typeof(object))]
|
||||
// Named MQTT request DTOs (replaces anonymous types, required for source-gen serialization)
|
||||
[JsonSerializable(typeof(LimitRequest))]
|
||||
[JsonSerializable(typeof(PaginatedRequest))]
|
||||
[JsonSerializable(typeof(DailyNewsRequest))]
|
||||
[JsonSerializable(typeof(IsinRequest))]
|
||||
[JsonSerializable(typeof(GetTradesRequest))]
|
||||
[JsonSerializable(typeof(ArticleRequest))]
|
||||
[JsonSerializable(typeof(AnalyzeSentimentRequest))]
|
||||
[JsonSerializable(typeof(EmptyRequest))]
|
||||
[JsonSerializable(typeof(ManualAnalysisRpcRequest))]
|
||||
|
||||
[JsonSerializable(typeof(ServiceHealthResponse))]
|
||||
[JsonSerializable(typeof(List<ServiceHealthResponse>))]
|
||||
[JsonSerializable(typeof(FetchLogoResponse))]
|
||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||
[JsonSerializable(typeof(ServiceConfigUpdatePayload))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.AssetDto))]
|
||||
[JsonSerializable(typeof(List<FinlyticCore.Dtos.Assets.AssetDto>))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.StockDto))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.EtfDto))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.CryptoDto))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.BondDto))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.DerivativeDto))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.SyntheticDto))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Dtos.Assets.TagDto))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.Assets.GetValidAssetRequest))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.Assets.SearchAssetsRequest))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.Assets.GetDiscoveryAssetsRequest))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.TradeRepublic.TradeRepublicTickerResponse))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.TradeRepublic.TradeRepublicTickerRequest))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.TradeRepublic.TradeRepublicConnectRequest))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.TradeRepublic.TradeRepublicSearchRequest))]
|
||||
[JsonSerializable(typeof(FinlyticCore.Models.TradeRepublic.TradeRepublicAssetResponse))]
|
||||
[JsonSerializable(typeof(YahooValueDto))]
|
||||
[JsonSerializable(typeof(YahooQuoteSummaryResponseDto))]
|
||||
[JsonSerializable(typeof(YahooQuoteSummaryResultDto))]
|
||||
[JsonSerializable(typeof(YahooQuoteSummaryModulesDto))]
|
||||
[JsonSerializable(typeof(YahooAssetProfileDto))]
|
||||
[JsonSerializable(typeof(YahooCompanyOfficerDto))]
|
||||
[JsonSerializable(typeof(YahooFinancialDataDto))]
|
||||
[JsonSerializable(typeof(YahooDefaultKeyStatisticsDto))]
|
||||
[JsonSerializable(typeof(YahooSummaryDetailDto))]
|
||||
[JsonSerializable(typeof(YahooFinancialStatementHistoryDto))]
|
||||
[JsonSerializable(typeof(YahooIncomeStatementDto))]
|
||||
[JsonSerializable(typeof(YahooBalanceSheetStatementDto))]
|
||||
[JsonSerializable(typeof(YahooCashflowStatementDto))]
|
||||
[JsonSerializable(typeof(YahooCalendarEventsDto))]
|
||||
[JsonSerializable(typeof(YahooEarningsCalendarDto))]
|
||||
[JsonSerializable(typeof(YahooChartResponseDto))]
|
||||
[JsonSerializable(typeof(YahooChartResultWrapperDto))]
|
||||
[JsonSerializable(typeof(YahooChartResultDto))]
|
||||
[JsonSerializable(typeof(YahooChartMetaDto))]
|
||||
[JsonSerializable(typeof(YahooChartIndicatorsDto))]
|
||||
[JsonSerializable(typeof(YahooChartQuoteDto))]
|
||||
[JsonSerializable(typeof(YahooChartAdjCloseDto))]
|
||||
[JsonSerializable(typeof(YahooQuoteResponseDto))]
|
||||
[JsonSerializable(typeof(YahooQuoteResultWrapperDto))]
|
||||
[JsonSerializable(typeof(YahooQuoteItemDto))]
|
||||
[JsonSerializable(typeof(List<AssetIndex>))]
|
||||
[JsonSerializable(typeof(N8nAnalysisRequestDto))]
|
||||
[JsonSerializable(typeof(TickMessageDto))]
|
||||
public partial class FinlyticJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -150,15 +150,34 @@ public abstract class ManagedMqttClient : IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a generic object into a structured JSON string and publishes it to the specified topic.
|
||||
/// Utilizes .NET 8 JSON Source Generators for zero-reflection overhead, with reflection fallback for unregistered types.
|
||||
/// </summary>
|
||||
public Task PublishAsync<T>(string topic, T data, bool retain = false)
|
||||
{
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
byte[] jsonBytes;
|
||||
var typeInfo = FinlyticJsonSerializerContext.Default.GetTypeInfo(typeof(T))
|
||||
?? (data != null ? FinlyticJsonSerializerContext.Default.GetTypeInfo(data.GetType()) : null);
|
||||
|
||||
if (typeInfo != null)
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
||||
};
|
||||
var json = JsonSerializer.Serialize(data, jsonOptions);
|
||||
return PublishAsync(topic, json, retain);
|
||||
jsonBytes = JsonSerializer.SerializeToUtf8Bytes(data, typeInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonBytes = JsonSerializer.SerializeToUtf8Bytes(data);
|
||||
}
|
||||
|
||||
if (!IsConnected)
|
||||
throw new InvalidOperationException("Cannot publish message: MQTT client is offline.");
|
||||
|
||||
var message = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(jsonBytes)
|
||||
.WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.WithRetainFlag(retain)
|
||||
.Build();
|
||||
|
||||
return _mqttClient.PublishAsync(message, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,12 +209,12 @@ public abstract class ManagedMqttClient : IDisposable
|
||||
|
||||
// 2. Serialize and dispatch via the existing JSON helper
|
||||
await PublishAsync(requestTopic, requestData);
|
||||
_logger.LogDebug("RPC request published to '{Topic}' [CorrelationId: {Id}]", requestTopic, correlationId);
|
||||
_logger.LogInformation("RPC request published to '{Topic}' [CorrelationId: {Id}]", requestTopic, correlationId);
|
||||
|
||||
try
|
||||
{
|
||||
// 3. Block asynchronously until the response loop resolves the token
|
||||
var effectiveTimeout = timeout ?? TimeSpan.FromSeconds(10);
|
||||
var effectiveTimeout = timeout ?? TimeSpan.FromSeconds(25);
|
||||
var rawJsonResult = await tcs.Task.WaitAsync(effectiveTimeout);
|
||||
|
||||
if (typeof(TResponse) == typeof(string))
|
||||
@@ -203,6 +222,12 @@ public abstract class ManagedMqttClient : IDisposable
|
||||
return rawJsonResult as TResponse;
|
||||
}
|
||||
|
||||
var respTypeInfo = FinlyticJsonSerializerContext.Default.GetTypeInfo(typeof(TResponse));
|
||||
if (respTypeInfo != null)
|
||||
{
|
||||
return JsonSerializer.Deserialize(rawJsonResult, respTypeInfo) as TResponse;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<TResponse>(rawJsonResult);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
@@ -223,6 +248,7 @@ public abstract class ManagedMqttClient : IDisposable
|
||||
{
|
||||
var topic = e.ApplicationMessage.Topic;
|
||||
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
|
||||
_logger.LogInformation("MQTT message received on topic '{Topic}', length={Length}", topic, payload?.Length ?? 0);
|
||||
|
||||
// Intercept message if it belongs to the RPC response convention
|
||||
if (topic.StartsWith("services/response/"))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace FinlyticAssets.Util;
|
||||
namespace FinlyticCore.Util;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace FinlyticCore.Util;
|
||||
|
||||
public static class StringCodeGenerator
|
||||
{
|
||||
public static string GenerateTraceparent()
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
var spanId = Guid.NewGuid().ToString("N").Substring(0, 16);
|
||||
return $"00-{traceId}-{spanId}-01";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user