使用 .NET Aspire 来编排资源 #7
							
								
								
									
										3
									
								
								.aspire/settings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.aspire/settings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| { | ||||
|   "appHostPath": "../DysonNetwork.Control/DysonNetwork.Control.csproj" | ||||
| } | ||||
							
								
								
									
										35
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # Default container port for ring | ||||
| RING_PORT=8080 | ||||
|  | ||||
| # Default container port for pass | ||||
| PASS_PORT=8080 | ||||
|  | ||||
| # Default container port for drive | ||||
| DRIVE_PORT=8080 | ||||
|  | ||||
| # Default container port for sphere | ||||
| SPHERE_PORT=8080 | ||||
|  | ||||
| # Default container port for develop | ||||
| DEVELOP_PORT=8080 | ||||
|  | ||||
| # Parameter cache-password | ||||
| CACHE_PASSWORD=KS3jSPaU9e | ||||
|  | ||||
| # Parameter queue-password | ||||
| QUEUE_PASSWORD=8xEECa4ckz | ||||
|  | ||||
| # Container image name for ring | ||||
| RING_IMAGE=ring:latest | ||||
|  | ||||
| # Container image name for pass | ||||
| PASS_IMAGE=pass:latest | ||||
|  | ||||
| # Container image name for drive | ||||
| DRIVE_IMAGE=drive:latest | ||||
|  | ||||
| # Container image name for sphere | ||||
| SPHERE_IMAGE=sphere:latest | ||||
|  | ||||
| # Container image name for develop | ||||
| DEVELOP_IMAGE=develop:latest | ||||
							
								
								
									
										190
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										190
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| name: Build and Push Microservices | ||||
| name: Aspire Publish Workflow | ||||
|  | ||||
| on: | ||||
|   push: | ||||
| @@ -7,7 +7,7 @@ on: | ||||
|   workflow_dispatch: | ||||
|  | ||||
| jobs: | ||||
|   build-sphere: | ||||
|   publish: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
| @@ -15,174 +15,46 @@ jobs: | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Setup NBGV | ||||
|         uses: dotnet/nbgv@master | ||||
|         id: nbgv | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Log in to GitHub Container Registry | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - name: Build and push DysonNetwork.Sphere Docker image | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           file: DysonNetwork.Sphere/Dockerfile | ||||
|           context: . | ||||
|           push: true | ||||
|           tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-sphere:latest | ||||
|           platforms: linux/amd64 | ||||
|  | ||||
|   build-pass: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Setup .NET | ||||
|         uses: actions/setup-dotnet@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Setup NBGV | ||||
|         uses: dotnet/nbgv@master | ||||
|         id: nbgv | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Log in to GitHub Container Registry | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - name: Build and push DysonNetwork.Pass Docker image | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           file: DysonNetwork.Pass/Dockerfile | ||||
|           context: . | ||||
|           push: true | ||||
|           tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-pass:latest | ||||
|           platforms: linux/amd64 | ||||
|           dotnet-version: "9.0.x" | ||||
|  | ||||
|   build-ring: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Setup NBGV | ||||
|         uses: dotnet/nbgv@master | ||||
|         id: nbgv | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Log in to GitHub Container Registry | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - name: Build and push DysonNetwork.Pusher Docker image | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           file: DysonNetwork.Ring/Dockerfile | ||||
|           context: . | ||||
|           push: true | ||||
|           tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-ring:latest | ||||
|           platforms: linux/amd64 | ||||
|  | ||||
|   build-drive: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Setup NBGV | ||||
|         uses: dotnet/nbgv@master | ||||
|         id: nbgv | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Log in to GitHub Container Registry | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - name: Build and push DysonNetwork.Drive Docker image | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           file: DysonNetwork.Drive/Dockerfile | ||||
|           context: . | ||||
|           push: true | ||||
|           tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-drive:latest | ||||
|           platforms: linux/amd64 | ||||
|       - name: Install Aspire CLI | ||||
|         run: dotnet tool install -g Aspire.Cli --prerelease | ||||
|  | ||||
|   build-gateway: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Setup NBGV | ||||
|         uses: dotnet/nbgv@master | ||||
|         id: nbgv | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Log in to GitHub Container Registry | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - name: Build and push DysonNetwork.Gateway Docker image | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           file: DysonNetwork.Gateway/Dockerfile | ||||
|           context: . | ||||
|           push: true | ||||
|           tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-gateway:latest | ||||
|           platforms: linux/amd64 | ||||
|       - name: Build and Publish Aspire Application | ||||
|         run: aspire publish --project ./DysonNetwork.Control/DysonNetwork.Control.csproj --output publish | ||||
|  | ||||
|   build-develop: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Tag and Push Images | ||||
|         run: | | ||||
|           IMAGES=( "sphere" "pass" "ring" "drive" "develop" ) | ||||
|  | ||||
|           for image in "${IMAGES[@]}"; do | ||||
|             IMAGE_NAME="ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-$image:alpha" | ||||
|             SOURCE_IMAGE_NAME="$image:latest" # Aspire's default local image name | ||||
|  | ||||
|             echo "Tagging and pushing $SOURCE_IMAGE_NAME to $IMAGE_NAME..." | ||||
|             docker tag $SOURCE_IMAGE_NAME $IMAGE_NAME | ||||
|             docker push $IMAGE_NAME | ||||
|           done | ||||
|  | ||||
|       - name: Upload Aspire Publish Directory | ||||
|         uses: actions/upload-artifact@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Setup NBGV | ||||
|         uses: dotnet/nbgv@master | ||||
|         id: nbgv | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Log in to GitHub Container Registry | ||||
|         uses: docker/login-action@v3 | ||||
|           name: aspire-publish-output | ||||
|           path: ./publish/ | ||||
|  | ||||
|       - name: Upload Docker Compose file | ||||
|         uses: actions/upload-artifact@v3 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: ${{ github.actor }} | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - name: Build and push DysonNetwork.Develop Docker image | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           file: DysonNetwork.Develop/Dockerfile | ||||
|           context: . | ||||
|           push: true | ||||
|           tags: ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-develop:latest | ||||
|           platforms: linux/amd64 | ||||
|           name: docker-compose-output | ||||
|           path: ./publish/docker-compose.yml | ||||
|   | ||||
							
								
								
									
										77
									
								
								DysonNetwork.Control/AppHost.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								DysonNetwork.Control/AppHost.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| using Aspire.Hosting.Yarp.Transforms; | ||||
