From f83823613130ae08929c9716f0a634a7518df133 Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Sat, 14 Feb 2026 22:04:32 +0100 Subject: [PATCH 01/10] feat: Init Aspire --- .aspire/settings.json | 3 + Config/Properties/launchSettings.json | 12 ++ NuGet.config | 12 ++ Valour.AppHost/AppHost.cs | 19 +++ Valour.AppHost/Properties/launchSettings.json | 31 +++++ Valour.AppHost/Valour.AppHost.csproj | 20 +++ Valour.AppHost/appsettings.Development.json | 8 ++ Valour.ServiceDefaults/Extensions.cs | 127 ++++++++++++++++++ .../Valour.ServiceDefaults.csproj | 22 +++ Valour.sln | 104 ++++++++++++++ Valour/Server/Program.cs | 5 +- Valour/Server/Valour.Server.csproj | 2 + 12 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 .aspire/settings.json create mode 100644 Config/Properties/launchSettings.json create mode 100644 NuGet.config create mode 100644 Valour.AppHost/AppHost.cs create mode 100644 Valour.AppHost/Properties/launchSettings.json create mode 100644 Valour.AppHost/Valour.AppHost.csproj create mode 100644 Valour.AppHost/appsettings.Development.json create mode 100644 Valour.ServiceDefaults/Extensions.cs create mode 100644 Valour.ServiceDefaults/Valour.ServiceDefaults.csproj diff --git a/.aspire/settings.json b/.aspire/settings.json new file mode 100644 index 000000000..dbd58eb28 --- /dev/null +++ b/.aspire/settings.json @@ -0,0 +1,3 @@ +{ + "appHostPath": "../Valour.AppHost/Valour.AppHost.csproj" +} \ No newline at end of file diff --git a/Config/Properties/launchSettings.json b/Config/Properties/launchSettings.json new file mode 100644 index 000000000..08bab548e --- /dev/null +++ b/Config/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Valour.Config": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:51199;http://localhost:51200" + } + } +} \ No newline at end of file diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 000000000..993d13d07 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Valour.AppHost/AppHost.cs b/Valour.AppHost/AppHost.cs new file mode 100644 index 000000000..f834c6b8b --- /dev/null +++ b/Valour.AppHost/AppHost.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using Projects; + +var builder = DistributedApplication.CreateBuilder(args); + +var postgres = builder.AddPostgres("valourgres") + .WithVolume("valour-postgres-data").WithPgAdmin(); + +var valourDb = postgres.AddDatabase("valourdb"); + +var redis = builder.AddRedis("redis").WithDataVolume("redis-data"); + +var valourServer = builder.AddProject("valour-server") + .WaitFor(valourDb) + .WaitFor(redis) + .WithReference(valourDb) + .WithReference(redis); + +builder.Build().Run(); diff --git a/Valour.AppHost/Properties/launchSettings.json b/Valour.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..029bfe856 --- /dev/null +++ b/Valour.AppHost/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17166;http://localhost:15103", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21027", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23074", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22220" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15103", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19169", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18170", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20155" + } + } + } +} diff --git a/Valour.AppHost/Valour.AppHost.csproj b/Valour.AppHost/Valour.AppHost.csproj new file mode 100644 index 000000000..cfedfea08 --- /dev/null +++ b/Valour.AppHost/Valour.AppHost.csproj @@ -0,0 +1,20 @@ + + + + + + + + + + + + + Exe + net10.0 + enable + enable + 52e455ab-3d66-4aa9-ab01-ccf4ee55a916 + + + diff --git a/Valour.AppHost/appsettings.Development.json b/Valour.AppHost/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/Valour.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Valour.ServiceDefaults/Extensions.cs b/Valour.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..b72c8753c --- /dev/null +++ b/Valour.ServiceDefaults/Extensions.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/Valour.ServiceDefaults/Valour.ServiceDefaults.csproj b/Valour.ServiceDefaults/Valour.ServiceDefaults.csproj new file mode 100644 index 000000000..eeb71e38f --- /dev/null +++ b/Valour.ServiceDefaults/Valour.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/Valour.sln b/Valour.sln index d495528fc..7b01a2834 100644 --- a/Valour.sln +++ b/Valour.sln @@ -26,48 +26,152 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Valour.Config", "Config\Val EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Valour.Client.Maui", "Valour\Client.Maui\Valour.Client.Maui.csproj", "{C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Valour.AppHost", "Valour.AppHost\Valour.AppHost.csproj", "{6B942C37-FE38-4DF7-BC0E-451C34CC799C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Valour.ServiceDefaults", "Valour.ServiceDefaults\Valour.ServiceDefaults.csproj", "{B35FB75E-5242-441D-BCBC-B769A5F78808}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Debug|x64.ActiveCfg = Debug|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Debug|x64.Build.0 = Debug|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Debug|x86.ActiveCfg = Debug|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Debug|x86.Build.0 = Debug|Any CPU {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Release|Any CPU.ActiveCfg = Release|Any CPU {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Release|Any CPU.Build.0 = Release|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Release|x64.ActiveCfg = Release|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Release|x64.Build.0 = Release|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Release|x86.ActiveCfg = Release|Any CPU + {17DA107C-C64F-4AE9-9FDA-EF6532139659}.Release|x86.Build.0 = Release|Any CPU {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Debug|x64.ActiveCfg = Debug|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Debug|x64.Build.0 = Debug|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Debug|x86.ActiveCfg = Debug|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Debug|x86.Build.0 = Debug|Any CPU {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Release|Any CPU.Build.0 = Release|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Release|x64.ActiveCfg = Release|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Release|x64.Build.0 = Release|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Release|x86.ActiveCfg = Release|Any CPU + {D3B85CB7-3EC8-4180-8F5C-73C09C2A1D7F}.Release|x86.Build.0 = Release|Any CPU {9CABBEEF-DE8F-4374-9818-A55B62503004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9CABBEEF-DE8F-4374-9818-A55B62503004}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Debug|x64.ActiveCfg = Debug|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Debug|x64.Build.0 = Debug|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Debug|x86.ActiveCfg = Debug|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Debug|x86.Build.0 = Debug|Any CPU {9CABBEEF-DE8F-4374-9818-A55B62503004}.Release|Any CPU.ActiveCfg = Release|Any CPU {9CABBEEF-DE8F-4374-9818-A55B62503004}.Release|Any CPU.Build.0 = Release|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Release|x64.ActiveCfg = Release|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Release|x64.Build.0 = Release|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Release|x86.ActiveCfg = Release|Any CPU + {9CABBEEF-DE8F-4374-9818-A55B62503004}.Release|x86.Build.0 = Release|Any CPU {D5C0CB27-80FC-415C-85DC-E800207166C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D5C0CB27-80FC-415C-85DC-E800207166C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Debug|x64.Build.0 = Debug|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Debug|x86.Build.0 = Debug|Any CPU {D5C0CB27-80FC-415C-85DC-E800207166C8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5C0CB27-80FC-415C-85DC-E800207166C8}.Release|Any CPU.Build.0 = Release|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Release|x64.ActiveCfg = Release|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Release|x64.Build.0 = Release|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Release|x86.ActiveCfg = Release|Any CPU + {D5C0CB27-80FC-415C-85DC-E800207166C8}.Release|x86.Build.0 = Release|Any CPU {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Debug|x64.ActiveCfg = Debug|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Debug|x64.Build.0 = Debug|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Debug|x86.ActiveCfg = Debug|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Debug|x86.Build.0 = Debug|Any CPU {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Release|Any CPU.Build.0 = Release|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Release|x64.ActiveCfg = Release|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Release|x64.Build.0 = Release|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Release|x86.ActiveCfg = Release|Any CPU + {0A2EBA6C-8804-4B28-9697-24DF8E744EE6}.Release|x86.Build.0 = Release|Any CPU {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Debug|x64.ActiveCfg = Debug|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Debug|x64.Build.0 = Debug|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Debug|x86.ActiveCfg = Debug|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Debug|x86.Build.0 = Debug|Any CPU {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Release|Any CPU.Build.0 = Release|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Release|x64.ActiveCfg = Release|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Release|x64.Build.0 = Release|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Release|x86.ActiveCfg = Release|Any CPU + {B192A998-9AED-40F7-BF3D-0BF7D060BEAD}.Release|x86.Build.0 = Release|Any CPU {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Debug|x64.ActiveCfg = Debug|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Debug|x64.Build.0 = Debug|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Debug|x86.ActiveCfg = Debug|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Debug|x86.Build.0 = Debug|Any CPU {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Release|Any CPU.Build.0 = Release|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Release|x64.ActiveCfg = Release|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Release|x64.Build.0 = Release|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Release|x86.ActiveCfg = Release|Any CPU + {E0ECFCA0-767D-4E71-B54D-66BB4C9BEEC9}.Release|x86.Build.0 = Release|Any CPU {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Debug|x64.ActiveCfg = Debug|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Debug|x64.Build.0 = Debug|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Debug|x86.ActiveCfg = Debug|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Debug|x86.Build.0 = Debug|Any CPU {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Release|Any CPU.Build.0 = Release|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Release|x64.ActiveCfg = Release|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Release|x64.Build.0 = Release|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Release|x86.ActiveCfg = Release|Any CPU + {6F86BE9F-D601-4330-81E2-C0B6C257B593}.Release|x86.Build.0 = Release|Any CPU {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Debug|x64.ActiveCfg = Debug|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Debug|x64.Build.0 = Debug|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Debug|x86.ActiveCfg = Debug|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Debug|x86.Build.0 = Debug|Any CPU {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Release|Any CPU.Build.0 = Release|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Release|x64.ActiveCfg = Release|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Release|x64.Build.0 = Release|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Release|x86.ActiveCfg = Release|Any CPU + {C1A6427C-433D-40D0-9C6A-F3BC2F3B46F8}.Release|x86.Build.0 = Release|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Debug|x64.ActiveCfg = Debug|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Debug|x64.Build.0 = Debug|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Debug|x86.ActiveCfg = Debug|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Debug|x86.Build.0 = Debug|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Release|Any CPU.Build.0 = Release|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Release|x64.ActiveCfg = Release|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Release|x64.Build.0 = Release|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Release|x86.ActiveCfg = Release|Any CPU + {6B942C37-FE38-4DF7-BC0E-451C34CC799C}.Release|x86.Build.0 = Release|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Debug|x64.ActiveCfg = Debug|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Debug|x64.Build.0 = Debug|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Debug|x86.ActiveCfg = Debug|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Debug|x86.Build.0 = Debug|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Release|Any CPU.Build.0 = Release|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Release|x64.ActiveCfg = Release|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Release|x64.Build.0 = Release|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Release|x86.ActiveCfg = Release|Any CPU + {B35FB75E-5242-441D-BCBC-B769A5F78808}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Valour/Server/Program.cs b/Valour/Server/Program.cs index d7489a13c..6a6f2da11 100644 --- a/Valour/Server/Program.cs +++ b/Valour/Server/Program.cs @@ -76,7 +76,10 @@ public static async Task Main(string[] args) x.ServerName = NodeConfig.Instance.Name; }); } - +#if DEBUG //only include during debug until aspire is considered for production + // Aspire specific + builder.AddServiceDefaults(); +#endif // Set up services ConfigureServices(builder); diff --git a/Valour/Server/Valour.Server.csproj b/Valour/Server/Valour.Server.csproj index 0581fcfe8..5d6afb283 100644 --- a/Valour/Server/Valour.Server.csproj +++ b/Valour/Server/Valour.Server.csproj @@ -15,6 +15,7 @@ + @@ -47,6 +48,7 @@ + From 447641289a84ebe23809ef327392f707b741a083 Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Sat, 14 Feb 2026 22:11:08 +0100 Subject: [PATCH 02/10] fix: fixed compile error --- Valour/Client.Maui/Valour.Client.Maui.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Valour/Client.Maui/Valour.Client.Maui.csproj b/Valour/Client.Maui/Valour.Client.Maui.csproj index 6ce3c1fb0..9ae466b93 100644 --- a/Valour/Client.Maui/Valour.Client.Maui.csproj +++ b/Valour/Client.Maui/Valour.Client.Maui.csproj @@ -77,6 +77,10 @@ + + + + From 7c8c34f216dcc01db8ebad5f376647dcd2ad059c Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Sat, 14 Feb 2026 22:15:35 +0100 Subject: [PATCH 03/10] fix: compile error --- Valour/Client.Maui/Valour.Client.Maui.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Valour/Client.Maui/Valour.Client.Maui.csproj b/Valour/Client.Maui/Valour.Client.Maui.csproj index 9ae466b93..9ed3c2849 100644 --- a/Valour/Client.Maui/Valour.Client.Maui.csproj +++ b/Valour/Client.Maui/Valour.Client.Maui.csproj @@ -39,7 +39,7 @@ gg.valour.app - 0.4.0.1 + 0.4.0 13 From d5c0fef39562054af4a5398c12353534a3a634d9 Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Sun, 15 Feb 2026 00:41:27 +0100 Subject: [PATCH 04/10] fix: fixed datavolume --- Valour.AppHost/AppHost.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Valour.AppHost/AppHost.cs b/Valour.AppHost/AppHost.cs index f834c6b8b..81f33385b 100644 --- a/Valour.AppHost/AppHost.cs +++ b/Valour.AppHost/AppHost.cs @@ -1,10 +1,9 @@ -using Microsoft.Extensions.Options; using Projects; var builder = DistributedApplication.CreateBuilder(args); var postgres = builder.AddPostgres("valourgres") - .WithVolume("valour-postgres-data").WithPgAdmin(); + .WithDataVolume("valour-postgres-data").WithPgAdmin(); var valourDb = postgres.AddDatabase("valourdb"); From 6006af86225851b8b5a67b87be62ea89e239674c Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Sun, 15 Feb 2026 00:54:50 +0100 Subject: [PATCH 05/10] chore: added dynamic connection string from aspire --- Valour/Database/Context/ValourDB.cs | 8 +++++++- Valour/Server/Program.cs | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Valour/Database/Context/ValourDB.cs b/Valour/Database/Context/ValourDB.cs index 29df5e016..97dbc4fb6 100644 --- a/Valour/Database/Context/ValourDB.cs +++ b/Valour/Database/Context/ValourDB.cs @@ -237,7 +237,13 @@ public ValourDb(DbContextOptions options) : base(options) protected override void OnConfiguring(DbContextOptionsBuilder options) { options.ConfigureWarnings(w => w.Ignore(RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables)); - options.UseNpgsql(ConnectionString).UseExceptionProcessor(); + + if (!options.IsConfigured) + { + options.UseNpgsql(ConnectionString); + } + + options.UseExceptionProcessor(); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/Valour/Server/Program.cs b/Valour/Server/Program.cs index 6a6f2da11..c9b4d5071 100644 --- a/Valour/Server/Program.cs +++ b/Valour/Server/Program.cs @@ -271,8 +271,13 @@ public static void ConfigureServices(WebApplicationBuilder builder) options.MemoryBufferThreshold = 20480000; options.MultipartBodyLengthLimit = 20480000; }); +#if DEBUG + var cnn = Environment.GetEnvironmentVariable("ConnectionStrings__valourdb") ?? throw new Exception("Connection unable to retrive connection string from aspire") ; // TODO: When moving to production this should be derived via dependency injection instead (https://aspire.dev/integrations/databases/postgres/postgres-get-started/?lang=csharp#set-up-client-projects) +#else + var cnn = ValourDb.ConnectionString; +#endif - services.AddDbContext(options => { options.UseNpgsql(ValourDb.ConnectionString); }, ServiceLifetime.Scoped); + services.AddDbContext(options => { options.UseNpgsql(cnn); }, ServiceLifetime.Scoped); // Apply migrations if flag is set //if (Environment.GetEnvironmentVariable("APPLY_MIGRATIONS") == "true") From 76a0990c367f95c63c11fd59c05f2eacee9083f8 Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Wed, 18 Feb 2026 23:23:30 +0100 Subject: [PATCH 06/10] fix: resolved an issue with automatic retries failing (disabled for now) + code formating --- .aspire/settings.json | 5 ++++- Valour.AppHost/AppHost.cs | 4 +++- Valour/Database/Context/ValourDB.cs | 10 ++++------ Valour/Server/Valour.Server.csproj | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.aspire/settings.json b/.aspire/settings.json index dbd58eb28..543eb1bdf 100644 --- a/.aspire/settings.json +++ b/.aspire/settings.json @@ -1,3 +1,6 @@ { - "appHostPath": "../Valour.AppHost/Valour.AppHost.csproj" + "appHostPath": "../Valour.AppHost/Valour.AppHost.csproj", + "features": { + "execCommandEnabled": "true" + } } \ No newline at end of file diff --git a/Valour.AppHost/AppHost.cs b/Valour.AppHost/AppHost.cs index 81f33385b..7167e3507 100644 --- a/Valour.AppHost/AppHost.cs +++ b/Valour.AppHost/AppHost.cs @@ -3,7 +3,9 @@ var builder = DistributedApplication.CreateBuilder(args); var postgres = builder.AddPostgres("valourgres") - .WithDataVolume("valour-postgres-data").WithPgAdmin(); + .WithImageTag("16.12") // pin version + .WithDataVolume("valour-postgres-data") + .WithPgAdmin(); var valourDb = postgres.AddDatabase("valourdb"); diff --git a/Valour/Database/Context/ValourDB.cs b/Valour/Database/Context/ValourDB.cs index 97dbc4fb6..bbccbd3a7 100644 --- a/Valour/Database/Context/ValourDB.cs +++ b/Valour/Database/Context/ValourDB.cs @@ -236,13 +236,11 @@ public ValourDb(DbContextOptions options) : base(options) protected override void OnConfiguring(DbContextOptionsBuilder options) { + if (options.IsConfigured) + return; + options.ConfigureWarnings(w => w.Ignore(RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables)); - - if (!options.IsConfigured) - { - options.UseNpgsql(ConnectionString); - } - + options.UseNpgsql(ConnectionString); options.UseExceptionProcessor(); } diff --git a/Valour/Server/Valour.Server.csproj b/Valour/Server/Valour.Server.csproj index 5d6afb283..8e88fa495 100644 --- a/Valour/Server/Valour.Server.csproj +++ b/Valour/Server/Valour.Server.csproj @@ -15,6 +15,7 @@ + From 4be83ffb179d85332877c38cda3f33757cb71cdf Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Wed, 18 Feb 2026 23:26:08 +0100 Subject: [PATCH 07/10] feat: added more aspire specific items --- Valour/Server/Program.cs | 79 +++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/Valour/Server/Program.cs b/Valour/Server/Program.cs index c9b4d5071..c0ecebc5d 100644 --- a/Valour/Server/Program.cs +++ b/Valour/Server/Program.cs @@ -6,6 +6,8 @@ using System.Text.Json; using Amazon; using CloudFlare.Client; +using EntityFramework.Exceptions.PostgreSQL; +using Microsoft.EntityFrameworkCore.Diagnostics; using StackExchange.Redis; using Valour.Server.API; using Valour.Server.Cdn; @@ -43,8 +45,19 @@ public static async Task Main(string[] args) // Load configs ConfigLoader.LoadConfigs(); - // Initialize Email Manager - EmailManager.SetupClient(); +#if DEBUG + NodeConfig.Instance ??= new NodeConfig + { + Name = "dev-node", + Location = "localhost", + LogInfo = true + }; + // Aspire specific + builder.AddServiceDefaults(); +#endif + + // Initialize Email Manager + EmailManager.SetupClient(); // Initialize Firebase for FCM push notifications if (!string.IsNullOrWhiteSpace(NotificationsConfig.Current?.FirebaseCredentialPath)) @@ -76,16 +89,18 @@ public static async Task Main(string[] args) x.ServerName = NodeConfig.Instance.Name; }); } -#if DEBUG //only include during debug until aspire is considered for production - // Aspire specific - builder.AddServiceDefaults(); -#endif + // Set up services ConfigureServices(builder); // Build web app var app = builder.Build(); + // Initialize database schema + using var scope = app.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); + // Configure application ConfigureApp(app); @@ -97,11 +112,11 @@ public static async Task Main(string[] args) UploadApi.AddRoutes(app); ProxyApi.AddRoutes(app); - // Add API routes - BaseAPI.AddRoutes(app); - EmbedAPI.AddRoutes(app); - OauthAPI.AddRoutes(app); - VoiceSignallingApi.AddRoutes(app); + // Add API routes + BaseAPI.AddRoutes(app); + EmbedAPI.AddRoutes(app); + OauthAPI.AddRoutes(app); + VoiceSignallingApi.AddRoutes(app); // s3 (r2) setup @@ -272,28 +287,24 @@ public static void ConfigureServices(WebApplicationBuilder builder) options.MultipartBodyLengthLimit = 20480000; }); #if DEBUG - var cnn = Environment.GetEnvironmentVariable("ConnectionStrings__valourdb") ?? throw new Exception("Connection unable to retrive connection string from aspire") ; // TODO: When moving to production this should be derived via dependency injection instead (https://aspire.dev/integrations/databases/postgres/postgres-get-started/?lang=csharp#set-up-client-projects) + + builder.AddNpgsqlDbContext("valourdb", configureDbContextOptions: options => + { + options.ConfigureWarnings(w => w.Ignore(RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables)); + options.UseExceptionProcessor(); + // Disable retrying execution strategy to allow manual transactions + options.UseNpgsql(npgsqlOptions => npgsqlOptions.EnableRetryOnFailure(0)); + }); + builder.AddRedisClient("redis"); #else - var cnn = ValourDb.ConnectionString; + services.AddDbContext(options => { options.UseNpgsql(ValourDb.ConnectionString); }, ServiceLifetime.Scoped); + Console.WriteLine("Connecting to redis with connection string: " + RedisConfig.Current.ConnectionString?.Split(",")[0]); + services.AddSingleton( + ConnectionMultiplexer.Connect(RedisConfig.Current.ConnectionString)); #endif - services.AddDbContext(options => { options.UseNpgsql(cnn); }, ServiceLifetime.Scoped); - - // Apply migrations if flag is set - //if (Environment.GetEnvironmentVariable("APPLY_MIGRATIONS") == "true") - //{ - using var scope = services.BuildServiceProvider().CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.Migrate(); - //} - - Console.WriteLine("Connecting to redis with connection string: " + RedisConfig.Current.ConnectionString?.Split(",")[0]); - - services.AddSingleton( - ConnectionMultiplexer.Connect(RedisConfig.Current.ConnectionString)); - - // This probably needs to be customized further but the documentation changed - services.AddAuthentication().AddCookie(CookieAuthenticationDefaults.AuthenticationScheme); + // This probably needs to be customized further but the documentation changed + services.AddAuthentication().AddCookie(CookieAuthenticationDefaults.AuthenticationScheme); services.AddControllersWithViews().AddJsonOptions(options => @@ -337,10 +348,10 @@ public static void ConfigureServices(WebApplicationBuilder builder) services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.TryAddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.TryAddSingleton(); services.AddScoped(); From 6c23c47b6fadf50fd80aad57e2373d4ea9557a7c Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Wed, 18 Feb 2026 23:46:13 +0100 Subject: [PATCH 08/10] chore: added redis insight --- Valour.AppHost/AppHost.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Valour.AppHost/AppHost.cs b/Valour.AppHost/AppHost.cs index 7167e3507..d17910fbb 100644 --- a/Valour.AppHost/AppHost.cs +++ b/Valour.AppHost/AppHost.cs @@ -9,7 +9,9 @@ var valourDb = postgres.AddDatabase("valourdb"); -var redis = builder.AddRedis("redis").WithDataVolume("redis-data"); +var redis = builder.AddRedis("redis") + .WithDataVolume("redis-data") + .WithRedisInsight(); var valourServer = builder.AddProject("valour-server") .WaitFor(valourDb) From 8817c741e8936def670a081dacc858c6463a3a39 Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Thu, 5 Mar 2026 00:01:42 +0100 Subject: [PATCH 09/10] fix: fixed static api endpoint --- Valour/Client.Blazor/Program.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Valour/Client.Blazor/Program.cs b/Valour/Client.Blazor/Program.cs index 4302abc30..b17100f24 100644 --- a/Valour/Client.Blazor/Program.cs +++ b/Valour/Client.Blazor/Program.cs @@ -17,20 +17,25 @@ public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("app"); - + builder.UseSentry(options => { options.Dsn = "https://6cfc20b598b8831b69f8a30629325213@o4510867505479680.ingest.us.sentry.io/4510869629435904"; options.MinimumEventLevel = LogLevel.Error; options.SetBeforeSend((e, _) => SentryGate.IsEnabled ? e : null); }); - + builder.Services.AddSingleton(); builder.Services.AddSingleton(); - // Default to the API host. Web deploys can override at runtime via valour-runtime-config.js. - builder.Services.AddValourClientServices("https://api.valour.gg"); - - var host = builder.Build(); + +#if DEBUG + + var apiBaseAddress = builder.HostEnvironment.BaseAddress; + builder.Services.AddValourClientServices(apiBaseAddress); +#else + builder.Services.AddValourClientServices("https://api.valour.gg"); +#endif + var host = builder.Build(); await host.RunAsync(); } } From 10364b24866db219e62e7c4171fcd69e7bc62469 Mon Sep 17 00:00:00 2001 From: "Cato K. Myhre" Date: Thu, 5 Mar 2026 00:02:27 +0100 Subject: [PATCH 10/10] chore: updated to latest aspire package versions --- Valour.AppHost/Valour.AppHost.csproj | 6 +++--- Valour/Server/Valour.Server.csproj | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Valour.AppHost/Valour.AppHost.csproj b/Valour.AppHost/Valour.AppHost.csproj index cfedfea08..aadc02a4d 100644 --- a/Valour.AppHost/Valour.AppHost.csproj +++ b/Valour.AppHost/Valour.AppHost.csproj @@ -1,12 +1,12 @@ - + - - + + diff --git a/Valour/Server/Valour.Server.csproj b/Valour/Server/Valour.Server.csproj index 79727bd6c..1f87b700e 100644 --- a/Valour/Server/Valour.Server.csproj +++ b/Valour/Server/Valour.Server.csproj @@ -15,8 +15,8 @@ - - + +