feat(Core): update DTOs and shared models

This commit is contained in:
2026-08-09 21:01:38 +02:00
parent 6337e63a77
commit 5475c3ac51
58 changed files with 3418 additions and 30 deletions
+62 -20
View File
@@ -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
{
}
+34 -8
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
namespace FinlyticAssets.Util;
namespace FinlyticCore.Util;
using System;
using System.IO;
+11
View File
@@ -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";
}
}