Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7497ec1
start mongodb implementation
JWMB Dec 12, 2025
f689714
semi-working
JWMB Dec 13, 2025
dce8b04
split into files
JWMB Dec 13, 2025
4104153
experiments
JWMB Dec 14, 2025
3124b19
semi-working
JWMB Dec 14, 2025
a2caec6
cleanup
JWMB Dec 14, 2025
30fef0d
cleanup
JWMB Dec 14, 2025
8ece083
migration tooling
JWMB Jan 24, 2026
8ec4d67
revert to actual migration
JWMB Jan 24, 2026
46ae24f
migrate users, config
JWMB Jan 25, 2026
37c9308
cleanup
JWMB Jan 25, 2026
6e3407e
MongoTrainingRepository: TId -> string
JWMB Jan 26, 2026
963ee99
mongodb working
JWMB Jan 31, 2026
2bb1c98
local MLPredictNumberlineLevelService, QueueListener: QueueClient -> …
JWMB Feb 1, 2026
637a650
compose.yaml
JWMB Feb 21, 2026
ac79eae
dotnet 10, a few nugets
JWMB Feb 21, 2026
23356fa
nugets
JWMB Feb 21, 2026
08efd10
forgot LightGbm
JWMB Feb 21, 2026
e571abe
revert breaking nuget upgrades
JWMB Feb 21, 2026
5b73706
fix build errors
JWMB Feb 21, 2026
2b4bb45
Merge branch 'main' into mongodb-dotnet10
JWMB Feb 21, 2026
a091945
Startup: log instead of throw
JWMB Feb 21, 2026
536bb6e
remove unnecessary System.Text.Encodings.Web
JWMB Feb 21, 2026
09e6655
ASPNETCORE_ENVIRONMENT: Development, allow fake login when Development
JWMB Feb 21, 2026
a222db8
working compose
JWMB Feb 23, 2026
958b64a
svelte app attempts
JWMB Feb 24, 2026
2606c9f
http://api et.al
JWMB Feb 25, 2026
35e93bc
.dockerignore - add build (otherwise vite uses old files?!)
JWMB Feb 26, 2026
1f5d7f1
adminapp logging
JWMB Feb 26, 2026
bc75c75
compose: adminapp port
JWMB Feb 26, 2026
198f9c4
http-server --proxy
JWMB Feb 26, 2026
8307779
svelte: attempts to read runtime env
JWMB Feb 26, 2026
2bab64a
trainingapi environment=Docker
JWMB Feb 26, 2026
f809f10
better Dockerfile.web
JWMB Feb 26, 2026
6538109
initial users from env variables
JWMB Feb 26, 2026
b0aa11d
some svelte+firefox bug
JWMB Feb 27, 2026
3a27189
mongodb-related fixes
JWMB Feb 27, 2026
b0dfd48
ITrainingSummaryRepository.GetByIds
JWMB Feb 28, 2026
507dd15
IPredictNumberlineLevelService: LocalMLPredictor
JWMB Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,6 @@

# performance testing sandbox
**/sandbox

# Vite
# **/build/ huh, also ignored when building inside dockerfile, thought it was only during COPY...
7 changes: 3 additions & 4 deletions Common.Web/Common.Web.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand All @@ -9,8 +9,7 @@
<PackageReference Include="Microsoft.ApplicationInsights" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" VersionOverride="8.0.1" />
<PackageReference Include="System.Text.Encodings.Web" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" VersionOverride="8.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PluginModuleBase\PluginModuleBase.csproj" />
Expand Down
16 changes: 16 additions & 0 deletions Common.Web/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.Extensions.Hosting;

