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
+399
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
# TriunniaLabs.Arch.Sample
A sample application for Arch.
@@ -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>