feat(fundamentals): dynamic settings, IFinlyticLogger, live log streaming, and EF migration
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
using FinlyticCore.Database;
|
||||
using FinlyticCore.Entities.Settings;
|
||||
using FinlyticFundamentals.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace FinlyticFundamentals.Database;
|
||||
|
||||
public class FundamentalsDbContext : DbContext
|
||||
public class FundamentalsDbContext : DbContext, ISettingsDbContext
|
||||
{
|
||||
public FundamentalsDbContext(DbContextOptions<FundamentalsDbContext> options) : base(options)
|
||||
{
|
||||
@@ -23,7 +25,7 @@ public class FundamentalsDbContext : DbContext
|
||||
modelBuilder.Entity<SettingEntity>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.Key);
|
||||
entity.HasIndex(e => e.Key).IsUnique();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<AssetDataEntity>(entity =>
|
||||
@@ -93,3 +95,13 @@ public class FundamentalsDbContext : DbContext
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class FundamentalsDbContextFactory : IDesignTimeDbContextFactory<FundamentalsDbContext>
|
||||
{
|
||||
public FundamentalsDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<FundamentalsDbContext>();
|
||||
optionsBuilder.UseNpgsql("Host=localhost;Database=fundamentals;Username=postgres;Password=postgres");
|
||||
return new FundamentalsDbContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FinlyticFundamentals.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FinlyticFundamentals.Migrations
|
||||
{
|
||||
[DbContext(typeof(FundamentalsDbContext))]
|
||||
[Migration("20260815183935_AddDynamicSettings")]
|
||||
partial class AddDynamicSettings
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FinlyticCore.Entities.Settings.SettingEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("character varying(150)");
|
||||
|
||||
b.Property<DateTime>("LastUpdatedUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ServiceIdentifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("ValueJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Key")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DynamicSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.AssetDataEntity", b =>
|
||||
{
|
||||
b.Property<string>("Isin")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Isin");
|
||||
|
||||
b.ToTable("AssetData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.AssetEventEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AssetDataIsin")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AssetDataIsin");
|
||||
|
||||
b.ToTable("AssetEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.FundamentalDataEntity", b =>
|
||||
{
|
||||
b.Property<string>("Isin")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("AssetDataIsin")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConsensusRating")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("CurrentRatio")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("DebtToEquity")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("DilutedEps")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("Ebitda")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("EnterpriseValue")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("EvToEbitda")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("FiftyTwoWeekHigh")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("FiftyTwoWeekLow")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("ForwardDividendYield")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("ForwardPe")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("FreeCashFlow")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("GrossProfit")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTime>("LastUpdatedUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<decimal?>("MarketCap")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("NetIncome")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("OperatingCashFlow")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("OperatingIncome")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PayoutRatio")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PegRatio")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PercentHeldByInsiders")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PercentHeldByInstitutions")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PriceTargetHigh")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PriceTargetLow")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PriceTargetMean")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PriceToBook")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("PriceToSales")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("ReturnOnAssets")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("ReturnOnEquity")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("RevenueGrowthYoY")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("ShortPercentOfFloat")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("ShortRatio")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("TotalCash")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("TotalDebt")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("TotalRevenue")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal?>("TrailingPe")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.HasKey("Isin");
|
||||
|
||||
b.HasIndex("AssetDataIsin");
|
||||
|
||||
b.ToTable("FundamentalData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.KeyExecutiveEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AssetDataIsin")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Payment")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AssetDataIsin", "SortOrder");
|
||||
|
||||
b.ToTable("KeyExecutives");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.AssetDataEntity", b =>
|
||||
{
|
||||
b.OwnsOne("FinlyticFundamentals.Entities.TickerEntity", "PrimaryTicker", b1 =>
|
||||
{
|
||||
b1.Property<string>("AssetDataEntityIsin")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Exchange")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("")
|
||||
.HasColumnName("PrimaryTickerExchange");
|
||||
|
||||
b1.Property<string>("Ticker")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("")
|
||||
.HasColumnName("PrimaryTicker");
|
||||
|
||||
b1.HasKey("AssetDataEntityIsin");
|
||||
|
||||
b1.ToTable("AssetData");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AssetDataEntityIsin");
|
||||
});
|
||||
|
||||
b.OwnsMany("FinlyticFundamentals.Entities.TickerEntity", "AvailableTickers", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("AssetDataIsin")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Exchange")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("Exchange");
|
||||
|
||||
b1.Property<string>("Ticker")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("Ticker");
|
||||
|
||||
b1.HasKey("Id");
|
||||
|
||||
b1.HasIndex("AssetDataIsin");
|
||||
|
||||
b1.HasIndex("Ticker");
|
||||
|
||||
b1.ToTable("Tickers", (string)null);
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AssetDataIsin");
|
||||
});
|
||||
|
||||
b.Navigation("AvailableTickers");
|
||||
|
||||
b.Navigation("PrimaryTicker")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.AssetEventEntity", b =>
|
||||
{
|
||||
b.HasOne("FinlyticFundamentals.Entities.AssetDataEntity", "AssetData")
|
||||
.WithMany("AssetEvents")
|
||||
.HasForeignKey("AssetDataIsin")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("FinlyticFundamentals.Entities.TickerEntity", "Ticker", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AssetEventEntityId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Exchange")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("")
|
||||
.HasColumnName("TickerExchange");
|
||||
|
||||
b1.Property<string>("Ticker")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("")
|
||||
.HasColumnName("Ticker");
|
||||
|
||||
b1.HasKey("AssetEventEntityId");
|
||||
|
||||
b1.ToTable("AssetEvents");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AssetEventEntityId");
|
||||
});
|
||||
|
||||
b.Navigation("AssetData");
|
||||
|
||||
b.Navigation("Ticker")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.FundamentalDataEntity", b =>
|
||||
{
|
||||
b.HasOne("FinlyticFundamentals.Entities.AssetDataEntity", "AssetData")
|
||||
.WithMany("FundamentalData")
|
||||
.HasForeignKey("AssetDataIsin")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("FinlyticFundamentals.Entities.TickerEntity", "Ticker", b1 =>
|
||||
{
|
||||
b1.Property<string>("FundamentalDataEntityIsin")
|
||||
.HasColumnType("text");
|
||||
|
||||
b1.Property<string>("Exchange")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("")
|
||||
.HasColumnName("TickerExchange");
|
||||
|
||||
b1.Property<string>("Ticker")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("")
|
||||
.HasColumnName("Ticker");
|
||||
|
||||
b1.HasKey("FundamentalDataEntityIsin");
|
||||
|
||||
b1.ToTable("FundamentalData");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FundamentalDataEntityIsin");
|
||||
});
|
||||
|
||||
b.Navigation("AssetData");
|
||||
|
||||
b.Navigation("Ticker")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.KeyExecutiveEntity", b =>
|
||||
{
|
||||
b.HasOne("FinlyticFundamentals.Entities.AssetDataEntity", "AssetData")
|
||||
.WithMany("KeyExecutives")
|
||||
.HasForeignKey("AssetDataIsin")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AssetData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticFundamentals.Entities.AssetDataEntity", b =>
|
||||
{
|
||||
b.Navigation("AssetEvents");
|
||||
|
||||
b.Navigation("FundamentalData");
|
||||
|
||||
b.Navigation("KeyExecutives");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FinlyticFundamentals.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDynamicSettings : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_DynamicSettings_Key",
|
||||
table: "DynamicSettings");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DynamicSettings_Key",
|
||||
table: "DynamicSettings",
|
||||
column: "Key",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_DynamicSettings_Key",
|
||||
table: "DynamicSettings");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DynamicSettings_Key",
|
||||
table: "DynamicSettings",
|
||||
column: "Key");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,8 @@ namespace FinlyticFundamentals.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Key");
|
||||
b.HasIndex("Key")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DynamicSettings");
|
||||
});
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
using FinlyticCore.Clients;
|
||||
using FinlyticCore.Database;
|
||||
using FinlyticCore.Services;
|
||||
using FinlyticCore.Services.PlaywrightScrapper;
|
||||
using FinlyticCore.Services.TradeRepublic;
|
||||
using FinlyticCore.Services.Yahoo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FinlyticFundamentals.Database;
|
||||
using FinlyticFundamentals.Services;
|
||||
using FinlyticFundamentals.Util;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Register DB Context
|
||||
// Register DB Context & ISettingsDbContext
|
||||
builder.Services.AddDbContext<FundamentalsDbContext>(options =>
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))
|
||||
.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)));
|
||||
builder.Services.AddScoped<ISettingsDbContext>(sp => sp.GetRequiredService<FundamentalsDbContext>());
|
||||
|
||||
// Register HTTP Clients
|
||||
builder.Services.AddHttpClient<IYahooFinanceScraper, YahooFinanceScraper>()
|
||||
@@ -25,22 +31,23 @@ builder.Services.AddHttpClient<IYahooFinanceScraper, YahooFinanceScraper>()
|
||||
AllowAutoRedirect = true
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<IPlaywrightBrowserFactory, PlaywrightBrowserFactory>();
|
||||
builder.Services.AddSingleton<IPlaywrightExecutionService, PlaywrightExecutionService>();
|
||||
builder.Services.AddTransient<IYahooFinanceHtmlClient, YahooFinanceHtmlClient<FundamentalsDbService, FundamentalsDbContext>>();
|
||||
builder.Services.AddScoped<IPlaywrightBrowserFactory, PlaywrightBrowserFactory>();
|
||||
builder.Services.AddScoped<IPlaywrightExecutionService, PlaywrightExecutionService>();
|
||||
builder.Services.AddTransient<IYahooFinanceHtmlClient, YahooFinanceHtmlClient>();
|
||||
|
||||
// Register Application Services
|
||||
builder.Services.AddSingleton<TradeRepublicClient>();
|
||||
builder.Services.AddSingleton<ITradeRepublicService, TradeRepublicService>();
|
||||
builder.Services.AddSingleton<FinlyticCore.Services.Yahoo.YahooFinanceClient>();
|
||||
builder.Services.AddTransient<IFundamentalsDbService, FundamentalsDbService>();
|
||||
builder.Services.AddScoped<TradeRepublicClient>();
|
||||
builder.Services.AddScoped<ITradeRepublicService, TradeRepublicService>();
|
||||
builder.Services.AddSingleton<YahooFinanceClient>();
|
||||
builder.Services.AddScoped<IFundamentalsDbService, FundamentalsDbService>();
|
||||
builder.Services.AddScoped<IYahooFinanceScraper, YahooFinanceScraper>();
|
||||
|
||||
builder.Services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>));
|
||||
builder.Services.AddScoped(typeof(IFinlyticLogger<,>), typeof(FinlyticLogger<,>));
|
||||
builder.Services.AddSingleton<ISettingsService, SettingsService>();
|
||||
builder.Services.AddSingleton(typeof(IFinlyticLogger<>), typeof(FinlyticLogger<>));
|
||||
|
||||
// Register MQTT Client (as a Hosted Service)
|
||||
builder.Services.AddHostedService<FundamentalsMqttClient>();
|
||||
builder.Services.AddSingleton<FundamentalsMqttClient>();
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<FundamentalsMqttClient>());
|
||||
|
||||
var host = builder.Build();
|
||||
|
||||
@@ -52,7 +59,6 @@ using (var scope = host.Services.CreateScope())
|
||||
var context = scope.ServiceProvider.GetRequiredService<FundamentalsDbContext>();
|
||||
await context.Database.MigrateAsync();
|
||||
Console.WriteLine("Database migrations successfully executed for FinlyticFundamentals.");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using FinlyticCore.Dtos.Yahoo;
|
||||
using FinlyticCore.Models.Settings;
|
||||
using FinlyticCore.Services;
|
||||
using FinlyticCore.Services.TradeRepublic;
|
||||
using FinlyticCore.Services.Yahoo;
|
||||
using FinlyticFundamentals.Database;
|
||||
using FinlyticFundamentals.Entities;
|
||||
using FinlyticFundamentals.Util;
|
||||
@@ -39,13 +40,13 @@ public class FundamentalsDbService : IFundamentalsDbService
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IYahooFinanceScraper _scraper;
|
||||
private readonly ITradeRepublicService _tradeRepublicService;
|
||||
private readonly IFinlyticLogger<FundamentalsDbService, FundamentalsDbContext> _finlyticLogger;
|
||||
private readonly IFinlyticLogger<FundamentalsDbService> _finlyticLogger;
|
||||
|
||||
public FundamentalsDbService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IYahooFinanceScraper scraper,
|
||||
ITradeRepublicService tradeRepublicService,
|
||||
IFinlyticLogger<FundamentalsDbService, FundamentalsDbContext> finlyticLogger)
|
||||
IFinlyticLogger<FundamentalsDbService> finlyticLogger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_scraper = scraper;
|
||||
@@ -71,7 +72,7 @@ public class FundamentalsDbService : IFundamentalsDbService
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var context = scope.ServiceProvider.GetRequiredService<FundamentalsDbContext>();
|
||||
var settingsService = scope.ServiceProvider.GetRequiredService<ISettingsService<FundamentalsDbContext>>();
|
||||
var settingsService = scope.ServiceProvider.GetRequiredService<ISettingsService>();
|
||||
|
||||
// 1. Dynamic Settings lesen
|
||||
bool allowForceRefresh =
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FinlyticCore.Dtos;
|
||||
using FinlyticCore.Dtos.Settings;
|
||||
using FinlyticCore.Models;
|
||||
using FinlyticCore.Services;
|
||||
using FinlyticCore.Util;
|
||||
using FinlyticFundamentals.Database;
|
||||
using FinlyticFundamentals.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -60,7 +61,17 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
|
||||
await SubscribeAsync("services/request/fundamentals_Get/#");
|
||||
await SubscribeAsync("services/request/events_GetAll/#");
|
||||
await SubscribeAsync("services/request/events_GetByMonth/#");
|
||||
await SubscribeAsync("services/request/fundamentals_settings_GetAll/#");
|
||||
await SubscribeAsync("services/request/fundamentals_settings_Update/#");
|
||||
await SubscribeAsync("services/request/health_Ping/#");
|
||||
|
||||
FinlyticLogBroadcaster.OnLogPublished = async (logDto) =>
|
||||
{
|
||||
if (IsConnected && string.Equals(logDto.ServiceName, "FinlyticFundamentals", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await PublishAsync("finlytic/logs/FinlyticFundamentals", logDto);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -85,6 +96,14 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
|
||||
{
|
||||
await OnEventsGetByMonthAsync(payload, correlationId);
|
||||
}
|
||||
else if (topic.StartsWith("services/request/fundamentals_settings_GetAll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await OnSettingsGetAllAsync(correlationId);
|
||||
}
|
||||
else if (topic.StartsWith("services/request/fundamentals_settings_Update", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await OnSettingsUpdateAsync(payload, correlationId);
|
||||
}
|
||||
else if (topic.StartsWith("services/request/health_Ping", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await OnHealthPingAsync(topic, correlationId);
|
||||
@@ -93,8 +112,8 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
|
||||
|
||||
private async Task OnFundamentalsGetAsync(string payload, string correlationId)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient, FundamentalsDbContext>>();
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient>>();
|
||||
var dbService = scope.ServiceProvider.GetRequiredService<IFundamentalsDbService>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(payload))
|
||||
@@ -129,8 +148,8 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
|
||||
|
||||
private async Task OnEventsGetAllAsync(string correlationId)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient, FundamentalsDbContext>>();
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient>>();
|
||||
var dbService = scope.ServiceProvider.GetRequiredService<IFundamentalsDbService>();
|
||||
|
||||
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [MQTT_Client] Processing RPC events_GetAll request [CorrelationId: {CorrelationId}]", correlationId);
|
||||
@@ -152,8 +171,8 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(payload)) return;
|
||||
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient, FundamentalsDbContext>>();
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient>>();
|
||||
var dbService = scope.ServiceProvider.GetRequiredService<IFundamentalsDbService>();
|
||||
|
||||
try
|
||||
@@ -175,12 +194,78 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnSettingsGetAllAsync(string correlationId)
|
||||
{
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient>>();
|
||||
var settingsService = scope.ServiceProvider.GetRequiredService<ISettingsService>();
|
||||
|
||||
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [Settings_GetAll] Retrieving all dynamic settings via reflection [CorrelationId: {CorrelationId}]", correlationId);
|
||||
try
|
||||
{
|
||||
var settings = await settingsService.GetAllRegisteredSettingsAsync(new[] { typeof(SettingKeys) });
|
||||
var responseTopic = $"services/response/fundamentals_settings_GetAll/{correlationId}";
|
||||
|
||||
await PublishAsync(responseTopic, settings);
|
||||
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [Settings_GetAll] Published {Count} settings to '{ResponseTopic}'", settings.Count, responseTopic);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await finlyticLogger.LogErrorAsync(SettingKeys.MqttChannel, ex, "[FinlyticFundamentals] [Settings_GetAll] Failed to retrieve settings.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnSettingsUpdateAsync(string payload, string correlationId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(payload)) return;
|
||||
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient>>();
|
||||
var settingsService = scope.ServiceProvider.GetRequiredService<ISettingsService>();
|
||||
|
||||
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [Settings_Update] Processing settings update RPC [CorrelationId: {CorrelationId}]", correlationId);
|
||||
try
|
||||
{
|
||||
Dictionary<string, object?>? updates = null;
|
||||
try
|
||||
{
|
||||
updates = JsonSerializer.Deserialize<Dictionary<string, object?>>(payload);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var list = JsonSerializer.Deserialize<List<DynamicSettingDto>>(payload);
|
||||
if (list != null)
|
||||
{
|
||||
updates = new Dictionary<string, object?>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
updates[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updates != null && updates.Count > 0)
|
||||
{
|
||||
await settingsService.UpdateSettingsAsync(updates);
|
||||
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [Settings_Update] Successfully updated {Count} settings in database and cache.", updates.Count);
|
||||
}
|
||||
|
||||
var currentSettings = await settingsService.GetAllRegisteredSettingsAsync(new[] { typeof(SettingKeys) });
|
||||
var responseTopic = $"services/response/fundamentals_settings_Update/{correlationId}";
|
||||
await PublishAsync(responseTopic, currentSettings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await finlyticLogger.LogErrorAsync(SettingKeys.MqttChannel, ex, "[FinlyticFundamentals] [Settings_Update] Failed to update settings.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnHealthPingAsync(string topic, string correlationId)
|
||||
{
|
||||
if (topic.Contains("FinlyticFundamentals", StringComparison.OrdinalIgnoreCase) || !topic.Contains("/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient, FundamentalsDbContext>>();
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient>>();
|
||||
|
||||
var respTopic = $"services/response/health_Ping/{correlationId}";
|
||||
await PublishAsync(respTopic, new ServiceHealthResponse("FinlyticFundamentals", "Online", DateTime.UtcNow, "Connected"));
|
||||
|
||||
Reference in New Issue
Block a user