namespace Common.Web
{
public static class Extensions
{
public static bool HasEnvironmentPart(this IHostEnvironment hostEnvironment, string environmentPart)
{
ArgumentNullException.ThrowIfNull(hostEnvironment);
return hostEnvironment.EnvironmentName.Split('.')
.Any(part => string.Equals(environmentPart, part, StringComparison.OrdinalIgnoreCase));
}
public static bool HasDevelopmentEnvironment(this IHostEnvironment hostEnvironment)
=> hostEnvironment.HasEnvironmentPart(Environments.Development);
}
}
7 changes: 4 additions & 3 deletions Common.Web/ServiceConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ namespace Common.Web
{
public static class ServiceConfiguration
{
public static void ConfigureProcessingPipelineServices(IServiceCollection services, IEnumerable<IPluginModule> pluginModules)
public static void ConfigureProcessingPipelineServices(IServiceCollection services, IConfiguration config, IEnumerable<IPluginModule> pluginModules)
{
services.AddSingleton<ITableClientFactory, TableClientFactory>(); //(sp => new TableClientFactory("vektor")
services.AddSingleton<IDataSink, AzureTableLogSink>();
services.AddSingleton<IProcessingMiddlewarePipelineRepository, ProcessingPipelineRepository>();
services.AddSingleton<SinkProcessingMiddleware>();

foreach (var plugin in pluginModules)
plugin.ConfigureServices(services);
plugin.ConfigureServices(services, config);
}

public static void ConfigurePlugins(IApplicationBuilder app, IEnumerable<IPluginModule> pluginModules)
Expand All @@ -36,7 +36,8 @@ public static void ConfigureApplicationInsights(IApplicationBuilder app, IConfig
if (aiConn == "SECRET" || aiConn == string.Empty)
{
if (isDevelopment == false)
throw new ArgumentException("InstrumentationKey not set");
Console.WriteLine($"Warning: InstrumentationKey not set ({aiConn})");
//throw new ArgumentException("InstrumentationKey not set");
}
else
{
Expand Down
128 changes: 87 additions & 41 deletions Common.Web/TypedConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,55 +21,101 @@ public class TypedConfiguration
// https://referbruv.com/blog/posts/working-with-options-pattern-in-aspnet-core-the-complete-guide
var appSettings = new T();

config.GetSection(sectionKey).Bind(appSettings);
services.AddSingleton(appSettings.GetType(), appSettings!);
//config.GetSection(sectionKey).Bind(appSettings);
//services.AddSingleton(appSettings.GetType(), appSettings!);

RecurseBind(appSettings, services, config);
//RecurseBind(appSettings, services, config);
XBind(appSettings, services, config.GetSection(typeof(T).Name));
// https://kaylumah.nl/2021/11/29/validated-strongly-typed-ioptions.html
// If we want to inject IOptions<Type> instead of just Type, this is needed: https://stackoverflow.com/a/61157181 services.ConfigureOptions(instance)
//services.Configure<AceKnowledgeOptions>(config.GetSection("AceKnowledge"));

// https://kaylumah.nl/2021/11/29/validated-strongly-typed-ioptions.html
// If we want to inject IOptions<Type> instead of just Type, this is needed: https://stackoverflow.com/a/61157181 services.ConfigureOptions(instance)
//services.Configure<AceKnowledgeOptions>(config.GetSection("AceKnowledge"));

return appSettings;
return appSettings;
}

private static void RecurseBind(object appSettings, IServiceCollection services, IConfiguration config)
private static void XBind(object setting, IServiceCollection services, IConfiguration config)
{
config.Bind(setting);
Rec(setting);
void Rec(object s)
{
services.AddSingleton(s.GetType(), s);
var props = s.GetType()
.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
foreach (var item in props)
{
if (item.PropertyType == typeof(string) || item.PropertyType.IsPrimitive)
{ }
else if (item.PropertyType.IsAssignableTo(typeof(System.Collections.IEnumerable))
&& item.PropertyType.IsGenericType)
{ }
else
{
var v = item.GetValue(s);
if (v != null)
Rec(v);
}
}
}
}

private static void RecurseBind(object appSettings, IServiceCollection services, IConfiguration config)
{
var props = appSettings.GetType()
.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Where(o => !o.PropertyType.IsSealed); // TODO: (low) better check than IsSealed (also unit test)
var props = appSettings.GetType()
.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
; //.Where(o => !o.PropertyType.IsSealed); // TODO: (low) better check than IsSealed (also unit test)

foreach (var prop in props)
{
var instance = prop.GetValue(appSettings);
//config.GetSection(prop.Name).Bind(instance);
services.AddSingleton(instance!.GetType(), instance!);
foreach (var prop in props)
{
var instance = prop.GetValue(appSettings);
if (instance == null)
{
var isNullable = Nullable.GetUnderlyingType(prop.PropertyType) != null;
if (isNullable)
continue;
if (prop.GetCustomAttributes(typeof(System.Runtime.CompilerServices.NullableAttribute), true).Any())
continue;
throw new Exception($"Null value for non-nullable property '{prop.Name}'");
}
//config.GetSection(prop.Name).Bind(instance);

RecurseBind(instance, services, config);
if (instance is System.Collections.IList lst)
{
foreach (var item in lst)
{
services.AddSingleton(item.GetType(), item);
//RecurseBind(item, services, config);
}
}
else
{
services.AddSingleton(instance!.GetType(), instance!);
RecurseBind(instance, services, config);
}

//var asOptions = Microsoft.Extensions.Options.Options.Create(instance);
//services.ConfigureOptions(instance);
//var asOptions = Microsoft.Extensions.Options.Options.Create(instance);
//services.ConfigureOptions(instance);

// Execute validation (if available)
var validatorType = instance.GetType().Assembly.GetTypes()
.Where(t =>
{
var validatorInterface = t.GetInterfaces().SingleOrDefault(o =>
o.IsGenericType && o.GetGenericTypeDefinition() == typeof(Microsoft.Extensions.Options.IValidateOptions<>));
return validatorInterface != null && validatorInterface.GenericTypeArguments.Single() == instance.GetType();
}
).FirstOrDefault();
if (validatorType != null)
{
var validator = Activator.CreateInstance(validatorType);
var m = validatorType.GetMethod("Validate");
var result = (Microsoft.Extensions.Options.ValidateOptionsResult?)m?.Invoke(validator, new object[] { "", instance });
if (result!.Failed)
{
throw new Exception($"{validatorType.Name}: {result.FailureMessage}");
}
}
}
}
// Execute validation (if available)
var validatorType = instance.GetType().Assembly.GetTypes()
.Where(t =>
{
var validatorInterface = t.GetInterfaces().SingleOrDefault(o =>
o.IsGenericType && o.GetGenericTypeDefinition() == typeof(Microsoft.Extensions.Options.IValidateOptions<>));
return validatorInterface != null && validatorInterface.GenericTypeArguments.Single() == instance.GetType();
}
).FirstOrDefault();
if (validatorType != null)
{
var validator = Activator.CreateInstance(validatorType);
var m = validatorType.GetMethod("Validate");
var result = (Microsoft.Extensions.Options.ValidateOptionsResult?)m?.Invoke(validator, new object[] { "", instance });
if (result!.Failed)
{
throw new Exception($"{validatorType.Name}: {result.FailureMessage}");
}
}
}
}
}
}
2 changes: 1 addition & 1 deletion Common/Common.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
78 changes: 41 additions & 37 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,66 @@
<NoWarn>$(NoWarn);NU1507</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AngleSharp" Version="1.1.2" />
<PackageVersion Include="AngleSharp" Version="1.4.0" />
<PackageVersion Include="AutoBogus" Version="2.13.1" />
<PackageVersion Include="AutoBogus.FakeItEasy" Version="2.13.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="Azure.Core" Version="1.44.1" />
<PackageVersion Include="Azure.Data.Tables" Version="12.9.1" />
<PackageVersion Include="Azure.Storage.Queues" Version="12.21.0" />
<PackageVersion Include="Bogus" Version="35.6.1" />
<PackageVersion Include="ClosedXML" Version="0.104.2" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Azure.Core" Version="1.51.1" />
<PackageVersion Include="Azure.Data.Tables" Version="12.11.0" />
<PackageVersion Include="Azure.Storage.Queues" Version="12.25.0" />
<PackageVersion Include="Bogus" Version="35.6.5" />
<PackageVersion Include="ClosedXML" Version="0.105.0" />
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
<PackageVersion Include="DeepCloner" Version="0.10.4" />
<PackageVersion Include="EphemeralMongo7" Version="2.0.0" />
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.68.0.3427" />
<PackageVersion Include="JWMB.AzureTableGenerics" Version="0.0.6-preview.10" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.22.0" />
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.AzureAppServices.HostingStartup" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.23.0" />
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.AzureAppServices.HostingStartup" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.9" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.3" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.0" />
<PackageVersion Include="Microsoft.Azure.WebJobs" Version="3.0.41" />
<PackageVersion Include="Microsoft.Azure.WebJobs.Logging.ApplicationInsights" Version="3.0.41" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
<PackageVersion Include="Microsoft.Data.Analysis" Version="0.22.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.ML" Version="4.0.0" />
<PackageVersion Include="Microsoft.ML.AutoML" Version="0.22.0" />
<PackageVersion Include="Microsoft.ML.FastTree" Version="4.0.0" />
<PackageVersion Include="Microsoft.ML.LightGbm" Version="4.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.Data.Analysis" Version="0.23.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.ML" Version="5.0.0" />
<PackageVersion Include="Microsoft.ML.AutoML" Version="0.23.0" />
<PackageVersion Include="Microsoft.ML.FastTree" Version="5.0.0" />
<PackageVersion Include="Microsoft.ML.LightGbm" Version="5.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Mongo2Go" Version="4.1.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.6.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="NSwag.AspNetCore" Version="14.2.0" />
<PackageVersion Include="NSwag.MSBuild" Version="14.2.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.0.0" />
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
<PackageVersion Include="System.Data.SqlClient" Version="4.9.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.0" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.3" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.3.2" />
</ItemGroup>
</Project>
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Build the runtime image
# podman build -t trainingapi . -f Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
# Expose the port the app runs on
Expand Down Expand Up @@ -33,7 +34,7 @@ WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_HTTP_PORTS=80
# Define the startup command
ENTRYPOINT ["dotnet", "TrainingApi.dll"]
ENTRYPOINT ["dotnet", "TrainingApi.dll", "--environment=Docker"]

# FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

Expand Down
Loading