Initial commit

This commit is contained in:
paragon
2026-05-23 15:33:59 -04:00
commit b05d9bafda
30 changed files with 1538 additions and 0 deletions
@@ -0,0 +1,27 @@
using TriunniaLabs.Arch.Sample.Aspire.AppHost;
var builder = DistributedApplication.CreateBuilder(args);
// Sql Server: One server hosting all databases for an all-in-one setup
var sqlServer = builder.AddSampleSqlServer(); // http://host.docker.internal:51000
// Control Panel: Start here. Run the solution, which will start Aspire. Go to http://localhost:50000 in your browser to access the Control Panel.
// In the Control Panel, create your application and set up the Application Api.
// Copy its encrypted settings to both ApplicationApiSetup.cs and appsettings.json in the Sample Server, then uncomment it here.
// Run the health check on the Application Api from the Control Panel. Repeat for all other Apis, including your app.
builder.AddControlPanel(sqlServer); // http://host.docker.internal:50000
// Arch Apis: Pick which ones you need for your application. Uncomment only once they have been set up in the Control Panel. Application Api is required.
// builder.AddApplicationApi(sqlServer); // http://host.docker.internal:50001
// builder.AddKeysApi(sqlServer); // http://host.docker.internal:50002
// builder.AddTokensApi(sqlServer); // http://host.docker.internal:50003
// builder.AddEmailApi(sqlServer); // http://host.docker.internal:50004
// builder.AddUsersApi(sqlServer); // http://host.docker.internal:50005
// builder.AddCommentsApi(sqlServer); // http://host.docker.internal:50006
// builder.AddEntityApi(sqlServer); // http://host.docker.internal:50007
// builder.AddFilesApi(); // http://host.docker.internal:50008
// Your Application: Uncomment once you have set up your application in the Control Panel. It will run on port 50009.
// builder.AddProject("SampleServer", "..\\..\\TriunniaLabs.Arch.Sample.Server\\TriunniaLabs.Arch.Sample.Server.csproj");
builder.Build().Run();
@@ -0,0 +1,30 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17209;http://localhost:15193",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21057",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22051"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15240",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19042",
"ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18273",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20197"
}
}
}
}
@@ -0,0 +1,41 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class ApplicationApiSetup
{
/// <summary>
/// Adds a resource for the Application Api on port 50001.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddApplicationApi(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var applicationApiDb = sqlServer.AddDatabase("ApplicationApiDb");
var applicationApi = builder.AddContainer("ApplicationApi", "gitea.triunnialabs.com/triunnialabs/arch-application-api", "0.9.8")
.WithEndpoint(50001, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("SqlServer__ConnectionString", "")
.WithEnvironment("SqlServer__ReadOnlyConnectionString", "")
.WithEnvironment("YubiHsm__ConnectorUrl", "")
.WithEnvironment("Guardian__AccessToken", "")
.WithEnvironment("Guardian__BootstrapKeyObjectId", "")
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\ApplicationApi", "/opt/triunnialabs/arch/application-api")
.WithReference(applicationApiDb)
.WaitFor(applicationApiDb);
}
}
}
@@ -0,0 +1,39 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class ComomentsApiSetup
{
/// <summary>
/// Adds a resource for the Comments Api on port 50006.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddCommentsApi(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var commentsApiDb = sqlServer.AddDatabase("CommentsApiDb");
builder.AddContainer("CommentsApi", "gitea.triunnialabs.com/triunnialabs/arch-comments-api", "0.1.0")
.WithEndpoint(50006, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionIterations", "")
.WithEnvironment("SqlServer__ConnectionString", "")
.WithEnvironment("SqlServer__ReadOnlyConnectionString", "")
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\CommentsApi", "/opt/triunnialabs/arch/comments-api")
.WithReference(commentsApiDb)
.WaitFor(commentsApiDb);
}
}
}
@@ -0,0 +1,46 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class EmailApiSetup
{
/// <summary>
/// Adds a resource for the Email Api on port 50004.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddEmailApi(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var emailApiDb = sqlServer.AddDatabase("EmailApiDb");
builder.AddContainer("EmailApi", "gitea.triunnialabs.com/triunnialabs/arch-email-api", "0.2.2")
.WithEndpoint(50004, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionIterations", "")
.WithEnvironment("SqlServer__ConnectionString", "")
.WithEnvironment("SqlServer__ReadOnlyConnectionString", "")
.WithEnvironment("Postmark__ServerToken", "") // Postmark is required for sending emails
.WithEnvironment("Postmark__WebhookAuthUser", "") // Other providers to be added as demand requires (SendGrid, etc.)
.WithEnvironment("Postmark__WebhookAuthPassword", "")
.WithEnvironment("TokensApi__Url", "") // Email Api requires Tokens Api
.WithEnvironment("TokensApi__ApplicationId", "")
.WithEnvironment("TokensApi__ApiKey", "")
.WithEnvironment("TokensApi__EnvelopePassword", "")
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\EmailApi", "/opt/triunnialabs/arch/email-api")
.WithReference(emailApiDb)
.WaitFor(emailApiDb);
}
}
}
@@ -0,0 +1,39 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class EntityApiSetup
{
/// <summary>
/// Adds a resource for the Entity Api on port 50007.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddEntityApi(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var entityApiDb = sqlServer.AddDatabase("EntityApiDb");
builder.AddContainer("EntityApi", "gitea.triunnialabs.com/triunnialabs/arch-entity-api", "0.1.4")
.WithEndpoint(50007, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionIterations", "")
.WithEnvironment("SqlServer__ConnectionString", "")
.WithEnvironment("SqlServer__ReadOnlyConnectionString", "")
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\EntityApi", "/opt/triunnialabs/arch/entity-api")
.WithReference(entityApiDb)
.WaitFor(entityApiDb);
}
}
}
@@ -0,0 +1,33 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class FilesApiSetup
{
/// <summary>
/// Adds a resource for the Files Api on port 50008.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
public static void AddFilesApi(this IDistributedApplicationBuilder builder)
{
builder.AddContainer("FilesApi", "gitea.triunnialabs.com/triunnialabs/arch-files-api", "0.1.3")
.WithEndpoint(50008, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionIterations", "")
.WithEnvironment("FilesApi__DataPath", "")
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\FilesApi", "/opt/triunnialabs/arch/files-api");
}
}
}
@@ -0,0 +1,39 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class KeysApiSetup
{
/// <summary>
/// Adds a resource for the Keys Api on port 50002.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddKeysApi(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var keysApiDb = sqlServer.AddDatabase("KeysApiDb");
builder.AddContainer("KeysApi", "gitea.triunnialabs.com/triunnialabs/arch-keys-api", "0.1.17")
.WithEndpoint(50002, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionIterations", "")
.WithEnvironment("SqlServer__ConnectionString", "")
.WithEnvironment("SqlServer__ReadOnlyConnectionString", "")
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\KeysApi", "/opt/triunnialabs/arch/keys-api")
.WithReference(keysApiDb)
.WaitFor(keysApiDb);
}
}
}
@@ -0,0 +1,39 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class TokensApiSetup
{
/// <summary>
/// Adds a resource for the Tokens Api on port 50003.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddTokensApi(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var tokensApiDb = sqlServer.AddDatabase("TokensApiDb");
builder.AddContainer("TokensApi", "gitea.triunnialabs.com/triunnialabs/arch-tokens-api", "0.3.1")
.WithEndpoint(50003, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionIterations", "")
.WithEnvironment("SqlServer__ConnectionString", "")
.WithEnvironment("SqlServer__ReadOnlyConnectionString", "")
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\TokensApi", "/opt/triunnialabs/arch/tokens-api")
.WithReference(tokensApiDb)
.WaitFor(tokensApiDb);
}
}
}
@@ -0,0 +1,52 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class UsersApiSetup
{
/// <summary>
/// Adds a resource for the Users Api on port 50005.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddUsersApi(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var usersApiDb = sqlServer.AddDatabase("UsersApiDb");
builder.AddContainer("UsersApi", "gitea.triunnialabs.com/triunnialabs/arch-users-api", "0.2.44")
.WithEndpoint(50005, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
// Obtain the values for these fields from the Control Panel
.WithEnvironment("Microservice__Name", "")
.WithEnvironment("Microservice__EnvelopePassword", "")
.WithEnvironment("Microservice__EnableDiagnostics", "true") // Set to false in production deployments
.WithEnvironment("Microservice__Application__Url", "")
.WithEnvironment("Microservice__Application__ApplicationId", "")
.WithEnvironment("Microservice__Application__ApiKey", "")
.WithEnvironment("Microservice__Application__EnvelopePassword", "")
.WithEnvironment("Microservice__ProtectedMemory__HmacSha512HashKey", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionPassword", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionSalt", "")
.WithEnvironment("Microservice__ProtectedMemory__AesEncryptionIterations", "")
.WithEnvironment("SqlServer__ConnectionString", "")
.WithEnvironment("SqlServer__ReadOnlyConnectionString", "")
.WithEnvironment("TokensApi__Url", "") // Users Api requires Tokens Api
.WithEnvironment("TokensApi__ApplicationId", "")
.WithEnvironment("TokensApi__ApiKey", "")
.WithEnvironment("TokensApi__EnvelopePassword", "")
.WithEnvironment("KeysApi__Url", "") // Users Api requires Keys Api
.WithEnvironment("KeysApi__ApplicationId", "")
.WithEnvironment("KeysApi__ApiKey", "")
.WithEnvironment("KeysApi__EnvelopePassword", "")
.WithEnvironment("EmailApi__Url", "") // Users Api requires Email Api
.WithEnvironment("EmailApi__ApplicationId", "")
.WithEnvironment("EmailApi__ApiKey", "")
.WithEnvironment("EmailApi__EnvelopePassword", "")
.WithEnvironment("UsersApi__AuthenticationScheme", "MySampleAuthScheme") // Set to your authentication scheme name (appears in browser)
// Api will write log and other files to this relatiove source folder
.WithBindMount("..\\..\\..\\data\\UsersApi", "/opt/triunnialabs/arch/users-api")
.WithReference(usersApiDb)
.WaitFor(usersApiDb);
}
}
}
@@ -0,0 +1,22 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class ControlPanelSetup
{
/// <summary>
/// Adds a resource for the Control Panel on port 50000.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <param name="sqlServer">Reference to the Sql Server resource.</param>
public static void AddControlPanel(this IDistributedApplicationBuilder builder, IResourceBuilder<SqlServerServerResource> sqlServer)
{
var controlPanelDb = sqlServer.AddDatabase("ControlPanelDb");
builder.AddContainer("ControlPanel", "gitea.triunnialabs.com/triunnialabs/arch-control-panel", "0.0.22")
.WithEndpoint(50000, 8080, isProxied: false, name: "http")
.WithEndpoint("http", e => e.TargetHost = "0.0.0.0")
.WithEnvironment("ControlPanel__DataPath", "/opt/triunnialabs/arch/controlpanel")
.WithBindMount("..\\..\\..\\data\\ControlPanel", "/opt/triunnialabs/arch/controlpanel")
.WithReference(controlPanelDb)
.WaitFor(controlPanelDb);
}
}
}
@@ -0,0 +1,21 @@
namespace TriunniaLabs.Arch.Sample.Aspire.AppHost
{
internal static class SqlServerSetup
{
/// <summary>
/// Adds a resource for a Sql Server database on port 51000.
/// </summary>
/// <param name="builder">Reference to the application builder.</param>
/// <returns>
/// Returns the Sql Server resource.
/// </returns>
public static IResourceBuilder<SqlServerServerResource> AddSampleSqlServer(this IDistributedApplicationBuilder builder)
{
var password = builder.AddParameter("password", "p@ssw0rd");
var sqlServer = builder.AddSqlServer("Sample-SqlServer", password, 51000)
.WithDataVolume("Sample-SqlServer-Data")
.WithEndpoint("tcp", e => { e.IsProxied = false; });
return sqlServer;
}
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Aspire.AppHost.Sdk/13.2.4">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>eaa9faa0-83e2-49df-8f34-af7c4906a005</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.3.5" />
<PackageReference Include="Aspire.Hosting.SqlServer" Version="13.3.5" />
</ItemGroup>
</Project>
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
@@ -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<TBuilder>(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<ServiceDiscoveryOptions>(options =>
// {
// options.AllowedSchemes = ["https"];
// });
return builder;
}
public static TBuilder ConfigureOpenTelemetry<TBuilder>(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<TBuilder>(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<TBuilder>(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;
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.6.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.6.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,5 @@
{
"appHost": {
"path": "TriunniaLabs.Arch.Sample.Aspire.AppHost/TriunniaLabs.Arch.Sample.Aspire.AppHost.csproj"
}
}
@@ -0,0 +1,132 @@
using MessagePack;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text.Encodings.Web;
using TriunniaLabs.Arch.Api.Users.Client;
using TriunniaLabs.Arch.Microservice;
using TriunniaLabs.Memory.Protected;
namespace TriunniaLabs.Arch.Sample.Server
{
/// <summary>
/// An authentication handler that validates a user's token/session.
/// </summary>
public class UserAuthenticationHandler : AuthenticationHandler<UserAuthenticationHandlerOptions>
{
// Services
private readonly ILogger<UserAuthenticationHandler> _logger;
private readonly UsersApiSettings _settings;
private readonly IFormatterResolver _formatterResolver;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="options">Reference to the options class for this handler.</param>
/// <param name="loggerFactory">Reference to the logger factory.</param>
/// <param name="urlEncoder">Reference to the url encoder.</param>
/// <param name="logger">Reference to the logger.</param>
/// <param name="settings">Reference to the users api settings.</param>
/// <param name="formatterResolver">Reference to the MessagePack formatter resolver.</param>
public UserAuthenticationHandler(IOptionsMonitor<UserAuthenticationHandlerOptions> options, ILoggerFactory loggerFactory, UrlEncoder urlEncoder,
ILogger<UserAuthenticationHandler> logger, IOptions<UsersApiSettings> settings,
IFormatterResolver formatterResolver)
: base(options, loggerFactory, urlEncoder)
{
_logger = logger;
_settings = settings.Value;
_formatterResolver = formatterResolver;
}
/// <summary>
/// Handles token authentication.
/// </summary>
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
{
using (_logger.ScopeWithMethod())
try
{
_logger.LogInformation("Authenticate user...");
// Look for the authorization header
if (Request.Headers.ContainsKey("Authorization") == false)
{
_logger.LogInformation("Not applicable: Authorization header not found.");
return await Task.FromResult(AuthenticateResult.NoResult());
}
// Get the authorization header
var header = Request.Headers["Authorization"].ToString().Split(new string[] { " " }, System.StringSplitOptions.RemoveEmptyEntries);
if (header.Length != 2)
{
_logger.LogInformation("Not applicable: Incorrect authentication scheme - header length.");
return await Task.FromResult(AuthenticateResult.NoResult());
}
// Verify that it is an appropriate header
if (header[0] != _settings.AuthenticationScheme)
{
_logger.LogInformation("Not applicable: Incorrect authentication scheme - header name.");
return await Task.FromResult(AuthenticateResult.NoResult());
}
// Get the token
if (string.IsNullOrEmpty(header[1]) == true)
{
_logger.LogInformation("Authentication failed: Invalid token.");
return await Task.FromResult(AuthenticateResult.Fail("Invalid token.")).ConfigureAwait(false);
}
var relayProtector = ProtectedProtocol.RelayMessageProtector<string>(_formatterResolver);
var token = relayProtector.Receive(header[1].Replace("\"", ""));
if (token.IsEmpty)
{
_logger.LogInformation("Authentication failed: Invalid token.");
return await Task.FromResult(AuthenticateResult.Fail("Invalid token.")).ConfigureAwait(false);
}
// Token comes in Relay protected. Need to use Standard protected for ValidateSessionAsync.
var standardProtector = ProtectedProtocol.StandardProtector<string>(_formatterResolver);
token.Reconfigure(standardProtector);
// Validate the session using a fresh-scoped Users Api client so the standard-protected
// auth header does not contaminate the shared _usersApiClient used by relay controllers.
// Relay controllers that call unauthenticated Users Api endpoints (e.g. CheckEmail) must
// not forward an auth header, otherwise the Users Api auth handler fails to relay-decrypt
// the standard-protected token and returns 500.
var ipAddress = Request.HttpContext.Connection?.RemoteIpAddress?.ToString() ?? "";
var protectedIpAddress = ipAddress.AsProtected();
using var validationScope = Context.RequestServices.CreateScope();
var validationClient = validationScope.ServiceProvider.GetRequiredService<IUsersApiClient>();
validationClient.SetAuthorizationHeader(token);
validationClient.SetIpAddressOriginHeader(protectedIpAddress);
var userId = await validationClient.ValidateSessionAsync(token, protectedIpAddress).ConfigureAwait(false);
if (userId is null)
{
_logger.LogInformation("Authentication failed: Unauthorized.");
return await Task.FromResult(AuthenticateResult.Fail("Unauthorized.")).ConfigureAwait(false);
}
else
{
// Token is valid and user is authenticated
_logger.LogInformation("Authentication successful.");
// Generate a principal object for the user
var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId.ToString()) };
var identity = new ClaimsIdentity(claims, nameof(UserAuthenticationHandler));
var principal = new ClaimsPrincipal(identity);
// Create an authentication ticket for the user.
var ticket = new AuthenticationTicket(principal, Scheme.Name);
// Return a success code with the authentication ticket
return await Task.FromResult(AuthenticateResult.Success(ticket)).ConfigureAwait(false);
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
throw;
}
}
}
}
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Authentication;
namespace TriunniaLabs.Arch.Sample.Server
{
/// <summary>
/// An options class for the User Authentication Handler.
/// </summary>
public class UserAuthenticationHandlerOptions : AuthenticationSchemeOptions
{
}
}
@@ -0,0 +1,21 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace TriunniaLabs.Arch.Sample.Server
{
/// <summary>
/// A health check to allow other services to check this service for uptime.
/// </summary>
public class HealthCheck : IHealthCheck
{
/// <summary>
/// Checks the health of the server to determine its condition.
/// </summary>
/// <param name="context">The context under which the health check is run.</param>
/// <param name="cancellationToken">A cancellation to use </param>
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
// Just return healthy as this is only an uptime check for now
return Task.FromResult(HealthCheckResult.Healthy());
}
}
}
@@ -0,0 +1,25 @@
using System.Net;
namespace TriunniaLabs.Arch.Sample.Server
{
/// <summary>
/// A middleware that specifies a dummy ip address for use with the test server.
/// </summary>
public class DummyIpAddressMiddleware : IMiddleware
{
/// <summary>
/// Occurs when this middleware is invoked.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
/// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
/// <returns>A <see cref="Task"/> that represents the execution of this middleware.</returns>
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// Set localhost ip address
context.Connection.RemoteIpAddress = IPAddress.Parse("::1");
// Pass control to the next middleware
await next(context);
}
}
}
@@ -0,0 +1,108 @@
using Microsoft.Extensions.Options;
using System.Reflection;
using TriunniaLabs.Arch.Api.Comments.Client;
using TriunniaLabs.Arch.Api.Email.Client;
using TriunniaLabs.Arch.Api.Entity.Client;
using TriunniaLabs.Arch.Api.Files.Client;
using TriunniaLabs.Arch.Api.Keys.Client;
using TriunniaLabs.Arch.Api.Tokens.Client;
using TriunniaLabs.Arch.Api.Users.Client;
using TriunniaLabs.Arch.Microservice;
using TriunniaLabs.Arch.Relay.Email.Server;
using TriunniaLabs.Arch.Relay.Users.Server;
using TriunniaLabs.Arch.Sample.Server;
using TriunniaLabs.Memory.Protected;
// Start the ASP.NET application builder
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Triunnia Labs: Set up your application to use Arch and add any services it directly depends on
builder.Host.UseTriunniaLabsMicroservice(EnvironmentType.Development);
builder.Services.AddTriunniaLabsMicroservice(builder.Configuration);
builder.Services.AddTriunniaLabsKeysApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
builder.Services.AddTriunniaLabsTokensApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
builder.Services.AddTriunniaLabsEmailApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
builder.Services.AddTriunniaLabsUsersApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
builder.Services.AddTriunniaLabsCommentsApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
builder.Services.AddTriunniaLabsEntityApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
builder.Services.AddTriunniaLabsFilesApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
// Configuration
builder.Services.AddRouting(options => options.LowercaseUrls = true);
// Diagnostics: Add a diagnostics provider so you can debug your configuration from the Control Panel
builder.Services.AddTransient<IDiagnosticConfigurationProvider, DiagnosicConfigurationProvider>();
// Converters: MessagePack is used for serialization within Arch and is part of the Protected Memory system.
// You may need to add some converters for your application to function.
builder.Services.AddKeyedSingleton<IByteArrayConverter<string>, MessagePackUntrustedByteArrayConverter<string>>("MessagePackUntrustedByteArrayConverter<System.String>");
builder.Services.AddKeyedSingleton<IByteArrayConverter<string[]>, MessagePackUntrustedByteArrayConverter<string[]>>("MessagePackUntrustedByteArrayConverter<System.String[]>");
builder.Services.AddKeyedSingleton<IByteArrayConverter<Guid>, MessagePackUntrustedByteArrayConverter<Guid>>("MessagePackUntrustedByteArrayConverter<System.Guid>");
// MessagePack Formatters: Any custom object or data type will need to be registered so that it can be handled by the Protected Memory system.
// All of your app's request/response objects, DTOs, and models should be registered.
builder.Services.AddStandardMessagePackFormatters(new[]
{
typeof(bool),
typeof(bool?),
typeof(char),
typeof(DateTime),
typeof(DateTime?)
});
// Authentication
var settings = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<UsersApiSettings>>().Value;
builder.Services.AddAuthentication(options => options.DefaultScheme = settings.AuthenticationScheme)
.AddScheme<UserAuthenticationHandlerOptions, UserAuthenticationHandler>(settings.AuthenticationScheme, options => { })
.AddScheme<ApplicationAuthenticationHandlerOptions, ApplicationAuthenticationHandler>(ArchConstants.ApplicationAuthenticationScheme, options => { });
// Controllers
builder.Services.AddMvc()
.AddApplicationPart(Assembly.GetExecutingAssembly()).AddControllersAsServices()
.AddTriunniaLabsUsersApiRelay(builder.Services.AddStandardMessagePackFormatters)
.AddTriunniaLabsEmailApiRelay();
// Health Checks
builder.Services.AddHealthChecks().AddCheck<HealthCheck>("health-check");
// Middleware
if (builder.Environment.IsDevelopment())
builder.Services.AddSingleton<DummyIpAddressMiddleware>();
// ------------------------------------------------------------------------------------
var app = builder.Build();
// Triunnia Labs
app.UseTriunniaLabsMicroservice();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMiddleware<DummyIpAddressMiddleware>();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseResponseCompression();
}
// Https and Static Files
app.UseHttpsRedirection();
app.UseStaticFiles();
// Authentication, Routing, and Authorization
app.UseAuthorization();
app.UseRouting();
app.UseAuthorization();
// Controllers and Routing
app.MapControllers();
app.MapHealthChecks("/diagnostics/health-check");
// Run the server
app.Run();
@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"TriunniaLabs.Arch.Sample.Server": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:50009;https://localhost:51009"
}
}
}
@@ -0,0 +1,81 @@
using Microsoft.Extensions.Options;
using TriunniaLabs.Arch.Api.Email.Client;
using TriunniaLabs.Arch.Api.Entity.Client;
using TriunniaLabs.Arch.Api.Files.Client;
using TriunniaLabs.Arch.Api.Users.Client;
using TriunniaLabs.Arch.Microservice;
namespace TriunniaLabs.Arch.Sample.Server
{
/// <summary>
/// A provider for supplying additional configuration information for the purpose of diagnostics.
/// </summary>
public class DiagnosicConfigurationProvider : IDiagnosticConfigurationProvider
{
// Data
private readonly UsersApiSettings _usersApiSettings;
private readonly EmailApiSettings _emailApiSettings;
private readonly EntityApiSettings _entityApiSettings;
private readonly FilesApiSettings _filesApiSettings;
// Services
private readonly ILogger<DiagnosicConfigurationProvider> _logger;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="usersApiSettings">Reference to the Users Api setttings.</param>
/// <param name="emailApiSettings">Reference to the Email Api setttings.</param>
/// <param name="entityApiSettings">Reference to the Entity Api setttings.</param>
/// <param name="filesApiSettings">Reference to the Files Api setttings.</param>
/// <param name="logger">Reference to the logger.</param>
public DiagnosicConfigurationProvider(IOptions<UsersApiSettings> usersApiSettings, IOptions<EmailApiSettings> emailApiSettings,
IOptions<EntityApiSettings> entityApiSettings, IOptions<FilesApiSettings> filesApiSettings,
ILogger<DiagnosicConfigurationProvider> logger)
{
_usersApiSettings = usersApiSettings?.Value ?? throw new ArgumentNullException(nameof(usersApiSettings));
_emailApiSettings = emailApiSettings?.Value ?? throw new ArgumentNullException(nameof(emailApiSettings));
_entityApiSettings = entityApiSettings?.Value ?? throw new ArgumentNullException(nameof(entityApiSettings));
_filesApiSettings = filesApiSettings?.Value ?? throw new ArgumentNullException(nameof(filesApiSettings));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Supplies custom settings used by the microservice to the diagnostic report.
/// Call <see cref="DiagnosticSettingsResponse.AddSetting{T}(string, Memory.Protected.Protected{T}?)" /> to add each setting.
/// </summary>
/// <param name="report">The diagnostic report to configure.</param>
public void SupplySettings(DiagnosticSettingsResponse report)
{
try
{
// Add all additional protected configuration settings for your application here.
// They will be reported to the Control Panel via its Diagnostics feature when EnableDiagnostics is true.
report.AddSetting("UsersApi.ApplicationId", _usersApiSettings.ApplicationId);
report.AddSetting("UsersApi.Url", _usersApiSettings.Url);
report.AddSetting("UsersApi.ApiKey", _usersApiSettings.ApiKey);
report.AddSetting("UsersApi.EnvelopePassword", _usersApiSettings.EnvelopePassword);
report.AddSetting("EmailApi.ApplicationId", _emailApiSettings.ApplicationId);
report.AddSetting("EmailApi.Url", _emailApiSettings.Url);
report.AddSetting("EmailApi.ApiKey", _emailApiSettings.ApiKey);
report.AddSetting("EmailApi.EnvelopePassword", _emailApiSettings.EnvelopePassword);
report.AddSetting("EntityApi.ApplicationId", _entityApiSettings.ApplicationId);
report.AddSetting("EntityApi.Url", _entityApiSettings.Url);
report.AddSetting("EntityApi.ApiKey", _entityApiSettings.ApiKey);
report.AddSetting("EntityApi.EnvelopePassword", _entityApiSettings.EnvelopePassword);
report.AddSetting("FilesApi.ApplicationId", _filesApiSettings.ApplicationId);
report.AddSetting("FilesApi.Url", _filesApiSettings.Url);
report.AddSetting("FilesApi.ApiKey", _filesApiSettings.ApiKey);
report.AddSetting("FilesApi.EnvelopePassword", _filesApiSettings.EnvelopePassword);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to supply settings for configuration diagnostic.");
}
}
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TriunniaLabs.Arch.Api.Comments.Client" Version="0.1.0" />
<PackageReference Include="TriunniaLabs.Arch.Api.Email.Client" Version="0.2.2" />
<PackageReference Include="TriunniaLabs.Arch.Api.Entity.Client" Version="0.1.4" />
<PackageReference Include="TriunniaLabs.Arch.Api.Files.Client" Version="0.1.3" />
<PackageReference Include="TriunniaLabs.Arch.Api.Keys.Client" Version="0.1.17" />
<PackageReference Include="TriunniaLabs.Arch.Api.Tokens.Client" Version="0.3.1" />
<PackageReference Include="TriunniaLabs.Arch.Api.Users.Client" Version="0.2.44" />
<PackageReference Include="TriunniaLabs.Arch.Microservice" Version="0.9.8" />
<PackageReference Include="TriunniaLabs.Arch.Relay.Email.Server" Version="0.2.2" />
<PackageReference Include="TriunniaLabs.Arch.Relay.Users.Server" Version="0.2.44" />
</ItemGroup>
</Project>
@@ -0,0 +1,98 @@
{
"Microservice": {
"Name": "",
"EnvelopePassword": "",
"EnableDiagnostics": true,
"Application": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": ""
},
"ProtectedMemory": {
"HmacSha512HashKey": "",
"AesEncryptionPassword": "",
"AesEncryptionSalt": "",
"AesEncryptionIterations": ""
}
},
"KeysApi": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": ""
},
"TokensApi": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": ""
},
"EmailApi": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": ""
},
"UsersApi": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": "",
"AuthenticationScheme": "MySampleAuthScheme"
},
"CommentsApi": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": ""
},
"EntityApi": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": ""
},
"FilesApi": {
"Url": "",
"ApplicationId": "",
"ApiKey": "",
"EnvelopePassword": ""
},
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
}
},
{
"Name": "File",
"Args": {
"path": "Logs/log-.txt",
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
"rollingInterval": "Day",
"retainedFileCountLimit": 7
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
}
}
+5
View File
@@ -0,0 +1,5 @@
<Solution>
<Project Path="TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/TriunniaLabs.Arch.Sample.Aspire.AppHost.csproj" Id="6cb44d36-37cb-47cb-b6dc-e3f1900b3eb5" />
<Project Path="TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults.csproj" />
<Project Path="TriunniaLabs.Arch.Sample.Server/TriunniaLabs.Arch.Sample.Server.csproj" />
</Solution>