From b05d9bafda74438e66f3aea47227cd86dc770ec2 Mon Sep 17 00:00:00 2001 From: paragon Date: Sat, 23 May 2026 15:33:59 -0400 Subject: [PATCH] Initial commit --- .gitignore | 399 ++++++++++++++++++ readme.md | 6 + .../AppHost.cs | 27 ++ .../Properties/launchSettings.json | 30 ++ .../Setup/Apis/ApplicationApiSetup.cs | 41 ++ .../Setup/Apis/CommentsApiSetup.cs | 39 ++ .../Setup/Apis/EmailApiSetup.cs | 46 ++ .../Setup/Apis/EntityApiSetup.cs | 39 ++ .../Setup/Apis/FilesApiSetup.cs | 33 ++ .../Setup/Apis/KeysApiSetup.cs | 39 ++ .../Setup/Apis/TokensApiSetup.cs | 39 ++ .../Setup/Apis/UsersApiSetup.cs | 52 +++ .../Setup/ControlPanel/ControlPanelSetup.cs | 22 + .../Setup/Databases/SqlServerSetup.cs | 21 + ...nniaLabs.Arch.Sample.Aspire.AppHost.csproj | 16 + .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../Extensions.cs | 127 ++++++ ....Arch.Sample.Aspire.ServiceDefaults.csproj | 22 + .../aspire.config.json | 5 + .../Handlers/UserAuthenticationHandler.cs | 132 ++++++ .../UserAuthenticationHandlerOptions.cs | 11 + .../HealthChecks/HealthCheck.cs | 21 + .../Middleware/DummyIpAddressMiddleware.cs | 25 ++ .../Program.cs | 108 +++++ .../Properties/launchSettings.json | 15 + .../DiagnosticsConfigurationProvider.cs | 81 ++++ .../TriunniaLabs.Arch.Sample.Server.csproj | 22 + .../appsettings.json | 98 +++++ src/TriunniaLabs.Arch.Sample.slnx | 5 + 30 files changed, 1538 insertions(+) create mode 100644 .gitignore create mode 100644 readme.md create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/AppHost.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Properties/launchSettings.json create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/ApplicationApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/CommentsApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EmailApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EntityApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/FilesApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/KeysApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/TokensApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/UsersApiSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/ControlPanel/ControlPanelSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Databases/SqlServerSetup.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/TriunniaLabs.Arch.Sample.Aspire.AppHost.csproj create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.Development.json create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.json create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/Extensions.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults.csproj create mode 100644 src/TriunniaLabs.Arch.Sample.Aspire/aspire.config.json create mode 100644 src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandler.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandlerOptions.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Server/HealthChecks/HealthCheck.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Server/Middleware/DummyIpAddressMiddleware.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Server/Program.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Server/Properties/launchSettings.json create mode 100644 src/TriunniaLabs.Arch.Sample.Server/Providers/DiagnosticsConfigurationProvider.cs create mode 100644 src/TriunniaLabs.Arch.Sample.Server/TriunniaLabs.Arch.Sample.Server.csproj create mode 100644 src/TriunniaLabs.Arch.Sample.Server/appsettings.json create mode 100644 src/TriunniaLabs.Arch.Sample.slnx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..719b90f --- /dev/null +++ b/.gitignore @@ -0,0 +1,399 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates +data/ + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..86a96a2 --- /dev/null +++ b/readme.md @@ -0,0 +1,6 @@ +# TriunniaLabs.Arch.Sample + +A sample application for Arch. + + + diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/AppHost.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/AppHost.cs new file mode 100644 index 0000000..2e46205 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/AppHost.cs @@ -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(); diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Properties/launchSettings.json b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000..b9f2c98 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Properties/launchSettings.json @@ -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" + } + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/ApplicationApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/ApplicationApiSetup.cs new file mode 100644 index 0000000..5331eaa --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/ApplicationApiSetup.cs @@ -0,0 +1,41 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class ApplicationApiSetup + { + /// + /// Adds a resource for the Application Api on port 50001. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddApplicationApi(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/CommentsApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/CommentsApiSetup.cs new file mode 100644 index 0000000..9810c3d --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/CommentsApiSetup.cs @@ -0,0 +1,39 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class ComomentsApiSetup + { + /// + /// Adds a resource for the Comments Api on port 50006. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddCommentsApi(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EmailApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EmailApiSetup.cs new file mode 100644 index 0000000..cd9ab6e --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EmailApiSetup.cs @@ -0,0 +1,46 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class EmailApiSetup + { + /// + /// Adds a resource for the Email Api on port 50004. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddEmailApi(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EntityApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EntityApiSetup.cs new file mode 100644 index 0000000..70ac3b2 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/EntityApiSetup.cs @@ -0,0 +1,39 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class EntityApiSetup + { + /// + /// Adds a resource for the Entity Api on port 50007. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddEntityApi(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/FilesApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/FilesApiSetup.cs new file mode 100644 index 0000000..6b73ad0 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/FilesApiSetup.cs @@ -0,0 +1,33 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class FilesApiSetup + { + /// + /// Adds a resource for the Files Api on port 50008. + /// + /// Reference to the application builder. + 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"); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/KeysApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/KeysApiSetup.cs new file mode 100644 index 0000000..c6e730e --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/KeysApiSetup.cs @@ -0,0 +1,39 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class KeysApiSetup + { + /// + /// Adds a resource for the Keys Api on port 50002. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddKeysApi(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/TokensApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/TokensApiSetup.cs new file mode 100644 index 0000000..032062c --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/TokensApiSetup.cs @@ -0,0 +1,39 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class TokensApiSetup + { + /// + /// Adds a resource for the Tokens Api on port 50003. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddTokensApi(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/UsersApiSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/UsersApiSetup.cs new file mode 100644 index 0000000..5e769e0 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Apis/UsersApiSetup.cs @@ -0,0 +1,52 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class UsersApiSetup + { + /// + /// Adds a resource for the Users Api on port 50005. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddUsersApi(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/ControlPanel/ControlPanelSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/ControlPanel/ControlPanelSetup.cs new file mode 100644 index 0000000..531c123 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/ControlPanel/ControlPanelSetup.cs @@ -0,0 +1,22 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class ControlPanelSetup + { + /// + /// Adds a resource for the Control Panel on port 50000. + /// + /// Reference to the application builder. + /// Reference to the Sql Server resource. + public static void AddControlPanel(this IDistributedApplicationBuilder builder, IResourceBuilder 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Databases/SqlServerSetup.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Databases/SqlServerSetup.cs new file mode 100644 index 0000000..817d16f --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/Setup/Databases/SqlServerSetup.cs @@ -0,0 +1,21 @@ +namespace TriunniaLabs.Arch.Sample.Aspire.AppHost +{ + internal static class SqlServerSetup + { + /// + /// Adds a resource for a Sql Server database on port 51000. + /// + /// Reference to the application builder. + /// + /// Returns the Sql Server resource. + /// + public static IResourceBuilder 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; + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/TriunniaLabs.Arch.Sample.Aspire.AppHost.csproj b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/TriunniaLabs.Arch.Sample.Aspire.AppHost.csproj new file mode 100644 index 0000000..827fbc7 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/TriunniaLabs.Arch.Sample.Aspire.AppHost.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + enable + eaa9faa0-83e2-49df-8f34-af7c4906a005 + + + + + + + + diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.Development.json b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.json b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.json new file mode 100644 index 0000000..31c092a --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/Extensions.cs b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..b72c875 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.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/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults.csproj b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults.csproj new file mode 100644 index 0000000..791c654 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults/TriunniaLabs.Arch.Sample.Aspire.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/TriunniaLabs.Arch.Sample.Aspire/aspire.config.json b/src/TriunniaLabs.Arch.Sample.Aspire/aspire.config.json new file mode 100644 index 0000000..eb58f4c --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Aspire/aspire.config.json @@ -0,0 +1,5 @@ +{ + "appHost": { + "path": "TriunniaLabs.Arch.Sample.Aspire.AppHost/TriunniaLabs.Arch.Sample.Aspire.AppHost.csproj" + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandler.cs b/src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandler.cs new file mode 100644 index 0000000..8a12a30 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandler.cs @@ -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 +{ + /// + /// An authentication handler that validates a user's token/session. + /// + public class UserAuthenticationHandler : AuthenticationHandler + { + // Services + private readonly ILogger _logger; + private readonly UsersApiSettings _settings; + private readonly IFormatterResolver _formatterResolver; + + /// + /// Constructor. + /// + /// Reference to the options class for this handler. + /// Reference to the logger factory. + /// Reference to the url encoder. + /// Reference to the logger. + /// Reference to the users api settings. + /// Reference to the MessagePack formatter resolver. + public UserAuthenticationHandler(IOptionsMonitor options, ILoggerFactory loggerFactory, UrlEncoder urlEncoder, + ILogger logger, IOptions settings, + IFormatterResolver formatterResolver) + : base(options, loggerFactory, urlEncoder) + { + _logger = logger; + _settings = settings.Value; + _formatterResolver = formatterResolver; + } + + /// + /// Handles token authentication. + /// + protected async override Task 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(_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(_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(); + 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; + } + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandlerOptions.cs b/src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandlerOptions.cs new file mode 100644 index 0000000..96b5d2c --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/Handlers/UserAuthenticationHandlerOptions.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authentication; + +namespace TriunniaLabs.Arch.Sample.Server +{ + /// + /// An options class for the User Authentication Handler. + /// + public class UserAuthenticationHandlerOptions : AuthenticationSchemeOptions + { + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Server/HealthChecks/HealthCheck.cs b/src/TriunniaLabs.Arch.Sample.Server/HealthChecks/HealthCheck.cs new file mode 100644 index 0000000..2b375ff --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/HealthChecks/HealthCheck.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace TriunniaLabs.Arch.Sample.Server +{ + /// + /// A health check to allow other services to check this service for uptime. + /// + public class HealthCheck : IHealthCheck + { + /// + /// Checks the health of the server to determine its condition. + /// + /// The context under which the health check is run. + /// A cancellation to use + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + // Just return healthy as this is only an uptime check for now + return Task.FromResult(HealthCheckResult.Healthy()); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Server/Middleware/DummyIpAddressMiddleware.cs b/src/TriunniaLabs.Arch.Sample.Server/Middleware/DummyIpAddressMiddleware.cs new file mode 100644 index 0000000..c67dd67 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/Middleware/DummyIpAddressMiddleware.cs @@ -0,0 +1,25 @@ +using System.Net; + +namespace TriunniaLabs.Arch.Sample.Server +{ + /// + /// A middleware that specifies a dummy ip address for use with the test server. + /// + public class DummyIpAddressMiddleware : IMiddleware + { + /// + /// Occurs when this middleware is invoked. + /// + /// The for the current request. + /// The delegate representing the remaining middleware in the request pipeline. + /// A that represents the execution of this middleware. + 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); + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Server/Program.cs b/src/TriunniaLabs.Arch.Sample.Server/Program.cs new file mode 100644 index 0000000..dabd878 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/Program.cs @@ -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(); + +// 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, MessagePackUntrustedByteArrayConverter>("MessagePackUntrustedByteArrayConverter"); +builder.Services.AddKeyedSingleton, MessagePackUntrustedByteArrayConverter>("MessagePackUntrustedByteArrayConverter"); +builder.Services.AddKeyedSingleton, MessagePackUntrustedByteArrayConverter>("MessagePackUntrustedByteArrayConverter"); + +// 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>().Value; +builder.Services.AddAuthentication(options => options.DefaultScheme = settings.AuthenticationScheme) + .AddScheme(settings.AuthenticationScheme, options => { }) + .AddScheme(ArchConstants.ApplicationAuthenticationScheme, options => { }); + +// Controllers +builder.Services.AddMvc() + .AddApplicationPart(Assembly.GetExecutingAssembly()).AddControllersAsServices() + .AddTriunniaLabsUsersApiRelay(builder.Services.AddStandardMessagePackFormatters) + .AddTriunniaLabsEmailApiRelay(); + +// Health Checks +builder.Services.AddHealthChecks().AddCheck("health-check"); + +// Middleware +if (builder.Environment.IsDevelopment()) + builder.Services.AddSingleton(); + +// ------------------------------------------------------------------------------------ + +var app = builder.Build(); + +// Triunnia Labs +app.UseTriunniaLabsMicroservice(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseMiddleware(); + 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(); diff --git a/src/TriunniaLabs.Arch.Sample.Server/Properties/launchSettings.json b/src/TriunniaLabs.Arch.Sample.Server/Properties/launchSettings.json new file mode 100644 index 0000000..a5ecce9 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/Properties/launchSettings.json @@ -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" + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Server/Providers/DiagnosticsConfigurationProvider.cs b/src/TriunniaLabs.Arch.Sample.Server/Providers/DiagnosticsConfigurationProvider.cs new file mode 100644 index 0000000..09e1354 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/Providers/DiagnosticsConfigurationProvider.cs @@ -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 +{ + /// + /// A provider for supplying additional configuration information for the purpose of diagnostics. + /// + 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 _logger; + + /// + /// Constructor. + /// + /// Reference to the Users Api setttings. + /// Reference to the Email Api setttings. + /// Reference to the Entity Api setttings. + /// Reference to the Files Api setttings. + /// Reference to the logger. + public DiagnosicConfigurationProvider(IOptions usersApiSettings, IOptions emailApiSettings, + IOptions entityApiSettings, IOptions filesApiSettings, + ILogger 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)); + } + + /// + /// Supplies custom settings used by the microservice to the diagnostic report. + /// Call to add each setting. + /// + /// The diagnostic report to configure. + 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."); + } + } + } +} diff --git a/src/TriunniaLabs.Arch.Sample.Server/TriunniaLabs.Arch.Sample.Server.csproj b/src/TriunniaLabs.Arch.Sample.Server/TriunniaLabs.Arch.Sample.Server.csproj new file mode 100644 index 0000000..d4cc6d2 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/TriunniaLabs.Arch.Sample.Server.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/src/TriunniaLabs.Arch.Sample.Server/appsettings.json b/src/TriunniaLabs.Arch.Sample.Server/appsettings.json new file mode 100644 index 0000000..873f949 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/appsettings.json @@ -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" ] + } +} diff --git a/src/TriunniaLabs.Arch.Sample.slnx b/src/TriunniaLabs.Arch.Sample.slnx new file mode 100644 index 0000000..0a35afa --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.slnx @@ -0,0 +1,5 @@ + + + + +