|  | ||||
| var builder = DistributedApplication.CreateBuilder(args); | ||||
|  | ||||
| // Database was configured separately in each service. | ||||
| // var database = builder.AddPostgres("database"); | ||||
|  | ||||
| var cache = builder.AddRedis("cache"); | ||||
| var queue = builder.AddNats("queue").WithJetStream(); | ||||
|  | ||||
| var ringService = builder.AddProject<Projects.DysonNetwork_Ring>("ring") | ||||
|     .WithReference(queue) | ||||
|     .WithHttpHealthCheck() | ||||
|     .WithEndpoint(5001, 5001, "https", name: "grpc"); | ||||
| var passService = builder.AddProject<Projects.DysonNetwork_Pass>("pass") | ||||
|     .WithReference(cache) | ||||
|     .WithReference(queue) | ||||
|     .WithReference(ringService) | ||||
|     .WithHttpHealthCheck() | ||||
|     .WithEndpoint(5001, 5001, "https", name: "grpc"); | ||||
| var driveService = builder.AddProject<Projects.DysonNetwork_Drive>("drive") | ||||
|     .WithReference(cache) | ||||
|     .WithReference(queue) | ||||
|     .WithReference(passService) | ||||
|     .WithReference(ringService) | ||||
|     .WithHttpHealthCheck() | ||||
|     .WithEndpoint(5001, 5001, "https", name: "grpc"); | ||||
| var sphereService = builder.AddProject<Projects.DysonNetwork_Sphere>("sphere") | ||||
|     .WithReference(cache) | ||||
|     .WithReference(queue) | ||||
|     .WithReference(passService) | ||||
|     .WithReference(ringService) | ||||
|     .WithHttpHealthCheck() | ||||
|     .WithEndpoint(5001, 5001, "https", name: "grpc"); | ||||
| var developService = builder.AddProject<Projects.DysonNetwork_Develop>("develop") | ||||
|     .WithReference(cache) | ||||
|     .WithReference(passService) | ||||
|     .WithReference(ringService) | ||||
|     .WithHttpHealthCheck() | ||||
|     .WithEndpoint(5001, 5001, "https", name: "grpc"); | ||||
|  | ||||
| // Extra double-ended references | ||||
| ringService.WithReference(passService); | ||||
|  | ||||
| builder.AddYarp("gateway") | ||||
|     .WithHostPort(5000) | ||||
|     .WithConfiguration(yarp => | ||||
|     { | ||||
|         var ringCluster = yarp.AddCluster(ringService.GetEndpoint("http")); | ||||
|         yarp.AddRoute("/ws", ringCluster); | ||||
|         yarp.AddRoute("/ring/{**catch-all}", ringCluster) | ||||
|             .WithTransformPathRemovePrefix("/ring") | ||||
|             .WithTransformPathPrefix("/api"); | ||||
|         var passCluster = yarp.AddCluster(passService.GetEndpoint("http")); | ||||
|         yarp.AddRoute("/.well-known/openid-configuration", passCluster); | ||||
|         yarp.AddRoute("/.well-known/jwks", passCluster); | ||||
|         yarp.AddRoute("/id/{**catch-all}", passCluster) | ||||
|             .WithTransformPathRemovePrefix("/id") | ||||
|             .WithTransformPathPrefix("/api"); | ||||
|         var driveCluster = yarp.AddCluster(driveService.GetEndpoint("http")); | ||||
|         yarp.AddRoute("/api/tus", driveCluster); | ||||
|         yarp.AddRoute("/drive/{**catch-all}", driveCluster) | ||||
|             .WithTransformPathRemovePrefix("/drive") | ||||
|             .WithTransformPathPrefix("/api"); | ||||
|         var sphereCluster = yarp.AddCluster(sphereService.GetEndpoint("http")); | ||||
|         yarp.AddRoute("/sphere/{**catch-all}", sphereCluster) | ||||
|             .WithTransformPathRemovePrefix("/sphere") | ||||
|             .WithTransformPathPrefix("/api"); | ||||
|         var developCluster = yarp.AddCluster(developService.GetEndpoint("http")); | ||||
|         yarp.AddRoute("/develop/{**catch-all}", developCluster) | ||||
|             .WithTransformPathRemovePrefix("/develop") | ||||
|             .WithTransformPathPrefix("/api"); | ||||
|     }); | ||||
|  | ||||
| builder.AddDockerComposeEnvironment("docker-compose"); | ||||
|  | ||||
| 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.Docker" Version="9.4.2-preview.1.25428.12" /> | ||||
|         <PackageReference Include="Aspire.Hosting.Nats" Version="9.4.2" /> | ||||
|         <PackageReference Include="Aspire.Hosting.Redis" Version="9.4.2" /> | ||||
|         <PackageReference Include="Aspire.Hosting.Yarp" Version="9.4.2-preview.1.25428.12" /> | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|       <ProjectReference Include="..\DysonNetwork.Develop\DysonNetwork.Develop.csproj" /> | ||||
|       <ProjectReference Include="..\DysonNetwork.Drive\DysonNetwork.Drive.csproj" /> | ||||
|       <ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj" /> | ||||
|       <ProjectReference Include="..\DysonNetwork.Ring\DysonNetwork.Ring.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> | ||||
|     <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" /> | ||||
|     <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" /> | ||||
|   </ItemGroup> | ||||
|   | ||||
|   | ||||
| @@ -1,17 +1,16 @@ | ||||
| using DysonNetwork.Develop; | ||||
| using DysonNetwork.Shared.Auth; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Develop.Startup; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| builder.AddServiceDefaults(); | ||||
|  | ||||
| builder.ConfigureAppKestrel(builder.Configuration); | ||||
|  | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppAuthentication(); | ||||
| builder.Services.AddAppSwagger(); | ||||
| @@ -22,6 +21,8 @@ builder.Services.AddDriveService(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| app.MapDefaultEndpoints(); | ||||
|  | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| { | ||||
|     var db = scope.ServiceProvider.GetRequiredService<AppDatabase>(); | ||||
|   | ||||
| @@ -20,11 +20,6 @@ public static class ServiceCollectionExtensions | ||||
|         services.AddDbContext<AppDatabase>(); | ||||
|         services.AddSingleton<IClock>(SystemClock.Instance); | ||||
|         services.AddHttpContextAccessor(); | ||||
|         services.AddSingleton<IConnectionMultiplexer>(_ => | ||||
|         { | ||||
|             var connection = configuration.GetConnectionString("FastRetrieve")!; | ||||
|             return ConnectionMultiplexer.Connect(connection); | ||||
|         }); | ||||
|         services.AddSingleton<ICacheService, CacheServiceRedis>(); | ||||
|  | ||||
|         services.AddHttpClient(); | ||||
|   | ||||
| @@ -10,10 +10,7 @@ | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379", | ||||
|     "Stream": "nats.orb.local:4222" | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "KnownProxies": [ | ||||
|     "127.0.0.1", | ||||
| @@ -24,8 +21,6 @@ | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Develop", | ||||
|     "Url": "https://localhost:7192", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|     "Url": "https://localhost:7192" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -66,6 +66,7 @@ | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|       <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" /> | ||||
|       <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" /> | ||||
|     </ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -5,18 +5,18 @@ using DysonNetwork.Shared.Auth; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.PageData; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using tusdotnet.Stores; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| builder.AddServiceDefaults(); | ||||
|  | ||||
| // Configure Kestrel and server options | ||||
| builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue); | ||||
|  | ||||
| // Add application services | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
|  | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppRateLimiting(); | ||||
| builder.Services.AddAppAuthentication(); | ||||
| @@ -39,6 +39,8 @@ builder.Services.AddTransient<IPageDataProvider, VersionPageData>(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| app.MapDefaultEndpoints(); | ||||
|  | ||||
| // Run database migrations | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| { | ||||
| @@ -51,8 +53,6 @@ var tusDiskStore = app.Services.GetRequiredService<TusDiskStore>(); | ||||
| // Configure application middleware pipeline | ||||
| app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath); | ||||
|  | ||||
| app.MapGatewayProxy(); | ||||
|  | ||||
| app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html")); | ||||
|  | ||||
| // Configure gRPC | ||||
|   | ||||
| @@ -17,11 +17,6 @@ public static class ServiceCollectionExtensions | ||||
|     public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) | ||||
|     { | ||||
|         services.AddDbContext<AppDatabase>(); // Assuming you'll have an AppDatabase | ||||
|         services.AddSingleton<IConnectionMultiplexer>(_ => | ||||
|         { | ||||
|             var connection = configuration.GetConnectionString("FastRetrieve")!; | ||||
|             return ConnectionMultiplexer.Connect(connection); | ||||
|         }); | ||||
|         services.AddSingleton<IClock>(SystemClock.Instance); | ||||
|         services.AddHttpContextAccessor(); | ||||
|         services.AddSingleton<ICacheService, CacheServiceRedis>(); // Uncomment if you have CacheServiceRedis | ||||
|   | ||||
| @@ -10,10 +10,7 @@ | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379", | ||||
|     "Stream": "nats.orb.local:4222" | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
| @@ -131,8 +128,6 @@ | ||||
|   ], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Drive", | ||||
|     "Url": "https://localhost:7092", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|     "Url": "https://localhost:7092" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,108 +0,0 @@ | ||||
| using System.Text; | ||||
| using System.Text.Json.Serialization; | ||||
| using dotnet_etcd.interfaces; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Yarp.ReverseProxy.Configuration; | ||||
|  | ||||
| namespace DysonNetwork.Gateway.Controllers; | ||||
|  | ||||
| [ApiController] | ||||
| [Route("/.well-known")] | ||||
| public class WellKnownController( | ||||
|     IConfiguration configuration, | ||||
|     IProxyConfigProvider proxyConfigProvider, | ||||
|     IEtcdClient etcdClient) | ||||
|     : ControllerBase | ||||
| { | ||||
|     public class IpCheckResponse | ||||
|     { | ||||
|         [JsonPropertyName("remote_ip")] public string? RemoteIp { get; set; } | ||||
|         [JsonPropertyName("x_forwarded_for")] public string? XForwardedFor { get; set; } | ||||
|         [JsonPropertyName("x_forwarded_proto")] public string? XForwardedProto { get; set; } | ||||
|         [JsonPropertyName("x_forwarded_host")] public string? XForwardedHost { get; set; } | ||||
|         [JsonPropertyName("x_real_ip")] public string? XRealIp { get; set; } | ||||
|     } | ||||
|      | ||||
|     [HttpGet("ip-check")] | ||||
|     public ActionResult<IpCheckResponse> GetIpCheck() | ||||
|     { | ||||
|         var ip = HttpContext.Connection.RemoteIpAddress?.ToString(); | ||||
|  | ||||
|         var xForwardedFor = Request.Headers["X-Forwarded-For"].FirstOrDefault(); | ||||
|         var xForwardedProto = Request.Headers["X-Forwarded-Proto"].FirstOrDefault(); | ||||
|         var xForwardedHost = Request.Headers["X-Forwarded-Host"].FirstOrDefault(); | ||||
|         var realIp = Request.Headers["X-Real-IP"].FirstOrDefault(); | ||||
|  | ||||
|         return Ok(new IpCheckResponse | ||||
|         { | ||||
|             RemoteIp = ip, | ||||
|             XForwardedFor = xForwardedFor, | ||||
|             XForwardedProto = xForwardedProto, | ||||
|             XForwardedHost = xForwardedHost, | ||||
|             XRealIp = realIp | ||||
|         }); | ||||
|     } | ||||
|      | ||||
|     [HttpGet("domains")] | ||||
|     public IActionResult GetDomainMappings() | ||||
|     { | ||||
|         var domainMappings = configuration.GetSection("DomainMappings").GetChildren() | ||||
|             .ToDictionary(x => x.Key, x => x.Value); | ||||
|         return Ok(domainMappings); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("services")] | ||||
|     public IActionResult GetServices() | ||||
|     { | ||||
|         var local = configuration.GetValue<bool>("LocalMode"); | ||||
|         var response = etcdClient.GetRange("/services/"); | ||||
|         var kvs = response.Kvs; | ||||
|  | ||||
|         var serviceMap = kvs.ToDictionary( | ||||
|             kv => Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""), | ||||
|             kv => Encoding.UTF8.GetString(kv.Value.ToByteArray()) | ||||
|         ); | ||||
|  | ||||
|         if (local) return Ok(serviceMap); | ||||
|          | ||||
|         var domainMappings = configuration.GetSection("DomainMappings").GetChildren() | ||||
|             .ToDictionary(x => x.Key, x => x.Value); | ||||
|         foreach (var (key, _) in serviceMap.ToList()) | ||||
|         { | ||||
|             if (!domainMappings.TryGetValue(key, out var domain)) continue; | ||||
|             if (domain is not null) | ||||
|                 serviceMap[key] = "https://" + domain; | ||||
|         } | ||||
|  | ||||
|         return Ok(serviceMap); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("routes")] | ||||
|     public IActionResult GetProxyRules() | ||||
|     { | ||||
|         var config = proxyConfigProvider.GetConfig(); | ||||
|         var rules = config.Routes.Select(r => new | ||||
|         { | ||||
|             r.RouteId, | ||||
|             r.ClusterId, | ||||
|             Match = new | ||||
|             { | ||||
|                 r.Match.Path, | ||||
|                 Hosts = r.Match.Hosts != null ? string.Join(", ", r.Match.Hosts) : null | ||||
|             }, | ||||
|             Transforms = r.Transforms?.Select(t => t.Select(kv => $"{kv.Key}: {kv.Value}").ToList()) | ||||
|         }).ToList(); | ||||
|  | ||||
|         var clusters = config.Clusters.Select(c => new | ||||
|         { | ||||
|             c.ClusterId, | ||||
|             Destinations = c.Destinations?.Select(d => new | ||||
|             { | ||||
|                 d.Key, | ||||
|                 d.Value.Address | ||||
|             }).ToList() | ||||
|         }).ToList(); | ||||
|  | ||||
|         return Ok(new { Rules = rules, Clusters = clusters }); | ||||
|     } | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base | ||||
| USER $APP_UID | ||||
| WORKDIR /app | ||||
| EXPOSE 8080 | ||||
| EXPOSE 8081 | ||||
|  | ||||
| FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build | ||||
| ARG BUILD_CONFIGURATION=Release | ||||
| WORKDIR /src | ||||
| COPY ["DysonNetwork.Gateway/DysonNetwork.Gateway.csproj", "DysonNetwork.Gateway/"] | ||||
| RUN dotnet restore "DysonNetwork.Gateway/DysonNetwork.Gateway.csproj" | ||||
| COPY . . | ||||
| WORKDIR "/src/DysonNetwork.Gateway" | ||||
| RUN dotnet build "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/build | ||||
|  | ||||
| FROM build AS publish | ||||
| ARG BUILD_CONFIGURATION=Release | ||||
| RUN dotnet publish "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false | ||||
|  | ||||
| FROM base AS final | ||||
| WORKDIR /app | ||||
| COPY --from=publish /app/publish . | ||||
| ENTRYPOINT ["dotnet", "DysonNetwork.Gateway.dll"] | ||||
| @@ -1,23 +0,0 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net9.0</TargetFramework> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="dotnet-etcd" Version="8.0.1" /> | ||||
|     <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" /> | ||||
|     <PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </PackageReference> | ||||
|     <PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
| @@ -1,36 +0,0 @@ | ||||
| using DysonNetwork.Gateway.Startup; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using Microsoft.AspNetCore.HttpOverrides; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); | ||||
| builder.WebHost.ConfigureKestrel(options => | ||||
| { | ||||
|     options.Limits.MaxRequestBodySize = long.MaxValue; | ||||
|     options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); | ||||
|     options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); | ||||
| }); | ||||
|  | ||||
| // Add services to the container. | ||||
| builder.Services.AddGateway(builder.Configuration); | ||||
| builder.Services.AddControllers(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| app.ConfigureForwardedHeaders(app.Configuration); | ||||
|  | ||||
| app.UseRequestTimeouts(); | ||||
| app.UseCors(opts => | ||||
|     opts.SetIsOriginAllowed(_ => true) | ||||
|         .WithExposedHeaders("*") | ||||
|         .WithHeaders("*") | ||||
|         .AllowCredentials() | ||||
|         .AllowAnyHeader() | ||||
|         .AllowAnyMethod() | ||||
| ); | ||||
|  | ||||
| app.MapControllers(); | ||||
| app.MapReverseProxy(); | ||||
|  | ||||
| app.Run(); | ||||
| @@ -1,23 +0,0 @@ | ||||
| { | ||||
|   "$schema": "https://json.schemastore.org/launchsettings.json", | ||||
|   "profiles": { | ||||
|     "http": { | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": false, | ||||
|       "applicationUrl": "http://localhost:5094", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|     }, | ||||
|     "https": { | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": false, | ||||
|       "applicationUrl": "https://localhost:7034;http://0.0.0.0:5094", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,239 +0,0 @@ | ||||
| using System.Text; | ||||
| using dotnet_etcd.interfaces; | ||||
| using Yarp.ReverseProxy.Configuration; | ||||
| using Yarp.ReverseProxy.Forwarder; | ||||
|  | ||||
| namespace DysonNetwork.Gateway; | ||||
|  | ||||
| public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable | ||||
| { | ||||
|     private readonly object _lock = new(); | ||||
|     private readonly IEtcdClient _etcdClient; | ||||
|     private readonly IConfiguration _configuration; | ||||
|     private readonly ILogger<RegistryProxyConfigProvider> _logger; | ||||
|     private readonly CancellationTokenSource _watchCts = new(); | ||||
|     private CancellationTokenSource _cts; | ||||
|     private IProxyConfig _config; | ||||
|  | ||||
|     public RegistryProxyConfigProvider( | ||||
|         IEtcdClient etcdClient, | ||||
|         IConfiguration configuration, | ||||
|         ILogger<RegistryProxyConfigProvider> logger | ||||
|     ) | ||||
|     { | ||||
|         _etcdClient = etcdClient; | ||||
|         _configuration = configuration; | ||||
|         _logger = logger; | ||||
|         _cts = new CancellationTokenSource(); | ||||
|         _config = LoadConfig(); | ||||
|  | ||||
|         // Watch for changes in etcd | ||||
|         _etcdClient.WatchRange("/services/", _ => | ||||
|         { | ||||
|             _logger.LogInformation("Etcd configuration changed. Reloading proxy config."); | ||||
|             ReloadConfig(); | ||||
|         }, cancellationToken: _watchCts.Token); | ||||
|     } | ||||
|  | ||||
|     public IProxyConfig GetConfig() => _config; | ||||
|  | ||||
|     private void ReloadConfig() | ||||
|     { | ||||
|         lock (_lock) | ||||
|         { | ||||
|             var oldCts = _cts; | ||||
|             _cts = new CancellationTokenSource(); | ||||
|             _config = LoadConfig(); | ||||
|             oldCts.Cancel(); | ||||
|             oldCts.Dispose(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private IProxyConfig LoadConfig() | ||||
|     { | ||||
|         _logger.LogInformation("Generating new proxy config."); | ||||
|         var response = _etcdClient.GetRange("/services/"); | ||||
|         var kvs = response.Kvs; | ||||
|  | ||||
|         var serviceMap = kvs.ToDictionary( | ||||
|             kv => Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""), | ||||
|             kv => Encoding.UTF8.GetString(kv.Value.ToByteArray()) | ||||
|         ); | ||||
|  | ||||
|         var clusters = new List<ClusterConfig>(); | ||||
|         var routes = new List<RouteConfig>(); | ||||
|  | ||||
|         var domainMappings = _configuration.GetSection("DomainMappings").GetChildren() | ||||
|             .ToDictionary(x => x.Key, x => x.Value); | ||||
|  | ||||
|         var pathAliases = _configuration.GetSection("PathAliases").GetChildren() | ||||
|             .ToDictionary(x => x.Key, x => x.Value); | ||||
|  | ||||
|         var directRoutes = _configuration.GetSection("DirectRoutes").Get<List<DirectRouteConfig>>() ?? | ||||
|                            []; | ||||
|  | ||||
|         _logger.LogInformation("Indexing {ServiceCount} services from Etcd.", kvs.Count); | ||||
|  | ||||
|         var gatewayServiceName = _configuration["Service:Name"]; | ||||
|  | ||||
|         // Add direct routes | ||||
|         foreach (var directRoute in directRoutes) | ||||
|         { | ||||
|             if (serviceMap.TryGetValue(directRoute.Service, out var serviceUrl)) | ||||
|             { | ||||
|                 var existingCluster = clusters.FirstOrDefault(c => c.ClusterId == directRoute.Service); | ||||
|                 if (existingCluster is null) | ||||
|                 { | ||||
|                     var cluster = new ClusterConfig | ||||
|                     { | ||||
|                         ClusterId = directRoute.Service, | ||||
|                         Destinations = new Dictionary<string, DestinationConfig> | ||||
|                         { | ||||
|                             { "destination1", new DestinationConfig { Address = serviceUrl } } | ||||
|                         }, | ||||
|                     }; | ||||
|                     clusters.Add(cluster); | ||||
|                 } | ||||
|  | ||||
|                 var route = new RouteConfig | ||||
|                 { | ||||
|                     RouteId = $"direct-{directRoute.Service}-{directRoute.Path.Replace("/", "-")}", | ||||
|                     ClusterId = directRoute.Service, | ||||
|                     Match = new RouteMatch { Path = directRoute.Path }, | ||||
|                 }; | ||||
|                 routes.Add(route); | ||||
|                 _logger.LogInformation("    Added Direct Route: {Path} -> {Service}", directRoute.Path, | ||||
|                     directRoute.Service); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _logger.LogWarning("    Direct route service {Service} not found in Etcd.", directRoute.Service); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         foreach (var serviceName in serviceMap.Keys) | ||||
|         { | ||||
|             if (serviceName == gatewayServiceName) | ||||
|             { | ||||
|                 _logger.LogInformation("Skipping gateway service: {ServiceName}", serviceName); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             var serviceUrl = serviceMap[serviceName]; | ||||
|  | ||||
|             // Determine the path alias | ||||
|             string? pathAlias; | ||||
|             pathAlias = pathAliases.TryGetValue(serviceName, out var alias) | ||||
|                 ? alias | ||||
|                 : serviceName.Split('.').Last().ToLowerInvariant(); | ||||
|  | ||||
|             _logger.LogInformation("  Service: {ServiceName}, URL: {ServiceUrl}, Path Alias: {PathAlias}", serviceName, | ||||
|                 serviceUrl, pathAlias); | ||||
|  | ||||
|             // Check if the cluster already exists | ||||
|             var existingCluster = clusters.FirstOrDefault(c => c.ClusterId == serviceName); | ||||
|             if (existingCluster == null) | ||||
|             { | ||||
|                 var cluster = new ClusterConfig | ||||
|                 { | ||||
|                     ClusterId = serviceName, | ||||
|                     Destinations = new Dictionary<string, DestinationConfig> | ||||
|                     { | ||||
|                         { "destination1", new DestinationConfig { Address = serviceUrl } } | ||||
|                     } | ||||
|                 }; | ||||
|                 clusters.Add(cluster); | ||||
|                 _logger.LogInformation("  Added Cluster: {ServiceName}", serviceName); | ||||
|             } | ||||
|             else if (existingCluster.Destinations is not null) | ||||
|             { | ||||
|                 // Create a new cluster with merged destinations | ||||
|                 var newDestinations = new Dictionary<string, DestinationConfig>(existingCluster.Destinations) | ||||
|                 { | ||||
|                     { | ||||
|                         $"destination{existingCluster.Destinations.Count + 1}", | ||||
|                         new DestinationConfig { Address = serviceUrl } | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 var mergedCluster = new ClusterConfig | ||||
|                 { | ||||
|                     ClusterId = serviceName, | ||||
|                     Destinations = newDestinations | ||||
|                 }; | ||||
|  | ||||
|                 // Replace the existing cluster with the merged one | ||||
|                 var index = clusters.IndexOf(existingCluster); | ||||
|                 clusters[index] = mergedCluster; | ||||
|  | ||||
|                 _logger.LogInformation("  Updated Cluster {ServiceName} with {DestinationCount} destinations", | ||||
|                     serviceName, mergedCluster.Destinations.Count); | ||||
|             } | ||||
|  | ||||
|             // Host-based routing | ||||
|             if (domainMappings.TryGetValue(serviceName, out var domain) && domain is not null) | ||||
|             { | ||||
|                 var hostRoute = new RouteConfig | ||||
|                 { | ||||
|                     RouteId = $"{serviceName}-host", | ||||
|                     ClusterId = serviceName, | ||||
|                     Match = new RouteMatch | ||||
|                     { | ||||
|                         Hosts = [domain], | ||||
|                         Path = "/{**catch-all}" | ||||
|                     }, | ||||
|                 }; | ||||
|                 routes.Add(hostRoute); | ||||
|                 _logger.LogInformation("    Added Host-based Route: {Host}", domain); | ||||
|             } | ||||
|  | ||||
|             // Path-based routing | ||||
|             var pathRoute = new RouteConfig | ||||
|             { | ||||
|                 RouteId = $"{serviceName}-path", | ||||
|                 ClusterId = serviceName, | ||||
|                 Match = new RouteMatch { Path = $"/{pathAlias}/{{**catch-all}}" }, | ||||
|                 Transforms = new List<Dictionary<string, string>> | ||||
|                 { | ||||
|                     new() { { "PathRemovePrefix", $"/{pathAlias}" } }, | ||||
|                     new() { { "PathPrefix", "/api" } }, | ||||
|                 }, | ||||
|                 Timeout = TimeSpan.FromSeconds(5) | ||||
|             }; | ||||
|             routes.Add(pathRoute); | ||||
|             _logger.LogInformation("    Added Path-based Route: {Path}", pathRoute.Match.Path); | ||||
|         } | ||||
|  | ||||
|         return new CustomProxyConfig( | ||||
|             routes, | ||||
|             clusters, | ||||
|             new Microsoft.Extensions.Primitives.CancellationChangeToken(_cts.Token) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     private class CustomProxyConfig( | ||||
|         IReadOnlyList<RouteConfig> routes, | ||||
|         IReadOnlyList<ClusterConfig> clusters, | ||||
|         Microsoft.Extensions.Primitives.IChangeToken changeToken | ||||
|     ) | ||||
|         : IProxyConfig | ||||
|     { | ||||
|         public IReadOnlyList<RouteConfig> Routes { get; } = routes; | ||||
|         public IReadOnlyList<ClusterConfig> Clusters { get; } = clusters; | ||||
|         public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = changeToken; | ||||
|     } | ||||
|  | ||||
|     public record DirectRouteConfig | ||||
|     { | ||||
|         public required string Path { get; set; } | ||||
|         public required string Service { get; set; } | ||||
|     } | ||||
|  | ||||
|     public virtual void Dispose() | ||||
|     { | ||||
|         _cts.Cancel(); | ||||
|         _cts.Dispose(); | ||||
|         _watchCts.Cancel(); | ||||
|         _watchCts.Dispose(); | ||||
|     } | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| using System.Net.Security; | ||||
| using System.Security.Cryptography.X509Certificates; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Yarp.ReverseProxy.Configuration; | ||||
| using Yarp.ReverseProxy.Transforms; | ||||
|  | ||||
| namespace DysonNetwork.Gateway.Startup; | ||||
|  | ||||
| public static class ServiceCollectionExtensions | ||||
| { | ||||
|     public static IServiceCollection AddGateway(this IServiceCollection services, IConfiguration configuration) | ||||
|     { | ||||
|         services.AddRequestTimeouts(); | ||||
|  | ||||
|         services | ||||
|             .AddReverseProxy() | ||||
|             .ConfigureHttpClient((context, handler) => | ||||
|             { | ||||
|                 // var caCert = X509CertificateLoader.LoadCertificateFromFile(configuration["CaCert"]!); | ||||
|                 handler.SslOptions = new SslClientAuthenticationOptions | ||||
|                 { | ||||
|                     RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true | ||||
|                 }; | ||||
|             }) | ||||
|             .AddTransforms(context => | ||||
|             { | ||||
|                 context.CopyRequestHeaders = true; | ||||
|                 context.AddOriginalHost(); | ||||
|                 context.AddForwarded(action: ForwardedTransformActions.Set); | ||||
|                 context.AddXForwarded(action: ForwardedTransformActions.Set); | ||||
|             }); | ||||
|  | ||||
|         services.AddRegistryService(configuration, addForwarder: false); | ||||
|         services.AddSingleton<IProxyConfigProvider, RegistryProxyConfigProvider>(); | ||||
|  | ||||
|         return services; | ||||
|     } | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| using DysonNetwork.Shared.Data; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| namespace DysonNetwork.Gateway; | ||||
|  | ||||
| [ApiController] | ||||
| [Route("/api/version")] | ||||
| public class VersionController : ControllerBase | ||||
| { | ||||
|     [HttpGet] | ||||
|     public IActionResult Get() | ||||
|     { | ||||
|         return Ok(new AppVersion | ||||
|         { | ||||
|             Version = ThisAssembly.AssemblyVersion, | ||||
|             Commit = ThisAssembly.GitCommitId, | ||||
|             UpdateDate = ThisAssembly.GitCommitDate | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| { | ||||
|   "LocalMode": true, | ||||
|   "CaCert": "../Certificates/ca.crt", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "Etcd": "etcd.orb.local:2379" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Gateway", | ||||
|     "Url": "https://localhost:7034" | ||||
|   }, | ||||
|   "DomainMappings": { | ||||
|     "DysonNetwork.Pass": "id.solsynth.dev", | ||||
|     "DysonNetwork.Drive": "drive.solsynth.dev", | ||||
|     "DysonNetwork.Ring": "push.solsynth.dev", | ||||
|     "DysonNetwork.Sphere": "sphere.solsynth.dev" | ||||
|   }, | ||||
|   "PathAliases": { | ||||
|     "DysonNetwork.Pass": "id", | ||||
|     "DysonNetwork.Drive": "drive" | ||||
|   }, | ||||
|   "DirectRoutes": [ | ||||
|     { | ||||
|       "Path": "/ws", | ||||
|       "Service": "DysonNetwork.Ring" | ||||
|     }, | ||||
|     { | ||||
|       "Path": "/api/tus", | ||||
|       "Service": "DysonNetwork.Drive" | ||||
|     }, | ||||
|     { | ||||
|       "Path": "/.well-known/openid-configuration", | ||||
|       "Service": "DysonNetwork.Pass" | ||||
|     }, | ||||
|     { | ||||
|       "Path": "/.well-known/jwks", | ||||
|       "Service": "DysonNetwork.Pass" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| { | ||||
|   "version": "1.0", | ||||
|   "publicReleaseRefSpec": ["^refs/heads/main$"], | ||||
|   "cloudBuild": { | ||||
|     "setVersionVariables": true | ||||
|   } | ||||
| } | ||||
| @@ -49,6 +49,7 @@ | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|         <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" /> | ||||
|         <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/> | ||||
|     </ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| using dotnet_etcd; | ||||
| using dotnet_etcd.interfaces; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
|   | ||||
| @@ -4,20 +4,16 @@ using DysonNetwork.Pass.Startup; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.PageData; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| builder.AddServiceDefaults(); | ||||
|  | ||||
| // Configure Kestrel and server options | ||||
| builder.ConfigureAppKestrel(builder.Configuration); | ||||
|  | ||||
| // Add metrics and telemetry | ||||
| builder.Services.AddAppMetrics(); | ||||
|  | ||||
| // Add application services | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppRateLimiting(); | ||||
| builder.Services.AddAppAuthentication(); | ||||
| @@ -41,6 +37,8 @@ builder.Services.AddTransient<IPageDataProvider, AccountPageData>(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| app.MapDefaultEndpoints(); | ||||
|  | ||||
| // Run database migrations | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| { | ||||
| @@ -51,8 +49,6 @@ using (var scope = app.Services.CreateScope()) | ||||
| // Configure application middleware pipeline | ||||
| app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath); | ||||
|  | ||||
| app.MapGatewayProxy(); | ||||
|  | ||||
| app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html")); | ||||
|  | ||||
| // 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; | ||||
|     } | ||||
| } | ||||
| @@ -34,11 +34,6 @@ public static class ServiceCollectionExtensions | ||||
|         services.AddLocalization(options => options.ResourcesPath = "Resources"); | ||||
|  | ||||
|         services.AddDbContext<AppDatabase>(); | ||||
|         services.AddSingleton<IConnectionMultiplexer>(_ => | ||||
|         { | ||||
|             var connection = configuration.GetConnectionString("FastRetrieve")!; | ||||
|             return ConnectionMultiplexer.Connect(connection); | ||||
|         }); | ||||
|         services.AddSingleton<IClock>(SystemClock.Instance); | ||||
|         services.AddHttpContextAccessor(); | ||||
|         services.AddSingleton<ICacheService, CacheServiceRedis>(); | ||||
|   | ||||
| @@ -10,10 +10,7 @@ | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379", | ||||
|     "Stream": "nats.orb.local:4222" | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
| @@ -83,9 +80,7 @@ | ||||
|   ], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Pass", | ||||
|     "Url": "https://localhost:7058", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|     "Url": "https://localhost:7058" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| using System.Collections.Concurrent; | ||||
| using System.Net.WebSockets; | ||||
| using dotnet_etcd.interfaces; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using Grpc.Core; | ||||
| @@ -11,17 +10,14 @@ public class WebSocketService | ||||
| { | ||||
|     private readonly IConfiguration _configuration; | ||||
|     private readonly ILogger<WebSocketService> _logger; | ||||
|     private readonly IEtcdClient _etcdClient; | ||||
|     private readonly IDictionary<string, IWebSocketPacketHandler> _handlerMap; | ||||
|  | ||||
|     public WebSocketService( | ||||
|         IEnumerable<IWebSocketPacketHandler> handlers, | ||||
|         IEtcdClient etcdClient, | ||||
|         ILogger<WebSocketService> logger, | ||||
|         IConfiguration configuration | ||||
|     ) | ||||
|     { | ||||
|         _etcdClient = etcdClient; | ||||
|         _logger = logger; | ||||
|         _configuration = configuration; | ||||
|         _handlerMap = handlers.ToDictionary(h => h.PacketType); | ||||
| @@ -59,8 +55,10 @@ public class WebSocketService | ||||
|         } | ||||
|         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(); | ||||
|         ActiveConnections.TryRemove(key, out _); | ||||
|     } | ||||
| @@ -140,45 +138,24 @@ public class WebSocketService | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 // Get the service URL from etcd for the specified endpoint | ||||
|                 var serviceKey = $"/services/{packet.Endpoint}"; | ||||
|                 var response = await _etcdClient.GetAsync(serviceKey); | ||||
|                 var serviceUrl = "https://" + packet.Endpoint; | ||||
|  | ||||
|                 if (response.Kvs.Count > 0) | ||||
|                 var callInvoker = GrpcClientHelper.CreateCallInvoker(serviceUrl); | ||||
|                 var client = new RingHandlerService.RingHandlerServiceClient(callInvoker); | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     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); | ||||
|  | ||||
|                     try | ||||
|                     await client.ReceiveWebSocketPacketAsync(new ReceiveWebSocketPacketRequest | ||||
|                     { | ||||
|                         await client.ReceiveWebSocketPacketAsync(new ReceiveWebSocketPacketRequest | ||||
|                         { | ||||
|                             Account = currentUser, | ||||
|                             DeviceId = deviceId, | ||||
|                             Packet = packet.ToProtoValue() | ||||
|                         }); | ||||
|                     } | ||||
|                     catch (RpcException ex) | ||||
|                     { | ||||
|                         _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}"); | ||||
|                     } | ||||
|  | ||||
|                     return; | ||||
|                         Account = currentUser, | ||||
|                         DeviceId = deviceId, | ||||
|                         Packet = packet.ToProtoValue() | ||||
|                     }); | ||||
|                 } | ||||
|                 catch (RpcException ex) | ||||
|                 { | ||||
|                     _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}"); | ||||
|                 } | ||||
|  | ||||
|                 _logger.LogWarning($"No service registered for endpoint: {packet.Endpoint}"); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
| @@ -197,4 +174,4 @@ public class WebSocketService | ||||
|             CancellationToken.None | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -42,6 +42,7 @@ | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|       <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" /> | ||||
|       <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" /> | ||||
|     </ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -3,17 +3,16 @@ using DysonNetwork.Ring.Startup; | ||||
| using DysonNetwork.Shared.Auth; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| builder.AddServiceDefaults(); | ||||
|  | ||||
| // Configure Kestrel and server options | ||||
| builder.ConfigureAppKestrel(builder.Configuration); | ||||
|  | ||||
| // Add application services | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppRateLimiting(); | ||||
| builder.Services.AddAppAuthentication(); | ||||
| @@ -32,6 +31,8 @@ builder.Services.AddAppScheduledJobs(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| app.MapDefaultEndpoints(); | ||||
|  | ||||
| // Run database migrations | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| { | ||||
|   | ||||
| @@ -2,7 +2,6 @@ using DysonNetwork.Ring.Connection; | ||||
| using DysonNetwork.Ring.Email; | ||||
| using DysonNetwork.Ring.Notification; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Google.Protobuf.WellKnownTypes; | ||||
| using Grpc.Core; | ||||
| using System.Text.Json; | ||||
|   | ||||
| @@ -2,7 +2,6 @@ using System.Text.Json; | ||||
| using DysonNetwork.Ring.Email; | ||||
| using DysonNetwork.Ring.Notification; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using Google.Protobuf; | ||||
| using NATS.Client.Core; | ||||
|   | ||||
| @@ -21,11 +21,6 @@ public static class ServiceCollectionExtensions | ||||
|     public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) | ||||
|     { | ||||
|         services.AddDbContext<AppDatabase>(); | ||||
|         services.AddSingleton<IConnectionMultiplexer>(_ => | ||||
|         { | ||||
|             var connection = configuration.GetConnectionString("FastRetrieve")!; | ||||
|             return ConnectionMultiplexer.Connect(connection); | ||||
|         }); | ||||
|         services.AddSingleton<IClock>(SystemClock.Instance); | ||||
|         services.AddHttpContextAccessor(); | ||||
|         services.AddSingleton<ICacheService, CacheServiceRedis>(); | ||||
|   | ||||
| @@ -9,10 +9,7 @@ | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379", | ||||
|     "Stream": "nats.orb.local:4222" | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Notifications": { | ||||
|     "Push": { | ||||
| @@ -45,9 +42,7 @@ | ||||
|   ], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Ring", | ||||
|     "Url": "https://localhost:7259", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|     "Url": "https://localhost:7259" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "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> | ||||
							
								
								
									
										137
									
								
								DysonNetwork.ServiceDefaults/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								DysonNetwork.ServiceDefaults/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| using Microsoft.AspNetCore.Builder; | ||||
| using Microsoft.AspNetCore.Diagnostics.HealthChecks; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||||
| using Microsoft.Extensions.Logging; | ||||
| 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 | ||||
|     { | ||||
|         // Allow unencrypted grpc | ||||
|         AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); | ||||
|          | ||||
|         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", configureOptions: opts => | ||||
|         { | ||||
|             opts.AbortOnConnectFail = false; | ||||
|         }); | ||||
|  | ||||
|         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,5 +1,5 @@ | ||||
| using dotnet_etcd.interfaces; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
| @@ -11,33 +11,7 @@ public static class DysonAuthStartup | ||||
|         this IServiceCollection services | ||||
|     ) | ||||
|     { | ||||
|         services.AddSingleton<AuthService.AuthServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             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 => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             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.AddAuthService(); | ||||
|  | ||||
|         services.AddAuthentication(options => | ||||
|             { | ||||
|   | ||||
| @@ -7,11 +7,11 @@ | ||||
|     </PropertyGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|         <PackageReference Include="dotnet-etcd" Version="8.0.1" /> | ||||
|         <PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" /> | ||||
|         <PackageReference Include="Google.Protobuf" Version="3.31.1" /> | ||||
|         <PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" /> | ||||
|         <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.Tools" Version="2.72.0"> | ||||
|             <PrivateAssets>all</PrivateAssets> | ||||
| @@ -27,7 +27,6 @@ | ||||
|         <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" /> | ||||
|         <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="Yarp.ReverseProxy" Version="2.3.0" /> | ||||
|     </ItemGroup> | ||||
| @@ -40,4 +39,8 @@ | ||||
|       <Folder Include="Error\" /> | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|       <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" /> | ||||
|     </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -12,7 +12,7 @@ public static class KestrelConfiguration | ||||
| { | ||||
|     public static WebApplicationBuilder ConfigureAppKestrel( | ||||
|         this WebApplicationBuilder builder, | ||||
|         IConfiguration configuration,  | ||||
|         IConfiguration configuration, | ||||
|         long maxRequestBodySize = 50 * 1024 * 1024 | ||||
|     ) | ||||
|     { | ||||
| @@ -20,22 +20,53 @@ public static class KestrelConfiguration | ||||
|         builder.WebHost.ConfigureKestrel(options => | ||||
|         { | ||||
|             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"]!; | ||||
|             // gRPC | ||||
|             options.ListenAnyIP(5001, listenOptions => | ||||
|             { | ||||
|                 listenOptions.Protocols = HttpProtocols.Http2; | ||||
|                  | ||||
|                 var selfSignedCert = _CreateSelfSignedCertificate(); | ||||
|                 listenOptions.UseHttps(selfSignedCert); | ||||
|             }); | ||||
|  | ||||
|             // Load PEM cert and key manually | ||||
|             var certificate = X509Certificate2.CreateFromPemFile(certPath, keyPath); | ||||
|             // Now pass the full cert | ||||
|             options.ListenAnyIP(5001, listenOptions => { listenOptions.UseHttps(certificate); }); | ||||
|             var httpPorts = configuration.GetValue<string>("HTTP_PORTS", "5000") | ||||
|                 .Split(',', StringSplitOptions.RemoveEmptyEntries) | ||||
|                 .Select(p => int.Parse(p.Trim())) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             // Optional: HTTP fallback | ||||
|             options.ListenAnyIP(8080); | ||||
|             // Regular HTTP | ||||
|             foreach (var httpPort in httpPorts) | ||||
|                 options.ListenAnyIP(httpPort, | ||||
|                     listenOptions => { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); | ||||
|         }); | ||||
|  | ||||
|         return builder; | ||||
|     } | ||||
|      | ||||
|     static X509Certificate2 _CreateSelfSignedCertificate() | ||||
|     { | ||||
|         using var rsa = RSA.Create(2048); | ||||
|         var certRequest = new CertificateRequest( | ||||
|             "CN=dyson.network", // Common Name for the certificate | ||||
|             rsa, | ||||
|             HashAlgorithmName.SHA256, | ||||
|             RSASignaturePadding.Pkcs1); | ||||
|  | ||||
|         // Add extensions (e.g., for server authentication) | ||||
|         certRequest.CertificateExtensions.Add( | ||||
|             new X509EnhancedKeyUsageExtension( | ||||
|                 new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, // Server Authentication | ||||
|                 false)); | ||||
|  | ||||
|         // Set validity period (e.g., 1 year) | ||||
|         var notBefore = DateTimeOffset.UtcNow.AddDays(-1); | ||||
|         var notAfter = notBefore.AddYears(1); | ||||
|  | ||||
|         var certificate = certRequest.CreateSelfSigned(notBefore, notAfter); | ||||
|      | ||||
|         // Export to PKCS#12 and load using X509CertificateLoader | ||||
|         var pfxBytes = certificate.Export(X509ContentType.Pfx); | ||||
|         return X509CertificateLoader.LoadPkcs12(pfxBytes, password: null); | ||||
|     } | ||||
| } | ||||
| @@ -2,171 +2,13 @@ using System.Net; | ||||
| using Grpc.Net.Client; | ||||
| using System.Security.Cryptography.X509Certificates; | ||||
| using Grpc.Core; | ||||
| using dotnet_etcd.interfaces; | ||||
|  | ||||
| namespace DysonNetwork.Shared.Proto; | ||||
|  | ||||
| public static class GrpcClientHelper | ||||
| { | ||||
|     public static CallInvoker CreateCallInvoker( | ||||
|         string url, | ||||
|         string clientCertPath, | ||||
|         string clientKeyPath, | ||||
|         string? clientCertPassword = null | ||||
|     ) | ||||
|     public static CallInvoker CreateCallInvoker(string url) | ||||
|     { | ||||
|         var handler = new HttpClientHandler(); | ||||
|         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)); | ||||
|         return GrpcChannel.ForAddress(url).CreateCallInvoker(); | ||||
|     } | ||||
| } | ||||
| @@ -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 Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| @@ -9,153 +8,94 @@ public static class ServiceInjectionHelper | ||||
| { | ||||
|     public static IServiceCollection AddRingService(this IServiceCollection services) | ||||
|     { | ||||
|         services.AddSingleton<RingService.RingServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = config["Service:ClientCert"]!; | ||||
|             var clientKeyPath = config["Service:ClientKey"]!; | ||||
|             var clientCertPassword = config["Service:CertPassword"]; | ||||
|         services | ||||
|             .AddGrpcClient<RingService.RingServiceClient>(o => o.Address = new Uri("https://_grpc.ring")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|             return GrpcClientHelper | ||||
|                 .CreateRingServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         });        | ||||
|          | ||||
|         return services; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public static IServiceCollection AddAuthService(this IServiceCollection services) | ||||
|     { | ||||
|         services | ||||
|             .AddGrpcClient<AuthService.AuthServiceClient>(o => o.Address = new Uri("https://_grpc.pass")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|         services | ||||
|             .AddGrpcClient<PermissionService.PermissionServiceClient>(o => o.Address = new Uri("https://_grpc.pass")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|         return services; | ||||
|     } | ||||
|  | ||||
|     public static IServiceCollection AddAccountService(this IServiceCollection services) | ||||
|     { | ||||
|         services.AddSingleton<AccountService.AccountServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             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 | ||||
|             .AddGrpcClient<AccountService.AccountServiceClient>(o => o.Address = new Uri("https://_grpc.pass") ) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|         services.AddSingleton<AccountClientHelper>(); | ||||
|          | ||||
|         services.AddSingleton<BotAccountReceiverService.BotAccountReceiverServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             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 => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = config["Service:ClientCert"]!; | ||||
|             var clientKeyPath = config["Service:ClientKey"]!; | ||||
|             var clientCertPassword = config["Service:CertPassword"]; | ||||
|         services | ||||
|             .AddGrpcClient<BotAccountReceiverService.BotAccountReceiverServiceClient>(o => | ||||
|                 o.Address = new Uri("https://_grpc.pass") | ||||
|             ) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|             return GrpcClientHelper | ||||
|                 .CreateActionLogServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         });  | ||||
|          | ||||
|         services.AddSingleton<PaymentService.PaymentServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = config["Service:ClientCert"]!; | ||||
|             var clientKeyPath = config["Service:ClientKey"]!; | ||||
|             var clientCertPassword = config["Service:CertPassword"]; | ||||
|         services.AddGrpcClient<ActionLogService.ActionLogServiceClient>(o => o.Address = new Uri("https://_grpc.pass")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|         services.AddGrpcClient<PaymentService.PaymentServiceClient>(o => o.Address = new Uri("https://_grpc.pass")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|             return GrpcClientHelper | ||||
|                 .CreatePaymentServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         }); | ||||
|          | ||||
|         return services; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public static IServiceCollection AddDriveService(this IServiceCollection services) | ||||
|     { | ||||
|         services.AddSingleton<FileService.FileServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = config["Service:ClientCert"]!; | ||||
|             var clientKeyPath = config["Service:ClientKey"]!; | ||||
|             var clientCertPassword = config["Service:CertPassword"]; | ||||
|         services.AddGrpcClient<FileService.FileServiceClient>(o => o.Address = new Uri("https://_grpc.drive")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|             return GrpcClientHelper | ||||
|                 .CreateFileServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         });        | ||||
|          | ||||
|         services.AddSingleton<FileReferenceService.FileReferenceServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = config["Service:ClientCert"]!; | ||||
|             var clientKeyPath = config["Service:ClientKey"]!; | ||||
|             var clientCertPassword = config["Service:CertPassword"]; | ||||
|         services.AddGrpcClient<FileReferenceService.FileReferenceServiceClient>(o => o.Address = new Uri("https://_grpc.drive")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|             return GrpcClientHelper | ||||
|                 .CreateFileReferenceServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         }); | ||||
|          | ||||
|         return services; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public static IServiceCollection AddPublisherService(this IServiceCollection services) | ||||
|     { | ||||
|         services.AddSingleton<PublisherService.PublisherServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = config["Service:ClientCert"]!; | ||||
|             var clientKeyPath = config["Service:ClientKey"]!; | ||||
|             var clientCertPassword = config["Service:CertPassword"]; | ||||
|         services.AddGrpcClient<PublisherService.PublisherServiceClient>(o => o.Address = new Uri("https://_grpc.sphere")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|             return GrpcClientHelper | ||||
|                 .CreatePublisherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         }); | ||||
|          | ||||
|         return services; | ||||
|     } | ||||
|  | ||||
|     public static IServiceCollection AddDevelopService(this IServiceCollection services) | ||||
|     { | ||||
|         services.AddSingleton<CustomAppService.CustomAppServiceClient>(sp => | ||||
|         { | ||||
|             var etcdClient = sp.GetRequiredService<IEtcdClient>(); | ||||
|             var config = sp.GetRequiredService<IConfiguration>(); | ||||
|             var clientCertPath = config["Service:ClientCert"]!; | ||||
|             var clientKeyPath = config["Service:ClientKey"]!; | ||||
|             var clientCertPassword = config["Service:CertPassword"]; | ||||
|         services.AddGrpcClient<CustomAppService.CustomAppServiceClient>(o => o.Address = new Uri("https://_grpc.develop")) | ||||
|             .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() | ||||
|                 { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } | ||||
|             ); | ||||
|  | ||||
|             return GrpcClientHelper | ||||
|                 .CreateCustomAppServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         }); | ||||
|          | ||||
|         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; | ||||
|     } | ||||
| } | ||||
| @@ -2,9 +2,7 @@ using System.ComponentModel.DataAnnotations; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Text.Json.Serialization; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using NodaTime; | ||||
| using Account = DysonNetwork.Pass.Account.Account; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Chat; | ||||
|  | ||||
| @@ -75,7 +73,7 @@ public class ChatMember : ModelBase | ||||
|     public Guid ChatRoomId { get; set; } | ||||
|     public ChatRoom ChatRoom { get; set; } = null!; | ||||
|     public Guid AccountId { get; set; } | ||||
|     [NotMapped] public Account? Account { get; set; } | ||||
|     [NotMapped] public AccountReference? Account { get; set; } | ||||
|     [NotMapped] public AccountStatusReference? Status { get; set; } | ||||
|  | ||||
|     [MaxLength(1024)] public string? Nick { get; set; } | ||||
| @@ -108,7 +106,7 @@ public class ChatMemberTransmissionObject : ModelBase | ||||
|     public Guid Id { get; set; } | ||||
|     public Guid ChatRoomId { get; set; } | ||||
|     public Guid AccountId { get; set; } | ||||
|     [NotMapped] public Account Account { get; set; } = null!; | ||||
|     [NotMapped] public AccountReference Account { get; set; } = null!; | ||||
|  | ||||
|     [MaxLength(1024)] public string? Nick { get; set; } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ using DysonNetwork.Shared.Cache; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| using Account = DysonNetwork.Pass.Account.Account; | ||||
| using Account = DysonNetwork.Shared.Data.AccountReference; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Chat; | ||||
|  | ||||
|   | ||||
| @@ -55,7 +55,6 @@ | ||||
|         <PackageReference Include="Quartz" Version="3.14.0"/> | ||||
|         <PackageReference Include="Quartz.AspNetCore" 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="Swashbuckle.AspNetCore" Version="9.0.3"/> | ||||
|         <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.3" /> | ||||
| @@ -160,8 +159,8 @@ | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|         <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" /> | ||||
|         <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/> | ||||
|         <ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj"/> | ||||
|     </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Text.Json; | ||||
| using System.Text.Json.Serialization; | ||||
| using DysonNetwork.Pass.Account; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using NodaTime; | ||||
|  | ||||
| @@ -64,5 +63,5 @@ public class PollAnswer : ModelBase | ||||
|     public Guid AccountId { get; set; } | ||||
|     public Guid PollId { get; set; } | ||||
|     [JsonIgnore] public Poll? Poll { get; set; } | ||||
|     [NotMapped] public Account? Account { get; set; } | ||||
|     [NotMapped] public AccountReference? Account { get; set; } | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Text.Json; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Sphere.Publisher; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| @@ -117,7 +117,7 @@ public class PollController( | ||||
|             { | ||||
|                 var protoValue = answeredAccounts.FirstOrDefault(a => a.Id == answer.AccountId.ToString()); | ||||
|                 if (protoValue is not null) | ||||
|                     answer.Account = Pass.Account.Account.FromProtoValue(protoValue); | ||||
|                     answer.Account = AccountReference.FromProtoValue(protoValue); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,6 @@ using DysonNetwork.Shared.Auth; | ||||
| using DysonNetwork.Shared.Http; | ||||
| using DysonNetwork.Shared.PageData; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using DysonNetwork.Sphere; | ||||
| using DysonNetwork.Sphere.PageData; | ||||
| using DysonNetwork.Sphere.Startup; | ||||
| @@ -11,15 +10,13 @@ using Microsoft.Extensions.FileProviders; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| builder.AddServiceDefaults(); | ||||
|  | ||||
| // Configure Kestrel and server options | ||||
| builder.ConfigureAppKestrel(builder.Configuration); | ||||
|  | ||||
| // Add metrics and telemetry | ||||
| builder.Services.AddAppMetrics(); | ||||
|  | ||||
| // Add application services | ||||
| builder.Services.AddRegistryService(builder.Configuration); | ||||
| builder.Services.AddStreamConnection(builder.Configuration); | ||||
|  | ||||
| builder.Services.AddAppServices(builder.Configuration); | ||||
| builder.Services.AddAppRateLimiting(); | ||||
| builder.Services.AddAppAuthentication(); | ||||
| @@ -42,6 +39,8 @@ builder.Services.AddTransient<IPageDataProvider, PostPageData>(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| app.MapDefaultEndpoints(); | ||||
|  | ||||
| // Run database migrations | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| { | ||||
| @@ -52,8 +51,6 @@ using (var scope = app.Services.CreateScope()) | ||||
| // Configure application middleware pipeline | ||||
| app.ConfigureAppMiddleware(builder.Configuration); | ||||
|  | ||||
| app.MapGatewayProxy(); | ||||
|  | ||||
| app.UseDefaultFiles(); | ||||
| app.UseStaticFiles(new StaticFileOptions | ||||
| { | ||||
|   | ||||
| @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| using NodaTime.Serialization.Protobuf; | ||||
| using VerificationMark = DysonNetwork.Shared.Data.VerificationMark; | ||||
| using Account = DysonNetwork.Pass.Account.Account; | ||||
| using Account = DysonNetwork.Shared.Data.AccountReference; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Publisher; | ||||
|  | ||||
|   | ||||
| @@ -34,7 +34,7 @@ public class PublisherController( | ||||
|         var account = await accounts.GetAccountAsync( | ||||
|             new GetAccountRequest { Id = publisher.AccountId.Value.ToString() } | ||||
|         ); | ||||
|         publisher.Account = Pass.Account.Account.FromProtoValue(account); | ||||
|         publisher.Account = AccountReference.FromProtoValue(account); | ||||
|  | ||||
|         return Ok(publisher); | ||||
|     } | ||||
|   | ||||
| @@ -382,7 +382,7 @@ public class PublisherService( | ||||
|     public async Task<PublisherMember> LoadMemberAccount(PublisherMember member) | ||||
|     { | ||||
|         var account = await accountsHelper.GetAccount(member.AccountId); | ||||
|         member.Account = Pass.Account.Account.FromProtoValue(account); | ||||
|         member.Account = AccountReference.FromProtoValue(account); | ||||
|         return member; | ||||
|     } | ||||
|  | ||||
| @@ -394,7 +394,7 @@ public class PublisherService( | ||||
|         return members.Select(m => | ||||
|         { | ||||
|             if (accounts.TryGetValue(m.AccountId, out var account)) | ||||
|                 m.Account = Pass.Account.Account.FromProtoValue(account); | ||||
|                 m.Account = AccountReference.FromProtoValue(account); | ||||
|             return m; | ||||
|         }).ToList(); | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Text.Json.Serialization; | ||||
| using DysonNetwork.Pass.Account; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Sphere.Chat; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| @@ -48,7 +47,7 @@ public class RealmMember : ModelBase | ||||
|     public Guid RealmId { get; set; } | ||||
|     public Realm Realm { get; set; } = null!; | ||||
|     public Guid AccountId { get; set; } | ||||
|     [NotMapped] public Account? Account { get; set; } | ||||
|     [NotMapped] public AccountReference? Account { get; set; } | ||||
|     [NotMapped] public AccountStatusReference? Status { get; set; } | ||||
|  | ||||
|     public int Role { get; set; } = RealmMemberRole.Normal; | ||||
|   | ||||
| @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| using Google.Protobuf.WellKnownTypes; | ||||
| using Microsoft.AspNetCore.Http.HttpResults; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Realm; | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using DysonNetwork.Shared; | ||||
| using DysonNetwork.Shared.Cache; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using DysonNetwork.Sphere.Localization; | ||||
| @@ -73,7 +74,7 @@ public class RealmService( | ||||
|     public async Task<RealmMember> LoadMemberAccount(RealmMember member) | ||||
|     { | ||||
|         var account = await accountsHelper.GetAccount(member.AccountId); | ||||
|         member.Account = Pass.Account.Account.FromProtoValue(account); | ||||
|         member.Account = AccountReference.FromProtoValue(account); | ||||
|         return member; | ||||
|     } | ||||
|  | ||||
| @@ -85,7 +86,7 @@ public class RealmService( | ||||
|         return members.Select(m => | ||||
|         { | ||||
|             if (accounts.TryGetValue(m.AccountId, out var account)) | ||||
|                 m.Account = Pass.Account.Account.FromProtoValue(account); | ||||
|                 m.Account = AccountReference.FromProtoValue(account); | ||||
|             return m; | ||||
|         }).ToList(); | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
| @@ -31,11 +31,6 @@ public static class ServiceCollectionExtensions | ||||
|         services.AddLocalization(options => options.ResourcesPath = "Resources"); | ||||
|  | ||||
|         services.AddDbContext<AppDatabase>(); | ||||
|         services.AddSingleton<IConnectionMultiplexer>(_ => | ||||
|         { | ||||
|             var connection = configuration.GetConnectionString("FastRetrieve")!; | ||||
|             return ConnectionMultiplexer.Connect(connection); | ||||
|         }); | ||||
|         services.AddSingleton<IClock>(SystemClock.Instance); | ||||
|         services.AddHttpContextAccessor(); | ||||
|         services.AddSingleton<ICacheService, CacheServiceRedis>(); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Text.Json.Serialization; | ||||
| using DysonNetwork.Pass.Account; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | ||||
| @@ -46,5 +45,5 @@ public class StickerPackOwnership : ModelBase | ||||
|     public Guid PackId { get; set; } | ||||
|     public StickerPack Pack { get; set; } = null!; | ||||
|     public Guid AccountId { get; set; } | ||||
|     [NotMapped] public Account Account { get; set; } = null!; | ||||
|     [NotMapped] public AccountReference Account { get; set; } = null!; | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Text.Json.Serialization; | ||||
| using DysonNetwork.Pass.Account; | ||||
| using DysonNetwork.Shared.Data; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.WebReader; | ||||
| @@ -54,5 +53,5 @@ public class WebFeedSubscription : ModelBase | ||||
|     public Guid FeedId { get; set; } | ||||
|     public WebFeed Feed { get; set; } = null!; | ||||
|     public Guid AccountId { get; set; } | ||||
|     [NotMapped] public Account Account { get; set; } = null!; | ||||
|     [NotMapped] public AccountReference Account { get; set; } = null!; | ||||
| } | ||||
| @@ -10,10 +10,7 @@ | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", | ||||
|     "FastRetrieve": "localhost:6379", | ||||
|     "Etcd": "etcd.orb.local:2379", | ||||
|     "Stream": "nats.orb.local:4222" | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
| @@ -39,8 +36,6 @@ | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Sphere", | ||||
|     "Url": "https://localhost:7099", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|     "Url": "https://localhost:7099" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -16,10 +16,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Ring", "DysonN | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Drive", "DysonNetwork.Drive\DysonNetwork.Drive.csproj", "{8DE0B783-8852-494D-B90A-201ABBB71202}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Gateway", "DysonNetwork.Gateway\DysonNetwork.Gateway.csproj", "{19EB0086-4049-4B78-91C4-EAC37130A006}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Develop", "DysonNetwork.Develop\DysonNetwork.Develop.csproj", "{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}" | ||||
| 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 | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| @@ -46,13 +48,17 @@ Global | ||||
| 		{8DE0B783-8852-494D-B90A-201ABBB71202}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{19EB0086-4049-4B78-91C4-EAC37130A006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{19EB0086-4049-4B78-91C4-EAC37130A006}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.ActiveCfg = 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.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 | ||||
| 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_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_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_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> | ||||
| @@ -21,6 +22,7 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClaim_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7fdc52b6e574ae7b9822133be91162a15800_003Ff7_003Feebffd8d_003FClaim_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClusterConfig_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf3f51607a3e4e76b5b91640cd7409195c430_003F3f_003F87f581ed_003FClusterConfig_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConnectionMultiplexer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2ed0e2f073b1d77b98dadb822da09ee8a9dfb91bf29bf2bbaecb8750d7e74cc9_003FConnectionMultiplexer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AContainerResourceBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F19256b6d2a8a458692f07fe8d98d79e9161628_003Fd7_003F266d041b_003FContainerResourceBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003Ff6_003Fdf150bb3_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AController_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb320290c1b964c3e88434ff5505d9086c9a00_003Fdf_003F95b535f9_003FController_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACookieOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F663f33943e4c4e889dc7050c1e97e703e000_003F89_003Fb06980d7_003FCookieOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| @@ -32,8 +34,10 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbFunctionsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc1c46ed28c61e1caa79185e4375a8ae7cd11cd5ba8853dcb37577f93f2ca8d5_003FDbFunctionsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADiagnosticServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F47e01f36dea14a23aaea6e0391c1347ace00_003F3c_003F140e6d8b_003FDiagnosticServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADirectory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fde_003F94973e27_003FDirectory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADockerComposeServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4768773ea5864bf8b6fc7a1a3c6f6f311fc38_003F2d_003F5f83b17e_003FDockerComposeServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpointConventionBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F8a_003F101938e3_003FEndpointConventionBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpointHttpContextExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc181aff8c6ec418494a7efcfec578fc154e00_003F81_003F048fd513_003FEndpointHttpContextExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpointReference_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fd5f877f8f192ec96518ae1b12604f822fe9d2d030cdf8c9ada29b2b831f442f_003FEndpointReference_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnforcerExtension_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbb4a120e56464fc6abd8c30969ef70864ba00_003Fb5_003F180850e0_003FEnforcerExtension_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnforcer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbb4a120e56464fc6abd8c30969ef70864ba00_003F47_003F3a6b6c4b_003FEnforcer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkQueryableExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe096e6f12c5d6b49356bc34ff1ea08738f910c0929c9d717c9cba7f44288_003FEntityFrameworkQueryableExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| @@ -64,6 +68,7 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpTransformer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf3f51607a3e4e76b5b91640cd7409195c430_003F8a_003Fd9fba048_003FHttpTransformer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpUtility_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F95cd5fa21c574d4087dec626d8227d77be00_003F08_003Fdd41228e_003FHttpUtility_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIConfiguration_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbb55221b2bd14b31a20b0d8bdcc7ff457328_003F19_003F707d23be_003FIConfiguration_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIConnectionMultiplexer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ffd5f2a75d480e8c786b15cfa0ac11aa9bf445a667ad13d25dc9db61f2cb1b_003FIConnectionMultiplexer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIEtcdClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F866376757aa64634b820c41d3553727886400_003Fbb_003F0fd3f8d7_003FIEtcdClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIHtmlString_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F95cd5fa21c574d4087dec626d8227d77be00_003Ff1_003F3a8957fa_003FIHtmlString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIHttpForwarder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf3f51607a3e4e76b5b91640cd7409195c430_003F29_003F7eee2eb9_003FIHttpForwarder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| @@ -81,6 +86,7 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIStringLocalizerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aa8ac544afb487082402c1fa422910f2e00_003F7f_003F8e728ed6_003FIStringLocalizerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIYarpConfigurationBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F89efee4bc1aa19cf867ad46491f8ffd947aee34fe555d66c3be5953e177b5_003FIYarpConfigurationBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonIgnoreCondition_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F80b40ae0ee1c48a5b065caa3311685c119a400_003Fc3_003F848024ce_003FJsonIgnoreCondition_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5703920a18f94462b4354fab05326e6519a200_003F35_003F8536fc49_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F80b40ae0ee1c48a5b065caa3311685c119a400_003F0a_003F55e3f48f_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| @@ -90,6 +96,7 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtSecurityTokenHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F477051138f1f40de9077b7b1cdc55c6215fb0_003Ff5_003Fd716e016_003FJwtSecurityTokenHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKnownResamplers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003Fb3_003Fcdb3e080_003FKnownResamplers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AListenOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fafff40a572554b579e7307e8ac16b014279a00_003Fc7_003Ff445744a_003FListenOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALivekitRoom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F82666257d5ad47354add7af860f66dd85df55ec93e92e8a45891b9bff7bf80ac_003FLivekitRoom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALocalDate_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9bab6c3f4ee252ba1a9d0707f963a846da4f248fa52e9ff43e789149728a4_003FLocalDate_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMailboxAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8e03e47c46b7469f97abc40667cbcf9b133000_003Fa6_003F83324248_003FMailboxAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| @@ -132,6 +139,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_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_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_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> | ||||
|   | ||||
							
								
								
									
										86
									
								
								compose.yaml
									
									
									
									
									
								
							
							
						
						
									
										86
									
								
								compose.yaml
									
									
									
									
									
								
							| @@ -1,86 +0,0 @@ | ||||
| services: | ||||
|   etcd: | ||||
|     image: bitnami/etcd:latest | ||||
|     ports: | ||||
|       - "2379:2379" | ||||
|       - "2380:2380" | ||||
|     environment: | ||||
|       - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 | ||||
|       - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 | ||||
|       - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 | ||||
|       - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd:2380 | ||||
|       - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster | ||||
|       - ETCD_INITIAL_CLUSTER_STATE=new | ||||
|       - ETCD_INITIAL_CLUSTER=etcd=http://etcd:2380 | ||||
|     healthcheck: | ||||
|       test: ["CMD", "etcdctl", "get", "/health"] | ||||
|       interval: 5s | ||||
|       timeout: 5s | ||||
|       retries: 5 | ||||
|  | ||||
|   gateway: | ||||
|     image: xsheep2010/dyson-gateway:latest | ||||
|     ports: | ||||
|       - "8000:8080" | ||||
|     environment: | ||||
|       - ConnectionStrings__Etcd=http://etcd:2379 | ||||
|       - Etcd__Insecure=true | ||||
|       - Service__Name=DysonNetwork.Gateway | ||||
|       - Service__Url=http://gateway:8080 | ||||
|     depends_on: | ||||
|       etcd: | ||||
|         condition: service_healthy | ||||
|  | ||||
|   drive: | ||||
|     image: xsheep2010/dyson-drive:latest | ||||
|     ports: | ||||
|       - "8001:8080" | ||||
|     environment: | ||||
|       - ConnectionStrings__Etcd=http://etcd:2379 | ||||
|       - Etcd__Insecure=true | ||||
|       - Service__Name=DysonNetwork.Drive | ||||
|       - Service__Url=http://drive:8080 | ||||
|     depends_on: | ||||
|       etcd: | ||||
|         condition: service_healthy | ||||
|  | ||||
|   pass: | ||||
|     image: xsheep2010/dyson-pass:latest | ||||
|     ports: | ||||
|       - "8002:8080" | ||||
|     environment: | ||||
|       - ConnectionStrings__Etcd=http://etcd:2379 | ||||
|       - Etcd__Insecure=true | ||||
|       - Service__Name=DysonNetwork.Pass | ||||
|       - Service__Url=http://pass:8080 | ||||
|     depends_on: | ||||
|       etcd: | ||||
|         condition: service_healthy | ||||
|  | ||||
|   ring: | ||||
|     image: xsheep2010/dyson-ring:latest | ||||
|     ports: | ||||
|       - "8003:8080" | ||||
|     environment: | ||||
|       - ConnectionStrings__Etcd=http://etcd:2379 | ||||
|       - Etcd__Insecure=true | ||||
|       - Service__Name=DysonNetwork.Ring | ||||
|       - Service__Url=http://ring:8080 | ||||
|     depends_on: | ||||
|       etcd: | ||||
|         condition: service_healthy | ||||
|  | ||||
|   sphere: | ||||
|     image: xsheep2010/dyson-sphere:latest | ||||
|     ports: | ||||
|       - "8004:8080" | ||||
|     environment: | ||||
|       - ConnectionStrings__Etcd=http://etcd:2379 | ||||
|       - Etcd__Insecure=true | ||||
|       - Service__Name=DysonNetwork.Sphere | ||||
|       - Service__Url=http://sphere:8080 | ||||
|     volumes: | ||||
|       - "./keys:/app/keys" | ||||
|     depends_on: | ||||
|       etcd: | ||||
|         condition: service_healthy | ||||
							
								
								
									
										217
									
								
								docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | ||||
| services: | ||||
|   aspire-dashboard: | ||||
|     image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest" | ||||
|     expose: | ||||
|       - "18888" | ||||
|       - "18889" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|     restart: "always" | ||||
|   cache: | ||||
|     image: "docker.io/library/redis:7.4" | ||||
|     command: | ||||
|       - "-c" | ||||
|       - "redis-server --requirepass $$REDIS_PASSWORD" | ||||
|     entrypoint: | ||||
|       - "/bin/sh" | ||||
|     environment: | ||||
|       REDIS_PASSWORD: "${CACHE_PASSWORD}" | ||||
|     expose: | ||||
|       - "6379" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   queue: | ||||
|     image: "docker.io/library/nats:2.11" | ||||
|     command: | ||||
|       - "--user" | ||||
|       - "nats" | ||||
|       - "--pass" | ||||
|       - "${QUEUE_PASSWORD}" | ||||
|       - "-js" | ||||
|     expose: | ||||
|       - "4222" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   ring: | ||||
|     image: "${RING_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${RING_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__pass__grpc__0: "https://pass:5001" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "ring" | ||||
|     volumes: | ||||
|       - "./Keys:/app/keys" | ||||
|       - "./settings/ring.json:/app/appsettings.json" | ||||
|     expose: | ||||
|       - "${RING_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   pass: | ||||
|     image: "${PASS_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${PASS_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       services__ring__grpc__0: "https://ring:5001" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "pass" | ||||
|     volumes: | ||||
|       - "./Keys:/app/keys" | ||||
|       - "./settings/pass.json:/app/appsettings.json" | ||||
|     expose: | ||||
|       - "${PASS_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   drive: | ||||
|     image: "${DRIVE_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${DRIVE_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__pass__grpc__0: "https://pass:5001" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       services__ring__grpc__0: "https://ring:5001" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "drive" | ||||
|     expose: | ||||
|       - "${DRIVE_PORT}" | ||||
|       - "5001" | ||||
|     volumes: | ||||
|       - "./settings/drive.json:/app/appsettings.json" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   sphere: | ||||
|     image: "${SPHERE_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${SPHERE_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__pass__grpc__0: "https://pass:5001" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       services__ring__grpc__0: "https://ring:5001" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "sphere" | ||||
|     volumes: | ||||
|       - "./Keys:/app/keys" | ||||
|       - "./settings/sphere.json:/app/appsettings.json" | ||||
|     expose: | ||||
|       - "${SPHERE_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   develop: | ||||
|     image: "${DEVELOP_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${DEVELOP_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__pass__grpc__0: "https://pass:5001" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       services__ring__grpc__0: "https://ring:5001" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "develop" | ||||
|     expose: | ||||
|       - "${DEVELOP_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   gateway: | ||||
|     image: "mcr.microsoft.com/dotnet/nightly/yarp:2.3.0-preview.4" | ||||
|     command: | ||||
|       - "/app/yarp.dll" | ||||
|     entrypoint: | ||||
|       - "dotnet" | ||||
|     environment: | ||||
|       ASPNETCORE_ENVIRONMENT: "Production" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       services__ring__grpc__0: "https://ring:5001" | ||||
|       REVERSEPROXY__ROUTES__route0__MATCH__PATH: "/ws" | ||||
|       REVERSEPROXY__ROUTES__route0__CLUSTERID: "cluster_ring" | ||||
|       REVERSEPROXY__ROUTES__route1__MATCH__PATH: "/ring/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route1__CLUSTERID: "cluster_ring" | ||||
|       REVERSEPROXY__ROUTES__route1__TRANSFORMS__0__PathRemovePrefix: "/ring" | ||||
|       REVERSEPROXY__ROUTES__route1__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route2__MATCH__PATH: "/.well-known/openid-configuration" | ||||
|       REVERSEPROXY__ROUTES__route2__CLUSTERID: "cluster_pass" | ||||
|       REVERSEPROXY__ROUTES__route3__MATCH__PATH: "/.well-known/jwks" | ||||
|       REVERSEPROXY__ROUTES__route3__CLUSTERID: "cluster_pass" | ||||
|       REVERSEPROXY__ROUTES__route4__MATCH__PATH: "/id/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route4__CLUSTERID: "cluster_pass" | ||||
|       REVERSEPROXY__ROUTES__route4__TRANSFORMS__0__PathRemovePrefix: "/id" | ||||
|       REVERSEPROXY__ROUTES__route4__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route5__MATCH__PATH: "/api/tus" | ||||
|       REVERSEPROXY__ROUTES__route5__CLUSTERID: "cluster_drive" | ||||
|       REVERSEPROXY__ROUTES__route6__MATCH__PATH: "/drive/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route6__CLUSTERID: "cluster_drive" | ||||
|       REVERSEPROXY__ROUTES__route6__TRANSFORMS__0__PathRemovePrefix: "/drive" | ||||
|       REVERSEPROXY__ROUTES__route6__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route7__MATCH__PATH: "/sphere/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route7__CLUSTERID: "cluster_sphere" | ||||
|       REVERSEPROXY__ROUTES__route7__TRANSFORMS__0__PathRemovePrefix: "/sphere" | ||||
|       REVERSEPROXY__ROUTES__route7__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route8__MATCH__PATH: "/develop/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route8__CLUSTERID: "cluster_develop" | ||||
|       REVERSEPROXY__ROUTES__route8__TRANSFORMS__0__PathRemovePrefix: "/develop" | ||||
|       REVERSEPROXY__ROUTES__route8__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_ring__DESTINATIONS__destination1__ADDRESS: "http://_http.ring" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_pass__DESTINATIONS__destination1__ADDRESS: "http://_http.pass" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_drive__DESTINATIONS__destination1__ADDRESS: "http://_http.drive" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_sphere__DESTINATIONS__destination1__ADDRESS: "http://_http.sphere" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_develop__DESTINATIONS__destination1__ADDRESS: "http://_http.develop" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__pass__grpc__0: "https://pass:5001" | ||||
|       services__drive__http__0: "http://drive:${DRIVE_PORT}" | ||||
|       services__drive__grpc__0: "https://drive:5001" | ||||
|       services__sphere__http__0: "http://sphere:${SPHERE_PORT}" | ||||
|       services__sphere__grpc__0: "https://sphere:5001" | ||||
|       services__develop__http__0: "http://develop:${DEVELOP_PORT}" | ||||
|       services__develop__grpc__0: "https://develop:5001" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://aspire-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "gateway" | ||||
|     expose: | ||||
|       - "5000" | ||||
|     ports: | ||||
|       - "5001:5000" | ||||
|     networks: | ||||
|       - "aspire" | ||||
| networks: | ||||
|   aspire: | ||||
|     driver: "bridge" | ||||
							
								
								
									
										35
									
								
								publish/.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								publish/.env
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # Default container port for ring | ||||
| RING_PORT=8080 | ||||
|  | ||||
| # Default container port for pass | ||||
| PASS_PORT=8080 | ||||
|  | ||||
| # Default container port for drive | ||||
| DRIVE_PORT=8080 | ||||
|  | ||||
| # Default container port for sphere | ||||
| SPHERE_PORT=8080 | ||||
|  | ||||
| # Default container port for develop | ||||
| DEVELOP_PORT=8080 | ||||
|  | ||||
| # Parameter cache-password | ||||
| CACHE_PASSWORD=KS3jSPaU9e | ||||
|  | ||||
| # Parameter queue-password | ||||
| QUEUE_PASSWORD=8xEECa4ckz | ||||
|  | ||||
| # Container image name for ring | ||||
| RING_IMAGE=ring:latest | ||||
|  | ||||
| # Container image name for pass | ||||
| PASS_IMAGE=pass:latest | ||||
|  | ||||
| # Container image name for drive | ||||
| DRIVE_IMAGE=drive:latest | ||||
|  | ||||
| # Container image name for sphere | ||||
| SPHERE_IMAGE=sphere:latest | ||||
|  | ||||
| # Container image name for develop | ||||
| DEVELOP_IMAGE=develop:latest | ||||
							
								
								
									
										191
									
								
								publish/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								publish/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| services: | ||||
|   docker-compose-dashboard: | ||||
|     image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest" | ||||
|     expose: | ||||
|       - "18888" | ||||
|       - "18889" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|     restart: "always" | ||||
|   cache: | ||||
|     image: "docker.io/library/redis:7.4" | ||||
|     command: | ||||
|       - "-c" | ||||
|       - "redis-server --requirepass $$REDIS_PASSWORD" | ||||
|     entrypoint: | ||||
|       - "/bin/sh" | ||||
|     environment: | ||||
|       REDIS_PASSWORD: "${CACHE_PASSWORD}" | ||||
|     expose: | ||||
|       - "6379" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   queue: | ||||
|     image: "docker.io/library/nats:2.11" | ||||
|     command: | ||||
|       - "--user" | ||||
|       - "nats" | ||||
|       - "--pass" | ||||
|       - "${QUEUE_PASSWORD}" | ||||
|       - "-js" | ||||
|     expose: | ||||
|       - "4222" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   ring: | ||||
|     image: "${RING_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${RING_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "ring" | ||||
|     expose: | ||||
|       - "${RING_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   pass: | ||||
|     image: "${PASS_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${PASS_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "pass" | ||||
|     expose: | ||||
|       - "${PASS_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   drive: | ||||
|     image: "${DRIVE_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${DRIVE_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "drive" | ||||
|     expose: | ||||
|       - "${DRIVE_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   sphere: | ||||
|     image: "${SPHERE_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${SPHERE_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       ConnectionStrings__queue: "nats://nats:${QUEUE_PASSWORD}@queue:4222" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "sphere" | ||||
|     expose: | ||||
|       - "${SPHERE_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   develop: | ||||
|     image: "${DEVELOP_IMAGE}" | ||||
|     environment: | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" | ||||
|       OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" | ||||
|       ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" | ||||
|       HTTP_PORTS: "${DEVELOP_PORT}" | ||||
|       HTTPS_PORTS: "5001" | ||||
|       ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "develop" | ||||
|     expose: | ||||
|       - "${DEVELOP_PORT}" | ||||
|       - "5001" | ||||
|     networks: | ||||
|       - "aspire" | ||||
|   gateway: | ||||
|     image: "mcr.microsoft.com/dotnet/nightly/yarp:2.3.0-preview.4" | ||||
|     command: | ||||
|       - "/app/yarp.dll" | ||||
|     entrypoint: | ||||
|       - "dotnet" | ||||
|     environment: | ||||
|       ASPNETCORE_ENVIRONMENT: "Production" | ||||
|       services__ring__http__0: "http://ring:${RING_PORT}" | ||||
|       REVERSEPROXY__ROUTES__route0__MATCH__PATH: "/ws" | ||||
|       REVERSEPROXY__ROUTES__route0__CLUSTERID: "cluster_ring" | ||||
|       REVERSEPROXY__ROUTES__route1__MATCH__PATH: "/ring/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route1__CLUSTERID: "cluster_ring" | ||||
|       REVERSEPROXY__ROUTES__route1__TRANSFORMS__0__PathRemovePrefix: "/ring" | ||||
|       REVERSEPROXY__ROUTES__route1__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route2__MATCH__PATH: "/.well-known/openid-configuration" | ||||
|       REVERSEPROXY__ROUTES__route2__CLUSTERID: "cluster_pass" | ||||
|       REVERSEPROXY__ROUTES__route3__MATCH__PATH: "/.well-known/jwks" | ||||
|       REVERSEPROXY__ROUTES__route3__CLUSTERID: "cluster_pass" | ||||
|       REVERSEPROXY__ROUTES__route4__MATCH__PATH: "/id/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route4__CLUSTERID: "cluster_pass" | ||||
|       REVERSEPROXY__ROUTES__route4__TRANSFORMS__0__PathRemovePrefix: "/id" | ||||
|       REVERSEPROXY__ROUTES__route4__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route5__MATCH__PATH: "/api/tus" | ||||
|       REVERSEPROXY__ROUTES__route5__CLUSTERID: "cluster_drive" | ||||
|       REVERSEPROXY__ROUTES__route6__MATCH__PATH: "/drive/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route6__CLUSTERID: "cluster_drive" | ||||
|       REVERSEPROXY__ROUTES__route6__TRANSFORMS__0__PathRemovePrefix: "/drive" | ||||
|       REVERSEPROXY__ROUTES__route6__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route7__MATCH__PATH: "/sphere/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route7__CLUSTERID: "cluster_sphere" | ||||
|       REVERSEPROXY__ROUTES__route7__TRANSFORMS__0__PathRemovePrefix: "/sphere" | ||||
|       REVERSEPROXY__ROUTES__route7__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__ROUTES__route8__MATCH__PATH: "/develop/{**catch-all}" | ||||
|       REVERSEPROXY__ROUTES__route8__CLUSTERID: "cluster_develop" | ||||
|       REVERSEPROXY__ROUTES__route8__TRANSFORMS__0__PathRemovePrefix: "/develop" | ||||
|       REVERSEPROXY__ROUTES__route8__TRANSFORMS__1__PathPrefix: "/api" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_ring__DESTINATIONS__destination1__ADDRESS: "http://_http.ring" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_pass__DESTINATIONS__destination1__ADDRESS: "http://_http.pass" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_drive__DESTINATIONS__destination1__ADDRESS: "http://_http.drive" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_sphere__DESTINATIONS__destination1__ADDRESS: "http://_http.sphere" | ||||
|       REVERSEPROXY__CLUSTERS__cluster_develop__DESTINATIONS__destination1__ADDRESS: "http://_http.develop" | ||||
|       services__pass__http__0: "http://pass:${PASS_PORT}" | ||||
|       services__drive__http__0: "http://drive:${DRIVE_PORT}" | ||||
|       services__sphere__http__0: "http://sphere:${SPHERE_PORT}" | ||||
|       services__develop__http__0: "http://develop:${DEVELOP_PORT}" | ||||
|       OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" | ||||
|       OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" | ||||
|       OTEL_SERVICE_NAME: "gateway" | ||||
|     expose: | ||||
|       - "5000" | ||||
|     networks: | ||||
|       - "aspire" | ||||
| networks: | ||||
|   aspire: | ||||
|     driver: "bridge" | ||||
							
								
								
									
										23
									
								
								publish/settings/develop.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								publish/settings/develop.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5071", | ||||
|   "SiteUrl": "https://solian.app", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Develop", | ||||
|     "Url": "https://localhost:7192" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										127
									
								
								publish/settings/drive.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								publish/settings/drive.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5090", | ||||
|   "GatewayUrl": "http://localhost:5094", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
|       "Bearer": { | ||||
|         "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], | ||||
|         "ValidIssuer": "solar-network" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "AuthToken": { | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem" | ||||
|   }, | ||||
|   "OidcProvider": { | ||||
|     "IssuerUri": "https://nt.solian.app", | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem", | ||||
|     "AccessTokenLifetime": "01:00:00", | ||||
|     "RefreshTokenLifetime": "30.00:00:00", | ||||
|     "AuthorizationCodeLifetime": "00:30:00", | ||||
|     "RequireHttpsMetadata": true | ||||
|   }, | ||||
|   "Tus": { | ||||
|     "StorePath": "Uploads" | ||||
|   }, | ||||
|   "Storage": { | ||||
|     "PreferredRemote": "2adceae3-981a-4564-9b8d-5d71a211c873", | ||||
|     "Remote": [ | ||||
|       { | ||||
|         "Id": "minio", | ||||
|         "Label": "Minio", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network-development", | ||||
|         "Endpoint": "localhost:9000", | ||||
|         "SecretId": "littlesheep", | ||||
|         "SecretKey": "password", | ||||
|         "EnabledSigned": true, | ||||
|         "EnableSsl": false | ||||
|       }, | ||||
|       { | ||||
|         "Id": "cloudflare", | ||||
|         "Label": "Cloudflare R2", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network", | ||||
|         "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", | ||||
|         "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", | ||||
|         "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", | ||||
|         "EnableSigned": true, | ||||
|         "EnableSsl": true | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   "Captcha": { | ||||
|     "Provider": "cloudflare", | ||||
|     "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", | ||||
|     "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" | ||||
|   }, | ||||
|   "Notifications": { | ||||
|     "Topic": "dev.solsynth.solian", | ||||
|     "Endpoint": "http://localhost:8088" | ||||
|   }, | ||||
|   "Email": { | ||||
|     "Server": "smtp4dev.orb.local", | ||||
|     "Port": 25, | ||||
|     "UseSsl": false, | ||||
|     "Username": "no-reply@mail.solsynth.dev", | ||||
|     "Password": "password", | ||||
|     "FromAddress": "no-reply@mail.solsynth.dev", | ||||
|     "FromName": "Alphabot", | ||||
|     "SubjectPrefix": "Solar Network" | ||||
|   }, | ||||
|   "RealtimeChat": { | ||||
|     "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", | ||||
|     "ApiKey": "APIs6TiL8wj3A4j", | ||||
|     "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "Oidc": { | ||||
|     "Google": { | ||||
|       "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", | ||||
|       "ClientSecret": "" | ||||
|     }, | ||||
|     "Apple": { | ||||
|       "ClientId": "dev.solsynth.solian", | ||||
|       "TeamId": "W7HPZ53V6B", | ||||
|       "KeyId": "B668YP4KBG", | ||||
|       "PrivateKeyPath": "./Keys/Solarpass.p8" | ||||
|     }, | ||||
|     "Microsoft": { | ||||
|       "ClientId": "YOUR_MICROSOFT_CLIENT_ID", | ||||
|       "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", | ||||
|       "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" | ||||
|     } | ||||
|   }, | ||||
|   "Payment": { | ||||
|     "Auth": { | ||||
|       "Afdian": "<token here>" | ||||
|     }, | ||||
|     "Subscriptions": { | ||||
|       "Afdian": { | ||||
|         "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", | ||||
|         "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", | ||||
|         "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Drive", | ||||
|     "Url": "https://localhost:7092" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										82
									
								
								publish/settings/pass.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								publish/settings/pass.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5216", | ||||
|   "SiteUrl": "https://id.solian.app", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
|       "Bearer": { | ||||
|         "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], | ||||
|         "ValidIssuer": "solar-network" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "AuthToken": { | ||||
|     "CookieDomain": "localhost", | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem" | ||||
|   }, | ||||
|   "OidcProvider": { | ||||
|     "IssuerUri": "https://nt.solian.app", | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem", | ||||
|     "AccessTokenLifetime": "01:00:00", | ||||
|     "RefreshTokenLifetime": "30.00:00:00", | ||||
|     "AuthorizationCodeLifetime": "00:30:00", | ||||
|     "RequireHttpsMetadata": true | ||||
|   }, | ||||
|   "Captcha": { | ||||
|     "Provider": "cloudflare", | ||||
|     "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", | ||||
|     "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "Oidc": { | ||||
|     "Google": { | ||||
|       "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", | ||||
|       "ClientSecret": "" | ||||
|     }, | ||||
|     "Apple": { | ||||
|       "ClientId": "dev.solsynth.solian", | ||||
|       "TeamId": "W7HPZ53V6B", | ||||
|       "KeyId": "B668YP4KBG", | ||||
|       "PrivateKeyPath": "./Keys/Solarpass.p8" | ||||
|     }, | ||||
|     "Microsoft": { | ||||
|       "ClientId": "YOUR_MICROSOFT_CLIENT_ID", | ||||
|       "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", | ||||
|       "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" | ||||
|     } | ||||
|   }, | ||||
|   "Payment": { | ||||
|     "Auth": { | ||||
|       "Afdian": "<token here>" | ||||
|     }, | ||||
|     "Subscriptions": { | ||||
|       "Afdian": { | ||||
|         "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", | ||||
|         "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", | ||||
|         "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Pass", | ||||
|     "Url": "https://localhost:7058" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   } | ||||
| } | ||||
							
								
								
									
										47
									
								
								publish/settings/ring.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								publish/settings/ring.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5212", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Notifications": { | ||||
|     "Push": { | ||||
|       "Production": true, | ||||
|       "Google": "./Keys/Solian.json", | ||||
|       "Apple": { | ||||
|         "PrivateKey": "./Keys/Solian.p8", | ||||
|         "PrivateKeyId": "4US4KSX4W6", | ||||
|         "TeamId": "W7HPZ53V6B", | ||||
|         "BundleIdentifier": "dev.solsynth.solian" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "Email": { | ||||
|     "Server": "smtp4dev.orb.local", | ||||
|     "Port": 25, | ||||
|     "UseSsl": false, | ||||
|     "Username": "no-reply@mail.solsynth.dev", | ||||
|     "Password": "password", | ||||
|     "FromAddress": "no-reply@mail.solsynth.dev", | ||||
|     "FromName": "Alphabot", | ||||
|     "SubjectPrefix": "Solar Network" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Ring", | ||||
|     "Url": "https://localhost:7259" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   } | ||||
| } | ||||
							
								
								
									
										38
									
								
								publish/settings/sphere.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								publish/settings/sphere.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5071", | ||||
|   "SiteUrl": "https://solian.app", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "RealtimeChat": { | ||||
|     "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", | ||||
|     "ApiKey": "", | ||||
|     "ApiSecret": "" | ||||
|   }, | ||||
|   "Translation": { | ||||
|     "Provider": "Tencent", | ||||
|     "Region": "ap-hongkong", | ||||
|     "ProjectId": "0", | ||||
|     "SecretId": "", | ||||
|     "SecretKey": "" | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Sphere", | ||||
|     "Url": "https://localhost:7099" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										23
									
								
								settings/develop.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								settings/develop.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5071", | ||||
|   "SiteUrl": "https://solian.app", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_network_dev;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Develop", | ||||
|     "Url": "https://localhost:7192" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										127
									
								
								settings/drive.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								settings/drive.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5090", | ||||
|   "GatewayUrl": "http://localhost:5094", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
|       "Bearer": { | ||||
|         "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], | ||||
|         "ValidIssuer": "solar-network" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "AuthToken": { | ||||
|     "PublicKeyPath": "app/keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "app/keys/PrivateKey.pem" | ||||
|   }, | ||||
|   "OidcProvider": { | ||||
|     "IssuerUri": "https://nt.solian.app", | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem", | ||||
|     "AccessTokenLifetime": "01:00:00", | ||||
|     "RefreshTokenLifetime": "30.00:00:00", | ||||
|     "AuthorizationCodeLifetime": "00:30:00", | ||||
|     "RequireHttpsMetadata": true | ||||
|   }, | ||||
|   "Tus": { | ||||
|     "StorePath": "Uploads" | ||||
|   }, | ||||
|   "Storage": { | ||||
|     "PreferredRemote": "2adceae3-981a-4564-9b8d-5d71a211c873", | ||||
|     "Remote": [ | ||||
|       { | ||||
|         "Id": "minio", | ||||
|         "Label": "Minio", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network-development", | ||||
|         "Endpoint": "localhost:9000", | ||||
|         "SecretId": "littlesheep", | ||||
|         "SecretKey": "password", | ||||
|         "EnabledSigned": true, | ||||
|         "EnableSsl": false | ||||
|       }, | ||||
|       { | ||||
|         "Id": "cloudflare", | ||||
|         "Label": "Cloudflare R2", | ||||
|         "Region": "auto", | ||||
|         "Bucket": "solar-network", | ||||
|         "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", | ||||
|         "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", | ||||
|         "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", | ||||
|         "EnableSigned": true, | ||||
|         "EnableSsl": true | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   "Captcha": { | ||||
|     "Provider": "cloudflare", | ||||
|     "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", | ||||
|     "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" | ||||
|   }, | ||||
|   "Notifications": { | ||||
|     "Topic": "dev.solsynth.solian", | ||||
|     "Endpoint": "http://localhost:8088" | ||||
|   }, | ||||
|   "Email": { | ||||
|     "Server": "smtp4dev.orb.local", | ||||
|     "Port": 25, | ||||
|     "UseSsl": false, | ||||
|     "Username": "no-reply@mail.solsynth.dev", | ||||
|     "Password": "password", | ||||
|     "FromAddress": "no-reply@mail.solsynth.dev", | ||||
|     "FromName": "Alphabot", | ||||
|     "SubjectPrefix": "Solar Network" | ||||
|   }, | ||||
|   "RealtimeChat": { | ||||
|     "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", | ||||
|     "ApiKey": "APIs6TiL8wj3A4j", | ||||
|     "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "Oidc": { | ||||
|     "Google": { | ||||
|       "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", | ||||
|       "ClientSecret": "" | ||||
|     }, | ||||
|     "Apple": { | ||||
|       "ClientId": "dev.solsynth.solian", | ||||
|       "TeamId": "W7HPZ53V6B", | ||||
|       "KeyId": "B668YP4KBG", | ||||
|       "PrivateKeyPath": "./Keys/Solarpass.p8" | ||||
|     }, | ||||
|     "Microsoft": { | ||||
|       "ClientId": "YOUR_MICROSOFT_CLIENT_ID", | ||||
|       "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", | ||||
|       "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" | ||||
|     } | ||||
|   }, | ||||
|   "Payment": { | ||||
|     "Auth": { | ||||
|       "Afdian": "<token here>" | ||||
|     }, | ||||
|     "Subscriptions": { | ||||
|       "Afdian": { | ||||
|         "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", | ||||
|         "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", | ||||
|         "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Drive", | ||||
|     "Url": "https://localhost:7092" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										82
									
								
								settings/pass.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								settings/pass.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5216", | ||||
|   "SiteUrl": "https://id.solian.app", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "Schemes": { | ||||
|       "Bearer": { | ||||
|         "ValidAudiences": ["http://localhost:5071", "https://localhost:7099"], | ||||
|         "ValidIssuer": "solar-network" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "AuthToken": { | ||||
|     "CookieDomain": "localhost", | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem" | ||||
|   }, | ||||
|   "OidcProvider": { | ||||
|     "IssuerUri": "https://nt.solian.app", | ||||
|     "PublicKeyPath": "Keys/PublicKey.pem", | ||||
|     "PrivateKeyPath": "Keys/PrivateKey.pem", | ||||
|     "AccessTokenLifetime": "01:00:00", | ||||
|     "RefreshTokenLifetime": "30.00:00:00", | ||||
|     "AuthorizationCodeLifetime": "00:30:00", | ||||
|     "RequireHttpsMetadata": true | ||||
|   }, | ||||
|   "Captcha": { | ||||
|     "Provider": "cloudflare", | ||||
|     "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", | ||||
|     "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "./Keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "Oidc": { | ||||
|     "Google": { | ||||
|       "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", | ||||
|       "ClientSecret": "" | ||||
|     }, | ||||
|     "Apple": { | ||||
|       "ClientId": "dev.solsynth.solian", | ||||
|       "TeamId": "W7HPZ53V6B", | ||||
|       "KeyId": "B668YP4KBG", | ||||
|       "PrivateKeyPath": "./Keys/Solarpass.p8" | ||||
|     }, | ||||
|     "Microsoft": { | ||||
|       "ClientId": "YOUR_MICROSOFT_CLIENT_ID", | ||||
|       "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", | ||||
|       "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" | ||||
|     } | ||||
|   }, | ||||
|   "Payment": { | ||||
|     "Auth": { | ||||
|       "Afdian": "<token here>" | ||||
|     }, | ||||
|     "Subscriptions": { | ||||
|       "Afdian": { | ||||
|         "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", | ||||
|         "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", | ||||
|         "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Pass", | ||||
|     "Url": "https://localhost:7058" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   } | ||||
| } | ||||
							
								
								
									
										47
									
								
								settings/ring.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								settings/ring.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5212", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "Notifications": { | ||||
|     "Push": { | ||||
|       "Production": true, | ||||
|       "Google": "/app/keys/Solian.json", | ||||
|       "Apple": { | ||||
|         "PrivateKey": "./Keys/Solian.p8", | ||||
|         "PrivateKeyId": "4US4KSX4W6", | ||||
|         "TeamId": "W7HPZ53V6B", | ||||
|         "BundleIdentifier": "dev.solsynth.solian" | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   "Email": { | ||||
|     "Server": "smtp4dev.orb.local", | ||||
|     "Port": 25, | ||||
|     "UseSsl": false, | ||||
|     "Username": "no-reply@mail.solsynth.dev", | ||||
|     "Password": "password", | ||||
|     "FromAddress": "no-reply@mail.solsynth.dev", | ||||
|     "FromName": "Alphabot", | ||||
|     "SubjectPrefix": "Solar Network" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "/app/keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Ring", | ||||
|     "Url": "https://localhost:7259" | ||||
|   }, | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   } | ||||
| } | ||||
							
								
								
									
										38
									
								
								settings/sphere.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								settings/sphere.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| { | ||||
|   "Debug": true, | ||||
|   "BaseUrl": "http://localhost:5071", | ||||
|   "SiteUrl": "https://solian.app", | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=host.docker.internal;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" | ||||
|   }, | ||||
|   "GeoIp": { | ||||
|     "DatabasePath": "/app/keys/GeoLite2-City.mmdb" | ||||
|   }, | ||||
|   "RealtimeChat": { | ||||
|     "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", | ||||
|     "ApiKey": "", | ||||
|     "ApiSecret": "" | ||||
|   }, | ||||
|   "Translation": { | ||||
|     "Provider": "Tencent", | ||||
|     "Region": "ap-hongkong", | ||||
|     "ProjectId": "0", | ||||
|     "SecretId": "", | ||||
|     "SecretKey": "" | ||||
|   }, | ||||
|   "KnownProxies": ["127.0.0.1", "::1"], | ||||
|   "Etcd": { | ||||
|     "Insecure": true | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Sphere", | ||||
|     "Url": "https://localhost:7099" | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user