♻️ Remove etcd, replace with asprie. Move infra to aspire. Disable gateway for now
This commit is contained in:
		
							
								
								
									
										30
									
								
								DysonNetwork.Control/AppHost.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								DysonNetwork.Control/AppHost.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					var builder = DistributedApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var database = builder.AddPostgres("database");
 | 
				
			||||||
 | 
					var cache = builder.AddConnectionString("cache");
 | 
				
			||||||
 | 
					var queue = builder.AddNats("queue").WithJetStream();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ring = builder.AddProject<Projects.DysonNetwork_Ring>("ring")
 | 
				
			||||||
 | 
					    .WithReference(database)
 | 
				
			||||||
 | 
					    .WithReference(queue);
 | 
				
			||||||
 | 
					var pass = builder.AddProject<Projects.DysonNetwork_Pass>("pass")
 | 
				
			||||||
 | 
					    .WithReference(database)
 | 
				
			||||||
 | 
					    .WithReference(cache)
 | 
				
			||||||
 | 
					    .WithReference(queue)
 | 
				
			||||||
 | 
					    .WithReference(ring);
 | 
				
			||||||
 | 
					builder.AddProject<Projects.DysonNetwork_Drive>("drive")
 | 
				
			||||||
 | 
					    .WithReference(database)
 | 
				
			||||||
 | 
					    .WithReference(cache)
 | 
				
			||||||
 | 
					    .WithReference(queue)
 | 
				
			||||||
 | 
					    .WithReference(pass);
 | 
				
			||||||
 | 
					builder.AddProject<Projects.DysonNetwork_Sphere>("sphere")
 | 
				
			||||||
 | 
					    .WithReference(database)
 | 
				
			||||||
 | 
					    .WithReference(cache)
 | 
				
			||||||
 | 
					    .WithReference(queue)
 | 
				
			||||||
 | 
					    .WithReference(pass);
 | 
				
			||||||
 | 
					builder.AddProject<Projects.DysonNetwork_Develop>("develop")
 | 
				
			||||||
 | 
					    .WithReference(database)
 | 
				
			||||||
 | 
					    .WithReference(cache)
 | 
				
			||||||
 | 
					    .WithReference(pass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.Build().Run();
 | 
				
			||||||
							
								
								
									
										30
									
								
								DysonNetwork.Control/DysonNetwork.Control.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								DysonNetwork.Control/DysonNetwork.Control.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					<Project Sdk="Microsoft.NET.Sdk">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <Sdk Name="Aspire.AppHost.Sdk" Version="9.4.2"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <PropertyGroup>
 | 
				
			||||||
 | 
					        <OutputType>Exe</OutputType>
 | 
				
			||||||
 | 
					        <TargetFramework>net9.0</TargetFramework>
 | 
				
			||||||
 | 
					        <ImplicitUsings>enable</ImplicitUsings>
 | 
				
			||||||
 | 
					        <Nullable>enable</Nullable>
 | 
				
			||||||
 | 
					        <UserSecretsId>a68b3195-a00d-40c2-b5ed-d675356b7cde</UserSecretsId>
 | 
				
			||||||
 | 
					        <RootNamespace>DysonNetwork.Control</RootNamespace>
 | 
				
			||||||
 | 
					    </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.Hosting.AppHost" Version="9.4.2"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.Hosting.Nats" Version="9.4.2" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.4.2" />
 | 
				
			||||||
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.Develop\DysonNetwork.Develop.csproj" />
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.Drive\DysonNetwork.Drive.csproj" />
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.Gateway\DysonNetwork.Gateway.csproj" />
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj" />
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.Ring\DysonNetwork.Ring.csproj" />
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.Sphere\DysonNetwork.Sphere.csproj" />
 | 
				
			||||||
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</Project>
 | 
				
			||||||
							
								
								
									
										29
									
								
								DysonNetwork.Control/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								DysonNetwork.Control/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://json.schemastore.org/launchsettings.json",
 | 
				
			||||||
 | 
					  "profiles": {
 | 
				
			||||||
 | 
					    "https": {
 | 
				
			||||||
 | 
					      "commandName": "Project",
 | 
				
			||||||
 | 
					      "dotnetRunMessages": true,
 | 
				
			||||||
 | 
					      "launchBrowser": true,
 | 
				
			||||||
 | 
					      "applicationUrl": "https://localhost:17025;http://localhost:15057",
 | 
				
			||||||
 | 
					      "environmentVariables": {
 | 
				
			||||||
 | 
					        "ASPNETCORE_ENVIRONMENT": "Development",
 | 
				
			||||||
 | 
					        "DOTNET_ENVIRONMENT": "Development",
 | 
				
			||||||
 | 
					        "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21175",
 | 
				
			||||||
 | 
					        "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22189"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "http": {
 | 
				
			||||||
 | 
					      "commandName": "Project",
 | 
				
			||||||
 | 
					      "dotnetRunMessages": true,
 | 
				
			||||||
 | 
					      "launchBrowser": true,
 | 
				
			||||||
 | 
					      "applicationUrl": "http://localhost:15057",
 | 
				
			||||||
 | 
					      "environmentVariables": {
 | 
				
			||||||
 | 
					        "ASPNETCORE_ENVIRONMENT": "Development",
 | 
				
			||||||
 | 
					        "DOTNET_ENVIRONMENT": "Development",
 | 
				
			||||||
 | 
					        "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19163",
 | 
				
			||||||
 | 
					        "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20185"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								DysonNetwork.Control/appsettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								DysonNetwork.Control/appsettings.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "Logging": {
 | 
				
			||||||
 | 
					    "LogLevel": {
 | 
				
			||||||
 | 
					      "Default": "Information",
 | 
				
			||||||
 | 
					      "Microsoft.AspNetCore": "Warning"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "ConnectionStrings": {
 | 
				
			||||||
 | 
					    "cache": "localhost:6379"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -31,6 +31,7 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
    <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
					    <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,16 @@
 | 
				
			|||||||
using DysonNetwork.Develop;
 | 
					using DysonNetwork.Develop;
 | 
				
			||||||
using DysonNetwork.Shared.Auth;
 | 
					using DysonNetwork.Shared.Auth;
 | 
				
			||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
using DysonNetwork.Develop.Startup;
 | 
					using DysonNetwork.Develop.Startup;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
builder.Services.AddAppSwagger();
 | 
					builder.Services.AddAppSwagger();
 | 
				
			||||||
@@ -22,6 +21,8 @@ builder.Services.AddDriveService();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
 | 
					    var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,6 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Develop",
 | 
					    "Name": "DysonNetwork.Develop",
 | 
				
			||||||
    "Url": "https://localhost:7192",
 | 
					    "Url": "https://localhost:7192"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,6 +66,7 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
					      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,18 +5,18 @@ using DysonNetwork.Shared.Auth;
 | 
				
			|||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.PageData;
 | 
					using DysonNetwork.Shared.PageData;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using tusdotnet.Stores;
 | 
					using tusdotnet.Stores;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue);
 | 
					builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					
 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -39,6 +39,8 @@ builder.Services.AddTransient<IPageDataProvider, VersionPageData>();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -51,8 +53,6 @@ var tusDiskStore = app.Services.GetRequiredService<TusDiskStore>();
 | 
				
			|||||||
// Configure application middleware pipeline
 | 
					// Configure application middleware pipeline
 | 
				
			||||||
app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath);
 | 
					app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.MapGatewayProxy();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html"));
 | 
					app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure gRPC
 | 
					// Configure gRPC
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -131,8 +131,6 @@
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Drive",
 | 
					    "Name": "DysonNetwork.Drive",
 | 
				
			||||||
    "Url": "https://localhost:7092",
 | 
					    "Url": "https://localhost:7092"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,7 +71,7 @@ public class WellKnownController(
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!domainMappings.TryGetValue(key, out var domain)) continue;
 | 
					            if (!domainMappings.TryGetValue(key, out var domain)) continue;
 | 
				
			||||||
            if (domain is not null)
 | 
					            if (domain is not null)
 | 
				
			||||||
                serviceMap[key] = "https://" + domain;
 | 
					                serviceMap[key] = "http://" + domain;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Ok(serviceMap);
 | 
					        return Ok(serviceMap);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@
 | 
				
			|||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
    <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
					    <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,8 @@ using Microsoft.AspNetCore.HttpOverrides;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
builder.Host.UseContentRoot(Directory.GetCurrentDirectory());
 | 
					builder.Host.UseContentRoot(Directory.GetCurrentDirectory());
 | 
				
			||||||
builder.WebHost.ConfigureKestrel(options =>
 | 
					builder.WebHost.ConfigureKestrel(options =>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -18,6 +20,8 @@ builder.Services.AddControllers();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.ConfigureForwardedHeaders(app.Configuration);
 | 
					app.ConfigureForwardedHeaders(app.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.UseRequestTimeouts();
 | 
					app.UseRequestTimeouts();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,4 @@
 | 
				
			|||||||
using System.Net.Security;
 | 
					using System.Net.Security;
 | 
				
			||||||
using System.Security.Cryptography.X509Certificates;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
using Yarp.ReverseProxy.Configuration;
 | 
					using Yarp.ReverseProxy.Configuration;
 | 
				
			||||||
using Yarp.ReverseProxy.Transforms;
 | 
					using Yarp.ReverseProxy.Transforms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,7 +28,6 @@ public static class ServiceCollectionExtensions
 | 
				
			|||||||
                context.AddXForwarded(action: ForwardedTransformActions.Set);
 | 
					                context.AddXForwarded(action: ForwardedTransformActions.Set);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddRegistryService(configuration, addForwarder: false);
 | 
					 | 
				
			||||||
        services.AddSingleton<IProxyConfigProvider, RegistryProxyConfigProvider>();
 | 
					        services.AddSingleton<IProxyConfigProvider, RegistryProxyConfigProvider>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,7 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
					        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
using dotnet_etcd;
 | 
					 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,20 +4,16 @@ using DysonNetwork.Pass.Startup;
 | 
				
			|||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.PageData;
 | 
					using DysonNetwork.Shared.PageData;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add metrics and telemetry
 | 
					 | 
				
			||||||
builder.Services.AddAppMetrics();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -41,6 +37,8 @@ builder.Services.AddTransient<IPageDataProvider, AccountPageData>();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -51,8 +49,6 @@ using (var scope = app.Services.CreateScope())
 | 
				
			|||||||
// Configure application middleware pipeline
 | 
					// Configure application middleware pipeline
 | 
				
			||||||
app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath);
 | 
					app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.MapGatewayProxy();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html"));
 | 
					app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure gRPC
 | 
					// Configure gRPC
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
using OpenTelemetry.Metrics;
 | 
					 | 
				
			||||||
using OpenTelemetry.Trace;
 | 
					 | 
				
			||||||
using Prometheus;
 | 
					 | 
				
			||||||
using Prometheus.SystemMetrics;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Pass.Startup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class MetricsConfiguration
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public static IServiceCollection AddAppMetrics(this IServiceCollection services)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Prometheus
 | 
					 | 
				
			||||||
        services.UseHttpClientMetrics();
 | 
					 | 
				
			||||||
        services.AddHealthChecks();
 | 
					 | 
				
			||||||
        services.AddSystemMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusEntityFrameworkMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusAspNetCoreMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusHttpClientMetrics();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // OpenTelemetry
 | 
					 | 
				
			||||||
        services.AddOpenTelemetry()
 | 
					 | 
				
			||||||
            .WithTracing(tracing =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                tracing
 | 
					 | 
				
			||||||
                    .AddAspNetCoreInstrumentation()
 | 
					 | 
				
			||||||
                    .AddHttpClientInstrumentation()
 | 
					 | 
				
			||||||
                    .AddOtlpExporter();
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .WithMetrics(metrics =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                metrics
 | 
					 | 
				
			||||||
                    .AddAspNetCoreInstrumentation()
 | 
					 | 
				
			||||||
                    .AddHttpClientInstrumentation()
 | 
					 | 
				
			||||||
                    .AddRuntimeInstrumentation()
 | 
					 | 
				
			||||||
                    .AddOtlpExporter();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return services;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -83,9 +83,7 @@
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Pass",
 | 
					    "Name": "DysonNetwork.Pass",
 | 
				
			||||||
    "Url": "https://localhost:7058",
 | 
					    "Url": "https://localhost:7058"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Etcd": {
 | 
					  "Etcd": {
 | 
				
			||||||
    "Insecure": true
 | 
					    "Insecure": true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
using System.Collections.Concurrent;
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
using System.Net.WebSockets;
 | 
					using System.Net.WebSockets;
 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Data;
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using Grpc.Core;
 | 
					using Grpc.Core;
 | 
				
			||||||
@@ -11,17 +10,14 @@ public class WebSocketService
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private readonly IConfiguration _configuration;
 | 
					    private readonly IConfiguration _configuration;
 | 
				
			||||||
    private readonly ILogger<WebSocketService> _logger;
 | 
					    private readonly ILogger<WebSocketService> _logger;
 | 
				
			||||||
    private readonly IEtcdClient _etcdClient;
 | 
					 | 
				
			||||||
    private readonly IDictionary<string, IWebSocketPacketHandler> _handlerMap;
 | 
					    private readonly IDictionary<string, IWebSocketPacketHandler> _handlerMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public WebSocketService(
 | 
					    public WebSocketService(
 | 
				
			||||||
        IEnumerable<IWebSocketPacketHandler> handlers,
 | 
					        IEnumerable<IWebSocketPacketHandler> handlers,
 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        ILogger<WebSocketService> logger,
 | 
					        ILogger<WebSocketService> logger,
 | 
				
			||||||
        IConfiguration configuration
 | 
					        IConfiguration configuration
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _etcdClient = etcdClient;
 | 
					 | 
				
			||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
        _configuration = configuration;
 | 
					        _configuration = configuration;
 | 
				
			||||||
        _handlerMap = handlers.ToDictionary(h => h.PacketType);
 | 
					        _handlerMap = handlers.ToDictionary(h => h.PacketType);
 | 
				
			||||||
@@ -59,8 +55,10 @@ public class WebSocketService
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (Exception ex)
 | 
					        catch (Exception ex)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId, key.DeviceId);
 | 
					            _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId,
 | 
				
			||||||
 | 
					                key.DeviceId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data.Cts.Cancel();
 | 
					        data.Cts.Cancel();
 | 
				
			||||||
        ActiveConnections.TryRemove(key, out _);
 | 
					        ActiveConnections.TryRemove(key, out _);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -140,25 +138,9 @@ public class WebSocketService
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // Get the service URL from etcd for the specified endpoint
 | 
					                var serviceUrl = "https://" + packet.Endpoint;
 | 
				
			||||||
                var serviceKey = $"/services/{packet.Endpoint}";
 | 
					 | 
				
			||||||
                var response = await _etcdClient.GetAsync(serviceKey);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (response.Kvs.Count > 0)
 | 
					                var callInvoker = GrpcClientHelper.CreateCallInvoker(serviceUrl);
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var serviceUrl = response.Kvs[0].Value.ToStringUtf8();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var clientCertPath = _configuration["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
                    var clientKeyPath = _configuration["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
                    var clientCertPassword = _configuration["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var callInvoker =
 | 
					 | 
				
			||||||
                        GrpcClientHelper.CreateCallInvoker(
 | 
					 | 
				
			||||||
                            serviceUrl,
 | 
					 | 
				
			||||||
                            clientCertPath,
 | 
					 | 
				
			||||||
                            clientKeyPath,
 | 
					 | 
				
			||||||
                            clientCertPassword
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                var client = new RingHandlerService.RingHandlerServiceClient(callInvoker);
 | 
					                var client = new RingHandlerService.RingHandlerServiceClient(callInvoker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try
 | 
					                try
 | 
				
			||||||
@@ -174,11 +156,6 @@ public class WebSocketService
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}");
 | 
					                    _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}");
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                _logger.LogWarning($"No service registered for endpoint: {packet.Endpoint}");
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,7 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
					      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,17 +3,16 @@ using DysonNetwork.Ring.Startup;
 | 
				
			|||||||
using DysonNetwork.Shared.Auth;
 | 
					using DysonNetwork.Shared.Auth;
 | 
				
			||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -32,6 +31,8 @@ builder.Services.AddAppScheduledJobs();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ using DysonNetwork.Ring.Connection;
 | 
				
			|||||||
using DysonNetwork.Ring.Email;
 | 
					using DysonNetwork.Ring.Email;
 | 
				
			||||||
using DysonNetwork.Ring.Notification;
 | 
					using DysonNetwork.Ring.Notification;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
using Google.Protobuf.WellKnownTypes;
 | 
					using Google.Protobuf.WellKnownTypes;
 | 
				
			||||||
using Grpc.Core;
 | 
					using Grpc.Core;
 | 
				
			||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ using System.Text.Json;
 | 
				
			|||||||
using DysonNetwork.Ring.Email;
 | 
					using DysonNetwork.Ring.Email;
 | 
				
			||||||
using DysonNetwork.Ring.Notification;
 | 
					using DysonNetwork.Ring.Notification;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					using DysonNetwork.Shared.Stream;
 | 
				
			||||||
using Google.Protobuf;
 | 
					using Google.Protobuf;
 | 
				
			||||||
using NATS.Client.Core;
 | 
					using NATS.Client.Core;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,9 +45,7 @@
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Ring",
 | 
					    "Name": "DysonNetwork.Ring",
 | 
				
			||||||
    "Url": "https://localhost:7259",
 | 
					    "Url": "https://localhost:7259"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Etcd": {
 | 
					  "Etcd": {
 | 
				
			||||||
    "Insecure": true
 | 
					    "Insecure": true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<Project Sdk="Microsoft.NET.Sdk">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <PropertyGroup>
 | 
				
			||||||
 | 
					        <TargetFramework>net9.0</TargetFramework>
 | 
				
			||||||
 | 
					        <ImplicitUsings>enable</ImplicitUsings>
 | 
				
			||||||
 | 
					        <Nullable>enable</Nullable>
 | 
				
			||||||
 | 
					        <IsAspireSharedProject>true</IsAspireSharedProject>
 | 
				
			||||||
 | 
					    </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <FrameworkReference Include="Microsoft.AspNetCore.App"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.NATS.Net" Version="9.4.2" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.4.2" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.StackExchange.Redis" Version="9.4.2" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.7.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.4.2"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.12.0-beta.1" />
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0"/>
 | 
				
			||||||
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</Project>
 | 
				
			||||||
							
								
								
									
										132
									
								
								DysonNetwork.ServiceDefaults/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								DysonNetwork.ServiceDefaults/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					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 .NET 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"];
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        builder.AddNatsClient("queue");
 | 
				
			||||||
 | 
					        builder.AddRedisClient("cache");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using Microsoft.Extensions.Configuration;
 | 
					using Microsoft.Extensions.Configuration;
 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					using Microsoft.Extensions.DependencyInjection;
 | 
				
			||||||
@@ -11,32 +10,14 @@ public static class DysonAuthStartup
 | 
				
			|||||||
        this IServiceCollection services
 | 
					        this IServiceCollection services
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<AuthService.AuthServiceClient>(sp =>
 | 
					        services.AddGrpcClient<AuthService.AuthServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://pass");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddSingleton<PermissionService.PermissionServiceClient>(sp =>
 | 
					        services.AddGrpcClient<PermissionService.PermissionServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://pass");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreatePermissionServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddAuthentication(options =>
 | 
					        services.AddAuthentication(options =>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,11 @@
 | 
				
			|||||||
    </PropertyGroup>
 | 
					    </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
        <PackageReference Include="dotnet-etcd" Version="8.0.1" />
 | 
					 | 
				
			||||||
        <PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
 | 
					        <PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
 | 
				
			||||||
        <PackageReference Include="Google.Protobuf" Version="3.31.1" />
 | 
					        <PackageReference Include="Google.Protobuf" Version="3.31.1" />
 | 
				
			||||||
        <PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
 | 
					        <PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
 | 
				
			||||||
        <PackageReference Include="Grpc" Version="2.46.6" />
 | 
					        <PackageReference Include="Grpc" Version="2.46.6" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.71.0" />
 | 
				
			||||||
        <PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
 | 
					        <PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
 | 
				
			||||||
        <PackageReference Include="Grpc.Tools" Version="2.72.0">
 | 
					        <PackageReference Include="Grpc.Tools" Version="2.72.0">
 | 
				
			||||||
            <PrivateAssets>all</PrivateAssets>
 | 
					            <PrivateAssets>all</PrivateAssets>
 | 
				
			||||||
@@ -27,7 +27,6 @@
 | 
				
			|||||||
        <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
 | 
					        <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
 | 
				
			||||||
        <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
 | 
					        <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
 | 
				
			||||||
        <PackageReference Include="OpenGraph-Net" Version="4.0.1" />
 | 
					        <PackageReference Include="OpenGraph-Net" Version="4.0.1" />
 | 
				
			||||||
        <PackageReference Include="StackExchange.Redis" Version="2.8.41" />
 | 
					 | 
				
			||||||
        <PackageReference Include="System.Net.Http" Version="4.3.4" />
 | 
					        <PackageReference Include="System.Net.Http" Version="4.3.4" />
 | 
				
			||||||
        <PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
 | 
					        <PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
@@ -40,4 +39,8 @@
 | 
				
			|||||||
      <Folder Include="Error\" />
 | 
					      <Folder Include="Error\" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,20 +20,6 @@ public static class KestrelConfiguration
 | 
				
			|||||||
        builder.WebHost.ConfigureKestrel(options =>
 | 
					        builder.WebHost.ConfigureKestrel(options =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            options.Limits.MaxRequestBodySize = maxRequestBodySize;
 | 
					            options.Limits.MaxRequestBodySize = maxRequestBodySize;
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var configuredUrl = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
 | 
					 | 
				
			||||||
            if (!string.IsNullOrEmpty(configuredUrl)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var certPath = configuration["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var keyPath = configuration["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Load PEM cert and key manually
 | 
					 | 
				
			||||||
            var certificate = X509Certificate2.CreateFromPemFile(certPath, keyPath);
 | 
					 | 
				
			||||||
            // Now pass the full cert
 | 
					 | 
				
			||||||
            options.ListenAnyIP(5001, listenOptions => { listenOptions.UseHttps(certificate); });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Optional: HTTP fallback
 | 
					 | 
				
			||||||
            options.ListenAnyIP(8080);
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return builder;
 | 
					        return builder;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,171 +2,13 @@ using System.Net;
 | 
				
			|||||||
using Grpc.Net.Client;
 | 
					using Grpc.Net.Client;
 | 
				
			||||||
using System.Security.Cryptography.X509Certificates;
 | 
					using System.Security.Cryptography.X509Certificates;
 | 
				
			||||||
using Grpc.Core;
 | 
					using Grpc.Core;
 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Shared.Proto;
 | 
					namespace DysonNetwork.Shared.Proto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public static class GrpcClientHelper
 | 
					public static class GrpcClientHelper
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static CallInvoker CreateCallInvoker(
 | 
					    public static CallInvoker CreateCallInvoker(string url)
 | 
				
			||||||
        string url,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var handler = new HttpClientHandler();
 | 
					        return GrpcChannel.ForAddress(url).CreateCallInvoker();
 | 
				
			||||||
        handler.ClientCertificates.Add(
 | 
					 | 
				
			||||||
            clientCertPassword is null
 | 
					 | 
				
			||||||
                ? X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath)
 | 
					 | 
				
			||||||
                : X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
 | 
					 | 
				
			||||||
        var httpClient = new HttpClient(handler);
 | 
					 | 
				
			||||||
        httpClient.DefaultRequestVersion = HttpVersion.Version20;
 | 
					 | 
				
			||||||
        httpClient.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
 | 
					 | 
				
			||||||
        return GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpClient = httpClient }).CreateCallInvoker();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static async Task<string> GetServiceUrlFromEtcd(IEtcdClient etcdClient, string serviceName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var response = await etcdClient.GetAsync($"/services/{serviceName}");
 | 
					 | 
				
			||||||
        return response.Kvs.Count == 0
 | 
					 | 
				
			||||||
            ? throw new InvalidOperationException($"Service '{serviceName}' not found in Etcd.")
 | 
					 | 
				
			||||||
            : response.Kvs[0].Value.ToStringUtf8();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<AccountService.AccountServiceClient> CreateAccountServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<BotAccountReceiverService.BotAccountReceiverServiceClient>
 | 
					 | 
				
			||||||
        CreateBotAccountReceiverServiceClient(
 | 
					 | 
				
			||||||
            IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
            string clientCertPath,
 | 
					 | 
				
			||||||
            string clientKeyPath,
 | 
					 | 
				
			||||||
            string? clientCertPassword = null
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new BotAccountReceiverService.BotAccountReceiverServiceClient(CreateCallInvoker(url, clientCertPath,
 | 
					 | 
				
			||||||
            clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<ActionLogService.ActionLogServiceClient> CreateActionLogServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new ActionLogService.ActionLogServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<AuthService.AuthServiceClient> CreateAuthServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<PermissionService.PermissionServiceClient> CreatePermissionServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new PermissionService.PermissionServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public static async Task<PaymentService.PaymentServiceClient> CreatePaymentServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new PaymentService.PaymentServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public static async Task<RingService.RingServiceClient> CreateRingServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Ring");
 | 
					 | 
				
			||||||
        return new RingService.RingServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<FileService.FileServiceClient> CreateFileServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive");
 | 
					 | 
				
			||||||
        return new FileService.FileServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<FileReferenceService.FileReferenceServiceClient> CreateFileReferenceServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive");
 | 
					 | 
				
			||||||
        return new FileReferenceService.FileReferenceServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<PublisherService.PublisherServiceClient> CreatePublisherServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Sphere");
 | 
					 | 
				
			||||||
        return new PublisherService.PublisherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<CustomAppService.CustomAppServiceClient> CreateCustomAppServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Develop");
 | 
					 | 
				
			||||||
        return new CustomAppService.CustomAppServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,83 +0,0 @@
 | 
				
			|||||||
using System.Diagnostics;
 | 
					 | 
				
			||||||
using System.Net;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Builder;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Http;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					 | 
				
			||||||
using Yarp.ReverseProxy.Forwarder;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class GatewayReverseProxy
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Provides reverse proxy for DysonNetwork.Gateway.
 | 
					 | 
				
			||||||
    /// Give the ability to the contained frontend to access other services via the gateway.
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    /// <param name="app">The asp.net core application</param>
 | 
					 | 
				
			||||||
    /// <returns>The modified application</returns>
 | 
					 | 
				
			||||||
    public static WebApplication MapGatewayProxy(this WebApplication app)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var httpClient = new HttpMessageInvoker(new SocketsHttpHandler
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            UseProxy = false,
 | 
					 | 
				
			||||||
            AllowAutoRedirect = true,
 | 
					 | 
				
			||||||
            AutomaticDecompression = DecompressionMethods.All,
 | 
					 | 
				
			||||||
            UseCookies = true,
 | 
					 | 
				
			||||||
            EnableMultipleHttp2Connections = true,
 | 
					 | 
				
			||||||
            ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current),
 | 
					 | 
				
			||||||
            ConnectTimeout = TimeSpan.FromSeconds(15),
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var transformer = new GatewayReverseProxyTransformer();
 | 
					 | 
				
			||||||
        var requestConfig = new ForwarderRequestConfig();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        app.Map("/cgi/{**catch-all}", async (HttpContext context, IHttpForwarder forwarder) =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var registry = context.RequestServices.GetRequiredService<ServiceRegistry>();
 | 
					 | 
				
			||||||
            var gatewayUrl = await registry.GetServiceUrl("DysonNetwork.Gateway");
 | 
					 | 
				
			||||||
            if (gatewayUrl is null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                context.Response.StatusCode = 404;
 | 
					 | 
				
			||||||
                await context.Response.WriteAsync("Gateway not found");
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var error = await forwarder.SendAsync(
 | 
					 | 
				
			||||||
                context,
 | 
					 | 
				
			||||||
                gatewayUrl,
 | 
					 | 
				
			||||||
                httpClient,
 | 
					 | 
				
			||||||
                requestConfig,
 | 
					 | 
				
			||||||
                transformer
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            if (error != ForwarderError.None)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var errorFeature = context.GetForwarderErrorFeature();
 | 
					 | 
				
			||||||
                var exception = errorFeature?.Exception;
 | 
					 | 
				
			||||||
                context.Response.StatusCode = 502;
 | 
					 | 
				
			||||||
                context.Response.ContentType = "text/plain";
 | 
					 | 
				
			||||||
                await context.Response.WriteAsync($"Gateway remote error: {exception?.Message}");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return app;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class GatewayReverseProxyTransformer : HttpTransformer
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private const string Value = "/cgi";
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public override ValueTask TransformRequestAsync(
 | 
					 | 
				
			||||||
        HttpContext httpContext,
 | 
					 | 
				
			||||||
        HttpRequestMessage proxyRequest,
 | 
					 | 
				
			||||||
        string destinationPrefix,
 | 
					 | 
				
			||||||
        CancellationToken cancellationToken
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        httpContext.Request.Path = httpContext.Request.Path.StartsWithSegments(Value, out var remaining)
 | 
					 | 
				
			||||||
            ? remaining
 | 
					 | 
				
			||||||
            : httpContext.Request.Path;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        return Default.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,60 +0,0 @@
 | 
				
			|||||||
using Microsoft.Extensions.Configuration;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Hosting;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class RegistryHostedService(
 | 
					 | 
				
			||||||
    ServiceRegistry serviceRegistry,
 | 
					 | 
				
			||||||
    IConfiguration configuration,
 | 
					 | 
				
			||||||
    ILogger<RegistryHostedService> logger
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
    : IHostedService
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private CancellationTokenSource? _cts;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task StartAsync(CancellationToken cancellationToken)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var serviceName = configuration["Service:Name"];
 | 
					 | 
				
			||||||
        var serviceUrl = configuration["Service:Url"];
 | 
					 | 
				
			||||||
        var insecure = configuration.GetValue<bool>("Etcd:Insecure");
 | 
					 | 
				
			||||||
        var remote = configuration.GetConnectionString("Etcd");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (insecure)
 | 
					 | 
				
			||||||
            logger.LogWarning("Etcd is configured to use insecure channel.");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            logger.LogWarning("Service URL or Service Name was not configured. Skipping Etcd registration.");
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        logger.LogInformation(
 | 
					 | 
				
			||||||
            "Registering service {ServiceName} at {ServiceUrl} with Etcd ({Remote}).",
 | 
					 | 
				
			||||||
            serviceName,
 | 
					 | 
				
			||||||
            serviceUrl,
 | 
					 | 
				
			||||||
            remote
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        try
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _cts = new CancellationTokenSource();
 | 
					 | 
				
			||||||
            await serviceRegistry.RegisterService(serviceName, serviceUrl, cancellationToken: _cts.Token);
 | 
					 | 
				
			||||||
            logger.LogInformation("Service {ServiceName} registered successfully.", serviceName);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task StopAsync(CancellationToken cancellationToken)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _cts?.Cancel();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // The lease will expire automatically if the service stops ungracefully.
 | 
					 | 
				
			||||||
        var serviceName = configuration["Service:Name"];
 | 
					 | 
				
			||||||
        if (serviceName is not null)
 | 
					 | 
				
			||||||
            await serviceRegistry.UnregisterService(serviceName);
 | 
					 | 
				
			||||||
        logger.LogInformation("Service registration hosted service is stopping.");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using Microsoft.Extensions.Configuration;
 | 
					using Microsoft.Extensions.Configuration;
 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					using Microsoft.Extensions.DependencyInjection;
 | 
				
			||||||
@@ -9,18 +8,9 @@ public static class ServiceInjectionHelper
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public static IServiceCollection AddRingService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddRingService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<RingService.RingServiceClient>(sp =>
 | 
					        services.AddGrpcClient<RingService.RingServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://ring");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateRingServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
@@ -28,61 +18,25 @@ public static class ServiceInjectionHelper
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    public static IServiceCollection AddAccountService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddAccountService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<AccountService.AccountServiceClient>(sp =>
 | 
					        services.AddGrpcClient<AccountService.AccountServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://pass");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        services.AddSingleton<AccountClientHelper>();
 | 
					        services.AddSingleton<AccountClientHelper>();
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        services.AddSingleton<BotAccountReceiverService.BotAccountReceiverServiceClient>(sp =>
 | 
					        services.AddGrpcClient<BotAccountReceiverService.BotAccountReceiverServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://pass");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateBotAccountReceiverServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        services.AddSingleton<ActionLogService.ActionLogServiceClient>(sp =>
 | 
					        services.AddGrpcClient<ActionLogService.ActionLogServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://pass");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateActionLogServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        }); 
 | 
					        }); 
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        services.AddSingleton<PaymentService.PaymentServiceClient>(sp =>
 | 
					        services.AddGrpcClient<PaymentService.PaymentServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://pass");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreatePaymentServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
@@ -90,32 +44,14 @@ public static class ServiceInjectionHelper
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    public static IServiceCollection AddDriveService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddDriveService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<FileService.FileServiceClient>(sp =>
 | 
					        services.AddGrpcClient<FileService.FileServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://drive");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateFileServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });       
 | 
					        });       
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        services.AddSingleton<FileReferenceService.FileReferenceServiceClient>(sp =>
 | 
					        services.AddGrpcClient<FileReferenceService.FileReferenceServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://drive");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateFileReferenceServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
@@ -123,18 +59,9 @@ public static class ServiceInjectionHelper
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    public static IServiceCollection AddPublisherService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddPublisherService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<PublisherService.PublisherServiceClient>(sp =>
 | 
					        services.AddGrpcClient<PublisherService.PublisherServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://sphere");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreatePublisherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
@@ -142,18 +69,9 @@ public static class ServiceInjectionHelper
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static IServiceCollection AddDevelopService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddDevelopService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<CustomAppService.CustomAppServiceClient>(sp =>
 | 
					        services.AddGrpcClient<CustomAppService.CustomAppServiceClient>(o =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            o.Address = new Uri("https://develop");
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateCustomAppServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,55 +0,0 @@
 | 
				
			|||||||
using System.Text;
 | 
					 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using Etcdserverpb;
 | 
					 | 
				
			||||||
using Google.Protobuf;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public async Task RegisterService(
 | 
					 | 
				
			||||||
        string serviceName,
 | 
					 | 
				
			||||||
        string serviceUrl,
 | 
					 | 
				
			||||||
        long leaseTtlSeconds = 60,
 | 
					 | 
				
			||||||
        CancellationToken cancellationToken = default
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var key = $"/services/{serviceName}";
 | 
					 | 
				
			||||||
        var leaseResponse = await etcd.LeaseGrantAsync(
 | 
					 | 
				
			||||||
            new LeaseGrantRequest { TTL = leaseTtlSeconds },
 | 
					 | 
				
			||||||
            cancellationToken: cancellationToken
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        await etcd.PutAsync(new PutRequest
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Key = ByteString.CopyFrom(key, Encoding.UTF8),
 | 
					 | 
				
			||||||
            Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8),
 | 
					 | 
				
			||||||
            Lease = leaseResponse.ID
 | 
					 | 
				
			||||||
        }, cancellationToken: cancellationToken);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _ = Task.Run(async () =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                await etcd.LeaseKeepAlive(leaseResponse.ID, cancellationToken);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                logger.LogError($"Lease keep-alive failed: {ex.Message}");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }, cancellationToken);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task UnregisterService(string serviceName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var key = $"/services/{serviceName}";
 | 
					 | 
				
			||||||
        await etcd.DeleteAsync(key);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task<string?> GetServiceUrl(string serviceName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var key = $"/services/{serviceName}";
 | 
					 | 
				
			||||||
        var response = await etcd.GetAsync(key);
 | 
					 | 
				
			||||||
        return response.Kvs.Count == 0 ? null : response.Kvs[0].Value.ToStringUtf8();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
using dotnet_etcd.DependencyInjection;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Configuration;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class RegistryStartup
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public static IServiceCollection AddRegistryService(
 | 
					 | 
				
			||||||
        this IServiceCollection services,
 | 
					 | 
				
			||||||
        IConfiguration configuration,
 | 
					 | 
				
			||||||
        bool addForwarder = true
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        services.AddEtcdClient(options =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            options.ConnectionString = configuration.GetConnectionString("Etcd");
 | 
					 | 
				
			||||||
            options.UseInsecureChannel = configuration.GetValue<bool>("Etcd:Insecure");
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        services.AddSingleton<ServiceRegistry>();
 | 
					 | 
				
			||||||
        services.AddHostedService<RegistryHostedService>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (addForwarder)
 | 
					 | 
				
			||||||
            services.AddHttpForwarder();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return services;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
using Microsoft.Extensions.Configuration;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					 | 
				
			||||||
using NATS.Client.Core;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class Connector
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public static IServiceCollection AddStreamConnection(this IServiceCollection services, IConfiguration configuration)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var connectionString = configuration.GetConnectionString("Stream");
 | 
					 | 
				
			||||||
        if (connectionString is null)
 | 
					 | 
				
			||||||
            throw new ArgumentNullException(nameof(connectionString));
 | 
					 | 
				
			||||||
        services.AddSingleton<INatsConnection>(_ => new NatsConnection(new NatsOpts()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Url = connectionString
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        return services;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -55,7 +55,6 @@
 | 
				
			|||||||
        <PackageReference Include="Quartz" Version="3.14.0"/>
 | 
					        <PackageReference Include="Quartz" Version="3.14.0"/>
 | 
				
			||||||
        <PackageReference Include="Quartz.AspNetCore" Version="3.14.0"/>
 | 
					        <PackageReference Include="Quartz.AspNetCore" Version="3.14.0"/>
 | 
				
			||||||
        <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0"/>
 | 
					        <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0"/>
 | 
				
			||||||
        <PackageReference Include="StackExchange.Redis" Version="2.8.41"/>
 | 
					 | 
				
			||||||
        <PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0"/>
 | 
					        <PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0"/>
 | 
				
			||||||
        <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3"/>
 | 
					        <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3"/>
 | 
				
			||||||
        <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.3" />
 | 
					        <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.3" />
 | 
				
			||||||
@@ -160,6 +159,7 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
					        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
				
			||||||
        <ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj"/>
 | 
					        <ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj"/>
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Sphere.Publisher;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ using DysonNetwork.Shared.Auth;
 | 
				
			|||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.PageData;
 | 
					using DysonNetwork.Shared.PageData;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using DysonNetwork.Sphere;
 | 
					using DysonNetwork.Sphere;
 | 
				
			||||||
using DysonNetwork.Sphere.PageData;
 | 
					using DysonNetwork.Sphere.PageData;
 | 
				
			||||||
using DysonNetwork.Sphere.Startup;
 | 
					using DysonNetwork.Sphere.Startup;
 | 
				
			||||||
@@ -11,15 +10,13 @@ using Microsoft.Extensions.FileProviders;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add metrics and telemetry
 | 
					 | 
				
			||||||
builder.Services.AddAppMetrics();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					
 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -42,6 +39,8 @@ builder.Services.AddTransient<IPageDataProvider, PostPageData>();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -52,8 +51,6 @@ using (var scope = app.Services.CreateScope())
 | 
				
			|||||||
// Configure application middleware pipeline
 | 
					// Configure application middleware pipeline
 | 
				
			||||||
app.ConfigureAppMiddleware(builder.Configuration);
 | 
					app.ConfigureAppMiddleware(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.MapGatewayProxy();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.UseDefaultFiles();
 | 
					app.UseDefaultFiles();
 | 
				
			||||||
app.UseStaticFiles(new StaticFileOptions
 | 
					app.UseStaticFiles(new StaticFileOptions
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc;
 | 
				
			|||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
using Google.Protobuf.WellKnownTypes;
 | 
					using Google.Protobuf.WellKnownTypes;
 | 
				
			||||||
using Microsoft.AspNetCore.Http.HttpResults;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Sphere.Realm;
 | 
					namespace DysonNetwork.Sphere.Realm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
using OpenTelemetry.Metrics;
 | 
					 | 
				
			||||||
using OpenTelemetry.Trace;
 | 
					 | 
				
			||||||
using Prometheus;
 | 
					 | 
				
			||||||
using Prometheus.SystemMetrics;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Sphere.Startup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class MetricsConfiguration
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public static IServiceCollection AddAppMetrics(this IServiceCollection services)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Prometheus
 | 
					 | 
				
			||||||
        services.UseHttpClientMetrics();
 | 
					 | 
				
			||||||
        services.AddHealthChecks();
 | 
					 | 
				
			||||||
        services.AddSystemMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusEntityFrameworkMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusAspNetCoreMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusHttpClientMetrics();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // OpenTelemetry
 | 
					 | 
				
			||||||
        services.AddOpenTelemetry()
 | 
					 | 
				
			||||||
            .WithTracing(tracing =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                tracing
 | 
					 | 
				
			||||||
                    .AddAspNetCoreInstrumentation()
 | 
					 | 
				
			||||||
                    .AddHttpClientInstrumentation()
 | 
					 | 
				
			||||||
                    .AddOtlpExporter();
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .WithMetrics(metrics =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                metrics
 | 
					 | 
				
			||||||
                    .AddAspNetCoreInstrumentation()
 | 
					 | 
				
			||||||
                    .AddHttpClientInstrumentation()
 | 
					 | 
				
			||||||
                    .AddRuntimeInstrumentation()
 | 
					 | 
				
			||||||
                    .AddOtlpExporter();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return services;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -39,8 +39,6 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Sphere",
 | 
					    "Name": "DysonNetwork.Sphere",
 | 
				
			||||||
    "Url": "https://localhost:7099",
 | 
					    "Url": "https://localhost:7099"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Gateway", "Dys
 | 
				
			|||||||
EndProject
 | 
					EndProject
 | 
				
			||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Develop", "DysonNetwork.Develop\DysonNetwork.Develop.csproj", "{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}"
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Develop", "DysonNetwork.Develop\DysonNetwork.Develop.csproj", "{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}"
 | 
				
			||||||
EndProject
 | 
					EndProject
 | 
				
			||||||
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Control", "DysonNetwork.Control\DysonNetwork.Control.csproj", "{7FFED190-51C7-4302-A8B5-96C839463458}"
 | 
				
			||||||
 | 
					EndProject
 | 
				
			||||||
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.ServiceDefaults", "DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj", "{877AAD96-C257-4305-9F1C-C9D9C9BEE615}"
 | 
				
			||||||
 | 
					EndProject
 | 
				
			||||||
Global
 | 
					Global
 | 
				
			||||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
						GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
				
			||||||
		Debug|Any CPU = Debug|Any CPU
 | 
							Debug|Any CPU = Debug|Any CPU
 | 
				
			||||||
@@ -54,5 +58,13 @@ Global
 | 
				
			|||||||
		{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
							{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
		{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
							{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
		{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
							{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
	EndGlobalSection
 | 
						EndGlobalSection
 | 
				
			||||||
EndGlobal
 | 
					EndGlobal
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@
 | 
				
			|||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F0f_003F51443844_003FApnSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F0f_003F51443844_003FApnSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArgumentNullException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003Faf_003F30ff0e5c_003FArgumentNullException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArgumentNullException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003Faf_003F30ff0e5c_003FArgumentNullException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArraySegment_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003F44_003Fb46e5e1b_003FArraySegment_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArraySegment_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003F44_003Fb46e5e1b_003FArraySegment_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAspireNatsClientExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F991136f2b57436db7b12f422388f1384993f9288637d2c461e0e9da6b55a88b_003FAspireNatsClientExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationHandler_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1f1354e4dbf943ecb04840af5ff9a527fa20_003F5d_003F1fb111f6_003FAuthenticationHandler_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationHandler_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1f1354e4dbf943ecb04840af5ff9a527fa20_003F5d_003F1fb111f6_003FAuthenticationHandler_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9b24a56e61ae4d86a9e8ba13482a2db924600_003F5b_003F9e854504_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9b24a56e61ae4d86a9e8ba13482a2db924600_003F5b_003F9e854504_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
@@ -132,6 +133,7 @@
 | 
				
			|||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffac2de31b5cc4ce09217dd126ac6f67b22000_003F5f_003Fd31c33fc_003FServiceCollectionServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffac2de31b5cc4ce09217dd126ac6f67b22000_003F5f_003Fd31c33fc_003FServiceCollectionServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceDiscoveryServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F2ce4b29a828792eb9a714f13dc06413f31e473999dea7bb4b1116c5fc0b3b3_003FServiceDiscoveryServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fce37be1a06b16c6faa02038d2cc477dd3bca5b217ceeb41c5f2ad45c1bf9_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fce37be1a06b16c6faa02038d2cc477dd3bca5b217ceeb41c5f2ad45c1bf9_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user