feat(core): update DTOs, Trade Republic client, Yahoo scrapers, and dynamic settings
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FinlyticCore.Entities.Settings;
|
||||
using FinlyticCore.Models.Settings;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FinlyticCore.Services;
|
||||
|
||||
public interface ISettingsService<TContext> where TContext : DbContext
|
||||
{
|
||||
// --- 1. Typsicherer Zugriff über SettingKey<T> (Empfohlen) ---
|
||||
Task<T> GetSettingAsync<T>(SettingKey<T> key,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task SetSettingAsync<T>(SettingKey<T> key, T value,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
// --- 2. Dynamischer Zugriff über Enum-Key ---
|
||||
Task<T> GetSettingAsync<TEnum, T>(TEnum enumKey, T defaultValue = default!,
|
||||
CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
||||
|
||||
Task SetSettingAsync<TEnum, T>(TEnum enumKey, T value,
|
||||
CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
||||
|
||||
// --- 3. Dynamischer Zugriff über String-Key ---
|
||||
Task<T> GetSettingAsync<T>(string key, T defaultValue = default!,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task SetSettingAsync<T>(string key, T value,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class SettingsService<TContext> : ISettingsService<TContext> where TContext : DbContext
|
||||
{
|
||||
private readonly Microsoft.Extensions.DependencyInjection.IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<SettingsService<TContext>>? _logger;
|
||||
|
||||
// Fast In-Memory Cache: Key Schema: "KeyName"
|
||||
private readonly ConcurrentDictionary<string, string> _cache = new();
|
||||
|
||||
public SettingsService(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory scopeFactory, ILogger<SettingsService<TContext>>? logger = null)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#region SettingKey<T> Overloads
|
||||
|
||||
public Task<T> GetSettingAsync<T>(SettingKey<T> key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return GetSettingInternalAsync(key.Name, key.DefaultValue, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SetSettingAsync<T>(SettingKey<T> key, T value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return SetSettingInternalAsync(key.Name, value, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enum-Key Overloads
|
||||
|
||||
public Task<T> GetSettingAsync<TEnum, T>(TEnum enumKey, T defaultValue = default!,
|
||||
CancellationToken cancellationToken = default)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
var keyName = $"{typeof(TEnum).Name}.{enumKey}";
|
||||
return GetSettingInternalAsync(keyName, defaultValue, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SetSettingAsync<TEnum, T>(TEnum enumKey, T value, CancellationToken cancellationToken = default)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
var keyName = $"{typeof(TEnum).Name}.{enumKey}";
|
||||
return SetSettingInternalAsync(keyName, value, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region String-Key Overloads
|
||||
|
||||
public Task<T> GetSettingAsync<T>(string key, T defaultValue = default!,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return GetSettingInternalAsync(key, defaultValue, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SetSettingAsync<T>(string key, T value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return SetSettingInternalAsync(key, value, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Core Engine Logik
|
||||
|
||||
private async Task<T> GetSettingInternalAsync<T>(string key, T defaultValue, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. Zuerst im In-Memory Cache prüfen
|
||||
if (_cache.TryGetValue(key, out var cachedJson))
|
||||
{
|
||||
return DeserializeValue(cachedJson, defaultValue);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<TContext>(scope.ServiceProvider);
|
||||
|
||||
// 2. Aus DB der spezifischen TContext-Instanz laden
|
||||
var entity = await dbContext.Set<SettingEntity>()
|
||||
.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
||||
|
||||
// 3. Falls noch nicht vorhanden: In DB anlegen (Seed on Demand)
|
||||
if (entity == null)
|
||||
{
|
||||
var defaultJson = JsonSerializer.Serialize(defaultValue);
|
||||
entity = new SettingEntity
|
||||
{
|
||||
Key = key,
|
||||
ValueJson = defaultJson,
|
||||
LastUpdatedUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
dbContext.Set<SettingEntity>().Add(entity);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_cache[key] = defaultJson;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// In Cache legen & Wert zurückgeben
|
||||
_cache[key] = entity.ValueJson;
|
||||
return DeserializeValue(entity.ValueJson, defaultValue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Setting '{Key}' could not be loaded or initialized in DB. Using default value in memory.", key);
|
||||
_cache[key] = JsonSerializer.Serialize(defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetSettingInternalAsync<T>(string key, T value, CancellationToken cancellationToken)
|
||||
{
|
||||
var jsonValue = JsonSerializer.Serialize(value);
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<TContext>(scope.ServiceProvider);
|
||||
|
||||
var entity = await dbContext.Set<SettingEntity>()
|
||||
.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
entity = new SettingEntity
|
||||
{
|
||||
Key = key,
|
||||
ValueJson = jsonValue,
|
||||
LastUpdatedUtc = DateTime.UtcNow
|
||||
};
|
||||
dbContext.Set<SettingEntity>().Add(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.ValueJson = jsonValue;
|
||||
entity.LastUpdatedUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Setting '{Key}' could not be saved to DB.", key);
|
||||
}
|
||||
|
||||
// Cache trotzdem aktualisieren
|
||||
_cache[key] = jsonValue;
|
||||
}
|
||||
|
||||
private static T DeserializeValue<T>(string json, T defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<T>(json);
|
||||
return result ?? defaultValue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user