使用 .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:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
@@ -7,7 +7,7 @@ on:
 | 
				
			|||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  build-sphere:
 | 
					  publish:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    permissions:
 | 
					    permissions:
 | 
				
			||||||
      contents: read
 | 
					      contents: read
 | 
				
			||||||
@@ -15,174 +15,46 @@ jobs:
 | 
				
			|||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout repository
 | 
					      - name: Checkout repository
 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					        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:
 | 
					      - name: Setup .NET
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					        uses: actions/setup-dotnet@v3
 | 
				
			||||||
    permissions:
 | 
					 | 
				
			||||||
      contents: read
 | 
					 | 
				
			||||||
      packages: write
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - name: Checkout repository
 | 
					 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 0
 | 
					          dotnet-version: "9.0.x"
 | 
				
			||||||
      - 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
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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
 | 
					      - name: Log in to GitHub Container Registry
 | 
				
			||||||
        uses: docker/login-action@v3
 | 
					        uses: docker/login-action@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          registry: ghcr.io
 | 
					          registry: ghcr.io
 | 
				
			||||||
          username: ${{ github.actor }}
 | 
					          username: ${{ github.actor }}
 | 
				
			||||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
					          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:
 | 
					      - name: Install Aspire CLI
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					        run: dotnet tool install -g Aspire.Cli --prerelease
 | 
				
			||||||
    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
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  build-gateway:
 | 
					      - name: Build and Publish Aspire Application
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					        run: aspire publish --project ./DysonNetwork.Control/DysonNetwork.Control.csproj --output publish
 | 
				
			||||||
    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
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  build-develop:
 | 
					      - name: Tag and Push Images
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					        run: |
 | 
				
			||||||
    permissions:
 | 
					          IMAGES=( "sphere" "pass" "ring" "drive" "develop" )
 | 
				
			||||||
      contents: read
 | 
					
 | 
				
			||||||
      packages: write
 | 
					          for image in "${IMAGES[@]}"; do
 | 
				
			||||||
    steps:
 | 
					            IMAGE_NAME="ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-$image:alpha"
 | 
				
			||||||
      - name: Checkout repository
 | 
					            SOURCE_IMAGE_NAME="$image:latest" # Aspire's default local image name
 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					
 | 
				
			||||||
 | 
					            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:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 0
 | 
					          name: aspire-publish-output
 | 
				
			||||||
      - name: Setup NBGV
 | 
					          path: ./publish/
 | 
				
			||||||
        uses: dotnet/nbgv@master
 | 
					
 | 
				
			||||||
        id: nbgv
 | 
					      - name: Upload Docker Compose file
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					        uses: actions/upload-artifact@v3
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3
 | 
					 | 
				
			||||||
      - name: Log in to GitHub Container Registry
 | 
					 | 
				
			||||||
        uses: docker/login-action@v3
 | 
					 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          registry: ghcr.io
 | 
					          name: docker-compose-output
 | 
				
			||||||
          username: ${{ github.actor }}
 | 
					          path: ./publish/docker-compose.yml
 | 
				
			||||||
          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
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
    <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
					    <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,16 @@
 | 
				
			|||||||
using DysonNetwork.Develop;
 | 
					using DysonNetwork.Develop;
 | 
				
			||||||
using DysonNetwork.Shared.Auth;
 | 
					using DysonNetwork.Shared.Auth;
 | 
				
			||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
using DysonNetwork.Develop.Startup;
 | 
					using DysonNetwork.Develop.Startup;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
builder.Services.AddAppSwagger();
 | 
					builder.Services.AddAppSwagger();
 | 
				
			||||||
@@ -22,6 +21,8 @@ builder.Services.AddDriveService();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
 | 
					    var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,11 +20,6 @@ public static class ServiceCollectionExtensions
 | 
				
			|||||||
        services.AddDbContext<AppDatabase>();
 | 
					        services.AddDbContext<AppDatabase>();
 | 
				
			||||||
        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
					        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
				
			||||||
        services.AddHttpContextAccessor();
 | 
					        services.AddHttpContextAccessor();
 | 
				
			||||||
        services.AddSingleton<IConnectionMultiplexer>(_ =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var connection = configuration.GetConnectionString("FastRetrieve")!;
 | 
					 | 
				
			||||||
            return ConnectionMultiplexer.Connect(connection);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
					        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddHttpClient();
 | 
					        services.AddHttpClient();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,10 +10,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "AllowedHosts": "*",
 | 
					  "AllowedHosts": "*",
 | 
				
			||||||
  "ConnectionStrings": {
 | 
					  "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",
 | 
					    "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"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "KnownProxies": [
 | 
					  "KnownProxies": [
 | 
				
			||||||
    "127.0.0.1",
 | 
					    "127.0.0.1",
 | 
				
			||||||
@@ -24,8 +21,6 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Develop",
 | 
					    "Name": "DysonNetwork.Develop",
 | 
				
			||||||
    "Url": "https://localhost:7192",
 | 
					    "Url": "https://localhost:7192"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,6 +66,7 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
					      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,18 +5,18 @@ using DysonNetwork.Shared.Auth;
 | 
				
			|||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.PageData;
 | 
					using DysonNetwork.Shared.PageData;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using tusdotnet.Stores;
 | 
					using tusdotnet.Stores;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue);
 | 
					builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					
 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -39,6 +39,8 @@ builder.Services.AddTransient<IPageDataProvider, VersionPageData>();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -51,8 +53,6 @@ var tusDiskStore = app.Services.GetRequiredService<TusDiskStore>();
 | 
				
			|||||||
// Configure application middleware pipeline
 | 
					// Configure application middleware pipeline
 | 
				
			||||||
app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath);
 | 
					app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.MapGatewayProxy();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html"));
 | 
					app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure gRPC
 | 
					// Configure gRPC
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,11 +17,6 @@ public static class ServiceCollectionExtensions
 | 
				
			|||||||
    public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
 | 
					    public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddDbContext<AppDatabase>(); // Assuming you'll have an AppDatabase
 | 
					        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.AddSingleton<IClock>(SystemClock.Instance);
 | 
				
			||||||
        services.AddHttpContextAccessor();
 | 
					        services.AddHttpContextAccessor();
 | 
				
			||||||
        services.AddSingleton<ICacheService, CacheServiceRedis>(); // Uncomment if you have CacheServiceRedis
 | 
					        services.AddSingleton<ICacheService, CacheServiceRedis>(); // Uncomment if you have CacheServiceRedis
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,10 +10,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "AllowedHosts": "*",
 | 
					  "AllowedHosts": "*",
 | 
				
			||||||
  "ConnectionStrings": {
 | 
					  "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",
 | 
					    "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"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Authentication": {
 | 
					  "Authentication": {
 | 
				
			||||||
    "Schemes": {
 | 
					    "Schemes": {
 | 
				
			||||||
@@ -131,8 +128,6 @@
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Drive",
 | 
					    "Name": "DysonNetwork.Drive",
 | 
				
			||||||
    "Url": "https://localhost:7092",
 | 
					    "Url": "https://localhost:7092"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
					        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
using dotnet_etcd;
 | 
					 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,20 +4,16 @@ using DysonNetwork.Pass.Startup;
 | 
				
			|||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.PageData;
 | 
					using DysonNetwork.Shared.PageData;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add metrics and telemetry
 | 
					 | 
				
			||||||
builder.Services.AddAppMetrics();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -41,6 +37,8 @@ builder.Services.AddTransient<IPageDataProvider, AccountPageData>();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -51,8 +49,6 @@ using (var scope = app.Services.CreateScope())
 | 
				
			|||||||
// Configure application middleware pipeline
 | 
					// Configure application middleware pipeline
 | 
				
			||||||
app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath);
 | 
					app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.MapGatewayProxy();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html"));
 | 
					app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure gRPC
 | 
					// Configure gRPC
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
using OpenTelemetry.Metrics;
 | 
					 | 
				
			||||||
using OpenTelemetry.Trace;
 | 
					 | 
				
			||||||
using Prometheus;
 | 
					 | 
				
			||||||
using Prometheus.SystemMetrics;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Pass.Startup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class MetricsConfiguration
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public static IServiceCollection AddAppMetrics(this IServiceCollection services)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Prometheus
 | 
					 | 
				
			||||||
        services.UseHttpClientMetrics();
 | 
					 | 
				
			||||||
        services.AddHealthChecks();
 | 
					 | 
				
			||||||
        services.AddSystemMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusEntityFrameworkMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusAspNetCoreMetrics();
 | 
					 | 
				
			||||||
        services.AddPrometheusHttpClientMetrics();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // OpenTelemetry
 | 
					 | 
				
			||||||
        services.AddOpenTelemetry()
 | 
					 | 
				
			||||||
            .WithTracing(tracing =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                tracing
 | 
					 | 
				
			||||||
                    .AddAspNetCoreInstrumentation()
 | 
					 | 
				
			||||||
                    .AddHttpClientInstrumentation()
 | 
					 | 
				
			||||||
                    .AddOtlpExporter();
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .WithMetrics(metrics =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                metrics
 | 
					 | 
				
			||||||
                    .AddAspNetCoreInstrumentation()
 | 
					 | 
				
			||||||
                    .AddHttpClientInstrumentation()
 | 
					 | 
				
			||||||
                    .AddRuntimeInstrumentation()
 | 
					 | 
				
			||||||
                    .AddOtlpExporter();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return services;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -34,11 +34,6 @@ public static class ServiceCollectionExtensions
 | 
				
			|||||||
        services.AddLocalization(options => options.ResourcesPath = "Resources");
 | 
					        services.AddLocalization(options => options.ResourcesPath = "Resources");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddDbContext<AppDatabase>();
 | 
					        services.AddDbContext<AppDatabase>();
 | 
				
			||||||
        services.AddSingleton<IConnectionMultiplexer>(_ =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var connection = configuration.GetConnectionString("FastRetrieve")!;
 | 
					 | 
				
			||||||
            return ConnectionMultiplexer.Connect(connection);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
					        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
				
			||||||
        services.AddHttpContextAccessor();
 | 
					        services.AddHttpContextAccessor();
 | 
				
			||||||
        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
					        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,10 +10,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "AllowedHosts": "*",
 | 
					  "AllowedHosts": "*",
 | 
				
			||||||
  "ConnectionStrings": {
 | 
					  "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",
 | 
					    "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"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Authentication": {
 | 
					  "Authentication": {
 | 
				
			||||||
    "Schemes": {
 | 
					    "Schemes": {
 | 
				
			||||||
@@ -83,9 +80,7 @@
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Pass",
 | 
					    "Name": "DysonNetwork.Pass",
 | 
				
			||||||
    "Url": "https://localhost:7058",
 | 
					    "Url": "https://localhost:7058"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Etcd": {
 | 
					  "Etcd": {
 | 
				
			||||||
    "Insecure": true
 | 
					    "Insecure": true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
using System.Collections.Concurrent;
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
using System.Net.WebSockets;
 | 
					using System.Net.WebSockets;
 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Data;
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using Grpc.Core;
 | 
					using Grpc.Core;
 | 
				
			||||||
@@ -11,17 +10,14 @@ public class WebSocketService
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private readonly IConfiguration _configuration;
 | 
					    private readonly IConfiguration _configuration;
 | 
				
			||||||
    private readonly ILogger<WebSocketService> _logger;
 | 
					    private readonly ILogger<WebSocketService> _logger;
 | 
				
			||||||
    private readonly IEtcdClient _etcdClient;
 | 
					 | 
				
			||||||
    private readonly IDictionary<string, IWebSocketPacketHandler> _handlerMap;
 | 
					    private readonly IDictionary<string, IWebSocketPacketHandler> _handlerMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public WebSocketService(
 | 
					    public WebSocketService(
 | 
				
			||||||
        IEnumerable<IWebSocketPacketHandler> handlers,
 | 
					        IEnumerable<IWebSocketPacketHandler> handlers,
 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        ILogger<WebSocketService> logger,
 | 
					        ILogger<WebSocketService> logger,
 | 
				
			||||||
        IConfiguration configuration
 | 
					        IConfiguration configuration
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _etcdClient = etcdClient;
 | 
					 | 
				
			||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
        _configuration = configuration;
 | 
					        _configuration = configuration;
 | 
				
			||||||
        _handlerMap = handlers.ToDictionary(h => h.PacketType);
 | 
					        _handlerMap = handlers.ToDictionary(h => h.PacketType);
 | 
				
			||||||
@@ -59,8 +55,10 @@ public class WebSocketService
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (Exception ex)
 | 
					        catch (Exception ex)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId, key.DeviceId);
 | 
					            _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId,
 | 
				
			||||||
 | 
					                key.DeviceId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data.Cts.Cancel();
 | 
					        data.Cts.Cancel();
 | 
				
			||||||
        ActiveConnections.TryRemove(key, out _);
 | 
					        ActiveConnections.TryRemove(key, out _);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -140,45 +138,24 @@ public class WebSocketService
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // Get the service URL from etcd for the specified endpoint
 | 
					                var serviceUrl = "https://" + packet.Endpoint;
 | 
				
			||||||
                var serviceKey = $"/services/{packet.Endpoint}";
 | 
					 | 
				
			||||||
                var response = await _etcdClient.GetAsync(serviceKey);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (response.Kvs.Count > 0)
 | 
					                var callInvoker = GrpcClientHelper.CreateCallInvoker(serviceUrl);
 | 
				
			||||||
 | 
					                var client = new RingHandlerService.RingHandlerServiceClient(callInvoker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var serviceUrl = response.Kvs[0].Value.ToStringUtf8();
 | 
					                    await client.ReceiveWebSocketPacketAsync(new ReceiveWebSocketPacketRequest
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    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
 | 
					                        Account = currentUser,
 | 
				
			||||||
                        {
 | 
					                        DeviceId = deviceId,
 | 
				
			||||||
                            Account = currentUser,
 | 
					                        Packet = packet.ToProtoValue()
 | 
				
			||||||
                            DeviceId = deviceId,
 | 
					                    });
 | 
				
			||||||
                            Packet = packet.ToProtoValue()
 | 
					                }
 | 
				
			||||||
                        });
 | 
					                catch (RpcException ex)
 | 
				
			||||||
                    }
 | 
					                {
 | 
				
			||||||
                    catch (RpcException ex)
 | 
					                    _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}");
 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}");
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                _logger.LogWarning($"No service registered for endpoint: {packet.Endpoint}");
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,7 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
					      <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,17 +3,16 @@ using DysonNetwork.Ring.Startup;
 | 
				
			|||||||
using DysonNetwork.Shared.Auth;
 | 
					using DysonNetwork.Shared.Auth;
 | 
				
			||||||
using DysonNetwork.Shared.Http;
 | 
					using DysonNetwork.Shared.Http;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -32,6 +31,8 @@ builder.Services.AddAppScheduledJobs();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ using DysonNetwork.Ring.Connection;
 | 
				
			|||||||
using DysonNetwork.Ring.Email;
 | 
					using DysonNetwork.Ring.Email;
 | 
				
			||||||
using DysonNetwork.Ring.Notification;
 | 
					using DysonNetwork.Ring.Notification;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
using Google.Protobuf.WellKnownTypes;
 | 
					using Google.Protobuf.WellKnownTypes;
 | 
				
			||||||
using Grpc.Core;
 | 
					using Grpc.Core;
 | 
				
			||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ using System.Text.Json;
 | 
				
			|||||||
using DysonNetwork.Ring.Email;
 | 
					using DysonNetwork.Ring.Email;
 | 
				
			||||||
using DysonNetwork.Ring.Notification;
 | 
					using DysonNetwork.Ring.Notification;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					using DysonNetwork.Shared.Stream;
 | 
				
			||||||
using Google.Protobuf;
 | 
					using Google.Protobuf;
 | 
				
			||||||
using NATS.Client.Core;
 | 
					using NATS.Client.Core;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,11 +21,6 @@ public static class ServiceCollectionExtensions
 | 
				
			|||||||
    public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
 | 
					    public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddDbContext<AppDatabase>();
 | 
					        services.AddDbContext<AppDatabase>();
 | 
				
			||||||
        services.AddSingleton<IConnectionMultiplexer>(_ =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var connection = configuration.GetConnectionString("FastRetrieve")!;
 | 
					 | 
				
			||||||
            return ConnectionMultiplexer.Connect(connection);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
					        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
				
			||||||
        services.AddHttpContextAccessor();
 | 
					        services.AddHttpContextAccessor();
 | 
				
			||||||
        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
					        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,10 +9,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "AllowedHosts": "*",
 | 
					  "AllowedHosts": "*",
 | 
				
			||||||
  "ConnectionStrings": {
 | 
					  "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",
 | 
					    "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"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Notifications": {
 | 
					  "Notifications": {
 | 
				
			||||||
    "Push": {
 | 
					    "Push": {
 | 
				
			||||||
@@ -45,9 +42,7 @@
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Ring",
 | 
					    "Name": "DysonNetwork.Ring",
 | 
				
			||||||
    "Url": "https://localhost:7259",
 | 
					    "Url": "https://localhost:7259"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Etcd": {
 | 
					  "Etcd": {
 | 
				
			||||||
    "Insecure": true
 | 
					    "Insecure": true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<Project Sdk="Microsoft.NET.Sdk">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <PropertyGroup>
 | 
				
			||||||
 | 
					        <TargetFramework>net9.0</TargetFramework>
 | 
				
			||||||
 | 
					        <ImplicitUsings>enable</ImplicitUsings>
 | 
				
			||||||
 | 
					        <Nullable>enable</Nullable>
 | 
				
			||||||
 | 
					        <IsAspireSharedProject>true</IsAspireSharedProject>
 | 
				
			||||||
 | 
					    </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <FrameworkReference Include="Microsoft.AspNetCore.App"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.NATS.Net" Version="9.4.2" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.4.2" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Aspire.StackExchange.Redis" Version="9.4.2" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.7.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.4.2"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.12.0-beta.1" />
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0"/>
 | 
				
			||||||
 | 
					        <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0"/>
 | 
				
			||||||
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</Project>
 | 
				
			||||||
							
								
								
									
										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.Proto;
 | 
				
			||||||
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using Microsoft.Extensions.Configuration;
 | 
					using Microsoft.Extensions.Configuration;
 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					using Microsoft.Extensions.DependencyInjection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,33 +11,7 @@ public static class DysonAuthStartup
 | 
				
			|||||||
        this IServiceCollection services
 | 
					        this IServiceCollection services
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<AuthService.AuthServiceClient>(sp =>
 | 
					        services.AddAuthService();
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            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.AddAuthentication(options =>
 | 
					        services.AddAuthentication(options =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,11 @@
 | 
				
			|||||||
    </PropertyGroup>
 | 
					    </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
        <PackageReference Include="dotnet-etcd" Version="8.0.1" />
 | 
					 | 
				
			||||||
        <PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
 | 
					        <PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
 | 
				
			||||||
        <PackageReference Include="Google.Protobuf" Version="3.31.1" />
 | 
					        <PackageReference Include="Google.Protobuf" Version="3.31.1" />
 | 
				
			||||||
        <PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
 | 
					        <PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
 | 
				
			||||||
        <PackageReference Include="Grpc" Version="2.46.6" />
 | 
					        <PackageReference Include="Grpc" Version="2.46.6" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.71.0" />
 | 
				
			||||||
        <PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
 | 
					        <PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
 | 
				
			||||||
        <PackageReference Include="Grpc.Tools" Version="2.72.0">
 | 
					        <PackageReference Include="Grpc.Tools" Version="2.72.0">
 | 
				
			||||||
            <PrivateAssets>all</PrivateAssets>
 | 
					            <PrivateAssets>all</PrivateAssets>
 | 
				
			||||||
@@ -27,7 +27,6 @@
 | 
				
			|||||||
        <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
 | 
					        <PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
 | 
				
			||||||
        <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
 | 
					        <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
 | 
				
			||||||
        <PackageReference Include="OpenGraph-Net" Version="4.0.1" />
 | 
					        <PackageReference Include="OpenGraph-Net" Version="4.0.1" />
 | 
				
			||||||
        <PackageReference Include="StackExchange.Redis" Version="2.8.41" />
 | 
					 | 
				
			||||||
        <PackageReference Include="System.Net.Http" Version="4.3.4" />
 | 
					        <PackageReference Include="System.Net.Http" Version="4.3.4" />
 | 
				
			||||||
        <PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
 | 
					        <PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
@@ -40,4 +39,8 @@
 | 
				
			|||||||
      <Folder Include="Error\" />
 | 
					      <Folder Include="Error\" />
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					      <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,21 +21,52 @@ public static class KestrelConfiguration
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            options.Limits.MaxRequestBodySize = maxRequestBodySize;
 | 
					            options.Limits.MaxRequestBodySize = maxRequestBodySize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var configuredUrl = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
 | 
					            // gRPC
 | 
				
			||||||
            if (!string.IsNullOrEmpty(configuredUrl)) return;
 | 
					            options.ListenAnyIP(5001, listenOptions =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                listenOptions.Protocols = HttpProtocols.Http2;
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            var certPath = configuration["Service:ClientCert"]!;
 | 
					                var selfSignedCert = _CreateSelfSignedCertificate();
 | 
				
			||||||
            var keyPath = configuration["Service:ClientKey"]!;
 | 
					                listenOptions.UseHttps(selfSignedCert);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Load PEM cert and key manually
 | 
					            var httpPorts = configuration.GetValue<string>("HTTP_PORTS", "5000")
 | 
				
			||||||
            var certificate = X509Certificate2.CreateFromPemFile(certPath, keyPath);
 | 
					                .Split(',', StringSplitOptions.RemoveEmptyEntries)
 | 
				
			||||||
            // Now pass the full cert
 | 
					                .Select(p => int.Parse(p.Trim()))
 | 
				
			||||||
            options.ListenAnyIP(5001, listenOptions => { listenOptions.UseHttps(certificate); });
 | 
					                .ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Optional: HTTP fallback
 | 
					            // Regular HTTP
 | 
				
			||||||
            options.ListenAnyIP(8080);
 | 
					            foreach (var httpPort in httpPorts)
 | 
				
			||||||
 | 
					                options.ListenAnyIP(httpPort,
 | 
				
			||||||
 | 
					                    listenOptions => { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return builder;
 | 
					        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 Grpc.Net.Client;
 | 
				
			||||||
using System.Security.Cryptography.X509Certificates;
 | 
					using System.Security.Cryptography.X509Certificates;
 | 
				
			||||||
using Grpc.Core;
 | 
					using Grpc.Core;
 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Shared.Proto;
 | 
					namespace DysonNetwork.Shared.Proto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public static class GrpcClientHelper
 | 
					public static class GrpcClientHelper
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static CallInvoker CreateCallInvoker(
 | 
					    public static CallInvoker CreateCallInvoker(string url)
 | 
				
			||||||
        string url,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var handler = new HttpClientHandler();
 | 
					        return GrpcChannel.ForAddress(url).CreateCallInvoker();
 | 
				
			||||||
        handler.ClientCertificates.Add(
 | 
					 | 
				
			||||||
            clientCertPassword is null
 | 
					 | 
				
			||||||
                ? X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath)
 | 
					 | 
				
			||||||
                : X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
 | 
					 | 
				
			||||||
        var httpClient = new HttpClient(handler);
 | 
					 | 
				
			||||||
        httpClient.DefaultRequestVersion = HttpVersion.Version20;
 | 
					 | 
				
			||||||
        httpClient.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
 | 
					 | 
				
			||||||
        return GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpClient = httpClient }).CreateCallInvoker();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static async Task<string> GetServiceUrlFromEtcd(IEtcdClient etcdClient, string serviceName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var response = await etcdClient.GetAsync($"/services/{serviceName}");
 | 
					 | 
				
			||||||
        return response.Kvs.Count == 0
 | 
					 | 
				
			||||||
            ? throw new InvalidOperationException($"Service '{serviceName}' not found in Etcd.")
 | 
					 | 
				
			||||||
            : response.Kvs[0].Value.ToStringUtf8();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<AccountService.AccountServiceClient> CreateAccountServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<BotAccountReceiverService.BotAccountReceiverServiceClient>
 | 
					 | 
				
			||||||
        CreateBotAccountReceiverServiceClient(
 | 
					 | 
				
			||||||
            IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
            string clientCertPath,
 | 
					 | 
				
			||||||
            string clientKeyPath,
 | 
					 | 
				
			||||||
            string? clientCertPassword = null
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new BotAccountReceiverService.BotAccountReceiverServiceClient(CreateCallInvoker(url, clientCertPath,
 | 
					 | 
				
			||||||
            clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<ActionLogService.ActionLogServiceClient> CreateActionLogServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new ActionLogService.ActionLogServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<AuthService.AuthServiceClient> CreateAuthServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<PermissionService.PermissionServiceClient> CreatePermissionServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new PermissionService.PermissionServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public static async Task<PaymentService.PaymentServiceClient> CreatePaymentServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
 | 
					 | 
				
			||||||
        return new PaymentService.PaymentServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public static async Task<RingService.RingServiceClient> CreateRingServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Ring");
 | 
					 | 
				
			||||||
        return new RingService.RingServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<FileService.FileServiceClient> CreateFileServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive");
 | 
					 | 
				
			||||||
        return new FileService.FileServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<FileReferenceService.FileReferenceServiceClient> CreateFileReferenceServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive");
 | 
					 | 
				
			||||||
        return new FileReferenceService.FileReferenceServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<PublisherService.PublisherServiceClient> CreatePublisherServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Sphere");
 | 
					 | 
				
			||||||
        return new PublisherService.PublisherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async Task<CustomAppService.CustomAppServiceClient> CreateCustomAppServiceClient(
 | 
					 | 
				
			||||||
        IEtcdClient etcdClient,
 | 
					 | 
				
			||||||
        string clientCertPath,
 | 
					 | 
				
			||||||
        string clientKeyPath,
 | 
					 | 
				
			||||||
        string? clientCertPassword = null
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Develop");
 | 
					 | 
				
			||||||
        return new CustomAppService.CustomAppServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
 | 
					 | 
				
			||||||
            clientCertPassword));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,83 +0,0 @@
 | 
				
			|||||||
using System.Diagnostics;
 | 
					 | 
				
			||||||
using System.Net;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Builder;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Http;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					 | 
				
			||||||
using Yarp.ReverseProxy.Forwarder;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class GatewayReverseProxy
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Provides reverse proxy for DysonNetwork.Gateway.
 | 
					 | 
				
			||||||
    /// Give the ability to the contained frontend to access other services via the gateway.
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    /// <param name="app">The asp.net core application</param>
 | 
					 | 
				
			||||||
    /// <returns>The modified application</returns>
 | 
					 | 
				
			||||||
    public static WebApplication MapGatewayProxy(this WebApplication app)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var httpClient = new HttpMessageInvoker(new SocketsHttpHandler
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            UseProxy = false,
 | 
					 | 
				
			||||||
            AllowAutoRedirect = true,
 | 
					 | 
				
			||||||
            AutomaticDecompression = DecompressionMethods.All,
 | 
					 | 
				
			||||||
            UseCookies = true,
 | 
					 | 
				
			||||||
            EnableMultipleHttp2Connections = true,
 | 
					 | 
				
			||||||
            ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current),
 | 
					 | 
				
			||||||
            ConnectTimeout = TimeSpan.FromSeconds(15),
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var transformer = new GatewayReverseProxyTransformer();
 | 
					 | 
				
			||||||
        var requestConfig = new ForwarderRequestConfig();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        app.Map("/cgi/{**catch-all}", async (HttpContext context, IHttpForwarder forwarder) =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var registry = context.RequestServices.GetRequiredService<ServiceRegistry>();
 | 
					 | 
				
			||||||
            var gatewayUrl = await registry.GetServiceUrl("DysonNetwork.Gateway");
 | 
					 | 
				
			||||||
            if (gatewayUrl is null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                context.Response.StatusCode = 404;
 | 
					 | 
				
			||||||
                await context.Response.WriteAsync("Gateway not found");
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var error = await forwarder.SendAsync(
 | 
					 | 
				
			||||||
                context,
 | 
					 | 
				
			||||||
                gatewayUrl,
 | 
					 | 
				
			||||||
                httpClient,
 | 
					 | 
				
			||||||
                requestConfig,
 | 
					 | 
				
			||||||
                transformer
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            if (error != ForwarderError.None)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var errorFeature = context.GetForwarderErrorFeature();
 | 
					 | 
				
			||||||
                var exception = errorFeature?.Exception;
 | 
					 | 
				
			||||||
                context.Response.StatusCode = 502;
 | 
					 | 
				
			||||||
                context.Response.ContentType = "text/plain";
 | 
					 | 
				
			||||||
                await context.Response.WriteAsync($"Gateway remote error: {exception?.Message}");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return app;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class GatewayReverseProxyTransformer : HttpTransformer
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private const string Value = "/cgi";
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public override ValueTask TransformRequestAsync(
 | 
					 | 
				
			||||||
        HttpContext httpContext,
 | 
					 | 
				
			||||||
        HttpRequestMessage proxyRequest,
 | 
					 | 
				
			||||||
        string destinationPrefix,
 | 
					 | 
				
			||||||
        CancellationToken cancellationToken
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        httpContext.Request.Path = httpContext.Request.Path.StartsWithSegments(Value, out var remaining)
 | 
					 | 
				
			||||||
            ? remaining
 | 
					 | 
				
			||||||
            : httpContext.Request.Path;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        return Default.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,60 +0,0 @@
 | 
				
			|||||||
using Microsoft.Extensions.Configuration;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Hosting;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class RegistryHostedService(
 | 
					 | 
				
			||||||
    ServiceRegistry serviceRegistry,
 | 
					 | 
				
			||||||
    IConfiguration configuration,
 | 
					 | 
				
			||||||
    ILogger<RegistryHostedService> logger
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
    : IHostedService
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private CancellationTokenSource? _cts;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task StartAsync(CancellationToken cancellationToken)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var serviceName = configuration["Service:Name"];
 | 
					 | 
				
			||||||
        var serviceUrl = configuration["Service:Url"];
 | 
					 | 
				
			||||||
        var insecure = configuration.GetValue<bool>("Etcd:Insecure");
 | 
					 | 
				
			||||||
        var remote = configuration.GetConnectionString("Etcd");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (insecure)
 | 
					 | 
				
			||||||
            logger.LogWarning("Etcd is configured to use insecure channel.");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            logger.LogWarning("Service URL or Service Name was not configured. Skipping Etcd registration.");
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        logger.LogInformation(
 | 
					 | 
				
			||||||
            "Registering service {ServiceName} at {ServiceUrl} with Etcd ({Remote}).",
 | 
					 | 
				
			||||||
            serviceName,
 | 
					 | 
				
			||||||
            serviceUrl,
 | 
					 | 
				
			||||||
            remote
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        try
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _cts = new CancellationTokenSource();
 | 
					 | 
				
			||||||
            await serviceRegistry.RegisterService(serviceName, serviceUrl, cancellationToken: _cts.Token);
 | 
					 | 
				
			||||||
            logger.LogInformation("Service {ServiceName} registered successfully.", serviceName);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task StopAsync(CancellationToken cancellationToken)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _cts?.Cancel();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // The lease will expire automatically if the service stops ungracefully.
 | 
					 | 
				
			||||||
        var serviceName = configuration["Service:Name"];
 | 
					 | 
				
			||||||
        if (serviceName is not null)
 | 
					 | 
				
			||||||
            await serviceRegistry.UnregisterService(serviceName);
 | 
					 | 
				
			||||||
        logger.LogInformation("Service registration hosted service is stopping.");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using Microsoft.Extensions.Configuration;
 | 
					using Microsoft.Extensions.Configuration;
 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					using Microsoft.Extensions.DependencyInjection;
 | 
				
			||||||
@@ -9,153 +8,94 @@ public static class ServiceInjectionHelper
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public static IServiceCollection AddRingService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddRingService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<RingService.RingServiceClient>(sp =>
 | 
					        services
 | 
				
			||||||
        {
 | 
					            .AddGrpcClient<RingService.RingServiceClient>(o => o.Address = new Uri("https://_grpc.ring"))
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					            );
 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return GrpcClientHelper
 | 
					        return services;
 | 
				
			||||||
                .CreateRingServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					    }
 | 
				
			||||||
                .GetAwaiter()
 | 
					
 | 
				
			||||||
                .GetResult();
 | 
					    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;
 | 
					        return services;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static IServiceCollection AddAccountService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddAccountService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<AccountService.AccountServiceClient>(sp =>
 | 
					        services
 | 
				
			||||||
        {
 | 
					            .AddGrpcClient<AccountService.AccountServiceClient>(o => o.Address = new Uri("https://_grpc.pass") )
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					            );
 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        services.AddSingleton<AccountClientHelper>();
 | 
					        services.AddSingleton<AccountClientHelper>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddSingleton<BotAccountReceiverService.BotAccountReceiverServiceClient>(sp =>
 | 
					        services
 | 
				
			||||||
        {
 | 
					            .AddGrpcClient<BotAccountReceiverService.BotAccountReceiverServiceClient>(o =>
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					                o.Address = new Uri("https://_grpc.pass")
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					            )
 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return GrpcClientHelper
 | 
					        services.AddGrpcClient<ActionLogService.ActionLogServiceClient>(o => o.Address = new Uri("https://_grpc.pass"))
 | 
				
			||||||
                .CreateBotAccountReceiverServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
                .GetAwaiter()
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
                .GetResult();
 | 
					            );
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddSingleton<ActionLogService.ActionLogServiceClient>(sp =>
 | 
					        services.AddGrpcClient<PaymentService.PaymentServiceClient>(o => o.Address = new Uri("https://_grpc.pass"))
 | 
				
			||||||
        {
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					            );
 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateActionLogServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        }); 
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        services.AddSingleton<PaymentService.PaymentServiceClient>(sp =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
                .CreatePaymentServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static IServiceCollection AddDriveService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddDriveService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<FileService.FileServiceClient>(sp =>
 | 
					        services.AddGrpcClient<FileService.FileServiceClient>(o => o.Address = new Uri("https://_grpc.drive"))
 | 
				
			||||||
        {
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					            );
 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return GrpcClientHelper
 | 
					        services.AddGrpcClient<FileReferenceService.FileReferenceServiceClient>(o => o.Address = new Uri("https://_grpc.drive"))
 | 
				
			||||||
                .CreateFileServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
                .GetAwaiter()
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
                .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"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateFileReferenceServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static IServiceCollection AddPublisherService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddPublisherService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<PublisherService.PublisherServiceClient>(sp =>
 | 
					        services.AddGrpcClient<PublisherService.PublisherServiceClient>(o => o.Address = new Uri("https://_grpc.sphere"))
 | 
				
			||||||
        {
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					            );
 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreatePublisherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static IServiceCollection AddDevelopService(this IServiceCollection services)
 | 
					    public static IServiceCollection AddDevelopService(this IServiceCollection services)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        services.AddSingleton<CustomAppService.CustomAppServiceClient>(sp =>
 | 
					        services.AddGrpcClient<CustomAppService.CustomAppServiceClient>(o => o.Address = new Uri("https://_grpc.develop"))
 | 
				
			||||||
        {
 | 
					            .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
 | 
				
			||||||
            var etcdClient = sp.GetRequiredService<IEtcdClient>();
 | 
					                { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
 | 
				
			||||||
            var config = sp.GetRequiredService<IConfiguration>();
 | 
					            );
 | 
				
			||||||
            var clientCertPath = config["Service:ClientCert"]!;
 | 
					 | 
				
			||||||
            var clientKeyPath = config["Service:ClientKey"]!;
 | 
					 | 
				
			||||||
            var clientCertPassword = config["Service:CertPassword"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GrpcClientHelper
 | 
					 | 
				
			||||||
                .CreateCustomAppServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
 | 
					 | 
				
			||||||
                .GetAwaiter()
 | 
					 | 
				
			||||||
                .GetResult();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return services;
 | 
					        return services;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 }
 | 
					}
 | 
				
			||||||
@@ -1,55 +0,0 @@
 | 
				
			|||||||
using System.Text;
 | 
					 | 
				
			||||||
using dotnet_etcd.interfaces;
 | 
					 | 
				
			||||||
using Etcdserverpb;
 | 
					 | 
				
			||||||
using Google.Protobuf;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public async Task RegisterService(
 | 
					 | 
				
			||||||
        string serviceName,
 | 
					 | 
				
			||||||
        string serviceUrl,
 | 
					 | 
				
			||||||
        long leaseTtlSeconds = 60,
 | 
					 | 
				
			||||||
        CancellationToken cancellationToken = default
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var key = $"/services/{serviceName}";
 | 
					 | 
				
			||||||
        var leaseResponse = await etcd.LeaseGrantAsync(
 | 
					 | 
				
			||||||
            new LeaseGrantRequest { TTL = leaseTtlSeconds },
 | 
					 | 
				
			||||||
            cancellationToken: cancellationToken
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        await etcd.PutAsync(new PutRequest
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Key = ByteString.CopyFrom(key, Encoding.UTF8),
 | 
					 | 
				
			||||||
            Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8),
 | 
					 | 
				
			||||||
            Lease = leaseResponse.ID
 | 
					 | 
				
			||||||
        }, cancellationToken: cancellationToken);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _ = Task.Run(async () =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                await etcd.LeaseKeepAlive(leaseResponse.ID, cancellationToken);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                logger.LogError($"Lease keep-alive failed: {ex.Message}");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }, cancellationToken);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task UnregisterService(string serviceName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var key = $"/services/{serviceName}";
 | 
					 | 
				
			||||||
        await etcd.DeleteAsync(key);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task<string?> GetServiceUrl(string serviceName)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var key = $"/services/{serviceName}";
 | 
					 | 
				
			||||||
        var response = await etcd.GetAsync(key);
 | 
					 | 
				
			||||||
        return response.Kvs.Count == 0 ? null : response.Kvs[0].Value.ToStringUtf8();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
using dotnet_etcd.DependencyInjection;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Configuration;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Registry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class RegistryStartup
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public static IServiceCollection AddRegistryService(
 | 
					 | 
				
			||||||
        this IServiceCollection services,
 | 
					 | 
				
			||||||
        IConfiguration configuration,
 | 
					 | 
				
			||||||
        bool addForwarder = true
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        services.AddEtcdClient(options =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            options.ConnectionString = configuration.GetConnectionString("Etcd");
 | 
					 | 
				
			||||||
            options.UseInsecureChannel = configuration.GetValue<bool>("Etcd:Insecure");
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        services.AddSingleton<ServiceRegistry>();
 | 
					 | 
				
			||||||
        services.AddHostedService<RegistryHostedService>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (addForwarder)
 | 
					 | 
				
			||||||
            services.AddHttpForwarder();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return services;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
using Microsoft.Extensions.Configuration;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.DependencyInjection;
 | 
					 | 
				
			||||||
using NATS.Client.Core;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public static class Connector
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public static IServiceCollection AddStreamConnection(this IServiceCollection services, IConfiguration configuration)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var connectionString = configuration.GetConnectionString("Stream");
 | 
					 | 
				
			||||||
        if (connectionString is null)
 | 
					 | 
				
			||||||
            throw new ArgumentNullException(nameof(connectionString));
 | 
					 | 
				
			||||||
        services.AddSingleton<INatsConnection>(_ => new NatsConnection(new NatsOpts()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Url = connectionString
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        return services;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -2,9 +2,7 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
using DysonNetwork.Shared.Data;
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					 | 
				
			||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
using Account = DysonNetwork.Pass.Account.Account;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Sphere.Chat;
 | 
					namespace DysonNetwork.Sphere.Chat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -75,7 +73,7 @@ public class ChatMember : ModelBase
 | 
				
			|||||||
    public Guid ChatRoomId { get; set; }
 | 
					    public Guid ChatRoomId { get; set; }
 | 
				
			||||||
    public ChatRoom ChatRoom { get; set; } = null!;
 | 
					    public ChatRoom ChatRoom { get; set; } = null!;
 | 
				
			||||||
    public Guid AccountId { get; set; }
 | 
					    public Guid AccountId { get; set; }
 | 
				
			||||||
    [NotMapped] public Account? Account { get; set; }
 | 
					    [NotMapped] public AccountReference? Account { get; set; }
 | 
				
			||||||
    [NotMapped] public AccountStatusReference? Status { get; set; }
 | 
					    [NotMapped] public AccountStatusReference? Status { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [MaxLength(1024)] public string? Nick { get; set; }
 | 
					    [MaxLength(1024)] public string? Nick { get; set; }
 | 
				
			||||||
@@ -108,7 +106,7 @@ public class ChatMemberTransmissionObject : ModelBase
 | 
				
			|||||||
    public Guid Id { get; set; }
 | 
					    public Guid Id { get; set; }
 | 
				
			||||||
    public Guid ChatRoomId { get; set; }
 | 
					    public Guid ChatRoomId { get; set; }
 | 
				
			||||||
    public Guid AccountId { 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; }
 | 
					    [MaxLength(1024)] public string? Nick { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ using DysonNetwork.Shared.Cache;
 | 
				
			|||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
using Account = DysonNetwork.Pass.Account.Account;
 | 
					using Account = DysonNetwork.Shared.Data.AccountReference;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Sphere.Chat;
 | 
					namespace DysonNetwork.Sphere.Chat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,7 +55,6 @@
 | 
				
			|||||||
        <PackageReference Include="Quartz" Version="3.14.0"/>
 | 
					        <PackageReference Include="Quartz" Version="3.14.0"/>
 | 
				
			||||||
        <PackageReference Include="Quartz.AspNetCore" Version="3.14.0"/>
 | 
					        <PackageReference Include="Quartz.AspNetCore" Version="3.14.0"/>
 | 
				
			||||||
        <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0"/>
 | 
					        <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0"/>
 | 
				
			||||||
        <PackageReference Include="StackExchange.Redis" Version="2.8.41"/>
 | 
					 | 
				
			||||||
        <PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0"/>
 | 
					        <PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0"/>
 | 
				
			||||||
        <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3"/>
 | 
					        <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3"/>
 | 
				
			||||||
        <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.3" />
 | 
					        <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.3" />
 | 
				
			||||||
@@ -160,8 +159,8 @@
 | 
				
			|||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ItemGroup>
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <ProjectReference Include="..\DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj" />
 | 
				
			||||||
        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
					        <ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj"/>
 | 
				
			||||||
        <ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj"/>
 | 
					 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
using DysonNetwork.Pass.Account;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Data;
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,5 +63,5 @@ public class PollAnswer : ModelBase
 | 
				
			|||||||
    public Guid AccountId { get; set; }
 | 
					    public Guid AccountId { get; set; }
 | 
				
			||||||
    public Guid PollId { get; set; }
 | 
					    public Guid PollId { get; set; }
 | 
				
			||||||
    [JsonIgnore] public Poll? Poll { 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.ComponentModel.DataAnnotations;
 | 
				
			||||||
using System.Text.Json;
 | 
					using System.Text.Json;
 | 
				
			||||||
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Sphere.Publisher;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
@@ -117,7 +117,7 @@ public class PollController(
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                var protoValue = answeredAccounts.FirstOrDefault(a => a.Id == answer.AccountId.ToString());
 | 
					                var protoValue = answeredAccounts.FirstOrDefault(a => a.Id == answer.AccountId.ToString());
 | 
				
			||||||
                if (protoValue is not null)
 | 
					                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.Http;
 | 
				
			||||||
using DysonNetwork.Shared.PageData;
 | 
					using DysonNetwork.Shared.PageData;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Shared.Stream;
 | 
					 | 
				
			||||||
using DysonNetwork.Sphere;
 | 
					using DysonNetwork.Sphere;
 | 
				
			||||||
using DysonNetwork.Sphere.PageData;
 | 
					using DysonNetwork.Sphere.PageData;
 | 
				
			||||||
using DysonNetwork.Sphere.Startup;
 | 
					using DysonNetwork.Sphere.Startup;
 | 
				
			||||||
@@ -11,15 +10,13 @@ using Microsoft.Extensions.FileProviders;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.AddServiceDefaults();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure Kestrel and server options
 | 
					// Configure Kestrel and server options
 | 
				
			||||||
builder.ConfigureAppKestrel(builder.Configuration);
 | 
					builder.ConfigureAppKestrel(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add metrics and telemetry
 | 
					 | 
				
			||||||
builder.Services.AddAppMetrics();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add application services
 | 
					// Add application services
 | 
				
			||||||
builder.Services.AddRegistryService(builder.Configuration);
 | 
					
 | 
				
			||||||
builder.Services.AddStreamConnection(builder.Configuration);
 | 
					 | 
				
			||||||
builder.Services.AddAppServices(builder.Configuration);
 | 
					builder.Services.AddAppServices(builder.Configuration);
 | 
				
			||||||
builder.Services.AddAppRateLimiting();
 | 
					builder.Services.AddAppRateLimiting();
 | 
				
			||||||
builder.Services.AddAppAuthentication();
 | 
					builder.Services.AddAppAuthentication();
 | 
				
			||||||
@@ -42,6 +39,8 @@ builder.Services.AddTransient<IPageDataProvider, PostPageData>();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.MapDefaultEndpoints();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Run database migrations
 | 
					// Run database migrations
 | 
				
			||||||
using (var scope = app.Services.CreateScope())
 | 
					using (var scope = app.Services.CreateScope())
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -52,8 +51,6 @@ using (var scope = app.Services.CreateScope())
 | 
				
			|||||||
// Configure application middleware pipeline
 | 
					// Configure application middleware pipeline
 | 
				
			||||||
app.ConfigureAppMiddleware(builder.Configuration);
 | 
					app.ConfigureAppMiddleware(builder.Configuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.MapGatewayProxy();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.UseDefaultFiles();
 | 
					app.UseDefaultFiles();
 | 
				
			||||||
app.UseStaticFiles(new StaticFileOptions
 | 
					app.UseStaticFiles(new StaticFileOptions
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore;
 | 
				
			|||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
using NodaTime.Serialization.Protobuf;
 | 
					using NodaTime.Serialization.Protobuf;
 | 
				
			||||||
using VerificationMark = DysonNetwork.Shared.Data.VerificationMark;
 | 
					using VerificationMark = DysonNetwork.Shared.Data.VerificationMark;
 | 
				
			||||||
using Account = DysonNetwork.Pass.Account.Account;
 | 
					using Account = DysonNetwork.Shared.Data.AccountReference;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Sphere.Publisher;
 | 
					namespace DysonNetwork.Sphere.Publisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ public class PublisherController(
 | 
				
			|||||||
        var account = await accounts.GetAccountAsync(
 | 
					        var account = await accounts.GetAccountAsync(
 | 
				
			||||||
            new GetAccountRequest { Id = publisher.AccountId.Value.ToString() }
 | 
					            new GetAccountRequest { Id = publisher.AccountId.Value.ToString() }
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        publisher.Account = Pass.Account.Account.FromProtoValue(account);
 | 
					        publisher.Account = AccountReference.FromProtoValue(account);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Ok(publisher);
 | 
					        return Ok(publisher);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -382,7 +382,7 @@ public class PublisherService(
 | 
				
			|||||||
    public async Task<PublisherMember> LoadMemberAccount(PublisherMember member)
 | 
					    public async Task<PublisherMember> LoadMemberAccount(PublisherMember member)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var account = await accountsHelper.GetAccount(member.AccountId);
 | 
					        var account = await accountsHelper.GetAccount(member.AccountId);
 | 
				
			||||||
        member.Account = Pass.Account.Account.FromProtoValue(account);
 | 
					        member.Account = AccountReference.FromProtoValue(account);
 | 
				
			||||||
        return member;
 | 
					        return member;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -394,7 +394,7 @@ public class PublisherService(
 | 
				
			|||||||
        return members.Select(m =>
 | 
					        return members.Select(m =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (accounts.TryGetValue(m.AccountId, out var account))
 | 
					            if (accounts.TryGetValue(m.AccountId, out var account))
 | 
				
			||||||
                m.Account = Pass.Account.Account.FromProtoValue(account);
 | 
					                m.Account = AccountReference.FromProtoValue(account);
 | 
				
			||||||
            return m;
 | 
					            return m;
 | 
				
			||||||
        }).ToList();
 | 
					        }).ToList();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations;
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
using DysonNetwork.Pass.Account;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Data;
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using DysonNetwork.Sphere.Chat;
 | 
					using DysonNetwork.Sphere.Chat;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
@@ -48,7 +47,7 @@ public class RealmMember : ModelBase
 | 
				
			|||||||
    public Guid RealmId { get; set; }
 | 
					    public Guid RealmId { get; set; }
 | 
				
			||||||
    public Realm Realm { get; set; } = null!;
 | 
					    public Realm Realm { get; set; } = null!;
 | 
				
			||||||
    public Guid AccountId { get; set; }
 | 
					    public Guid AccountId { get; set; }
 | 
				
			||||||
    [NotMapped] public Account? Account { get; set; }
 | 
					    [NotMapped] public AccountReference? Account { get; set; }
 | 
				
			||||||
    [NotMapped] public AccountStatusReference? Status { get; set; }
 | 
					    [NotMapped] public AccountStatusReference? Status { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public int Role { get; set; } = RealmMemberRole.Normal;
 | 
					    public int Role { get; set; } = RealmMemberRole.Normal;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc;
 | 
				
			|||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
using Google.Protobuf.WellKnownTypes;
 | 
					using Google.Protobuf.WellKnownTypes;
 | 
				
			||||||
using Microsoft.AspNetCore.Http.HttpResults;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Sphere.Realm;
 | 
					namespace DysonNetwork.Sphere.Realm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
using DysonNetwork.Shared;
 | 
					using DysonNetwork.Shared;
 | 
				
			||||||
using DysonNetwork.Shared.Cache;
 | 
					using DysonNetwork.Shared.Cache;
 | 
				
			||||||
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using DysonNetwork.Shared.Proto;
 | 
					using DysonNetwork.Shared.Proto;
 | 
				
			||||||
using DysonNetwork.Shared.Registry;
 | 
					using DysonNetwork.Shared.Registry;
 | 
				
			||||||
using DysonNetwork.Sphere.Localization;
 | 
					using DysonNetwork.Sphere.Localization;
 | 
				
			||||||
@@ -73,7 +74,7 @@ public class RealmService(
 | 
				
			|||||||
    public async Task<RealmMember> LoadMemberAccount(RealmMember member)
 | 
					    public async Task<RealmMember> LoadMemberAccount(RealmMember member)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var account = await accountsHelper.GetAccount(member.AccountId);
 | 
					        var account = await accountsHelper.GetAccount(member.AccountId);
 | 
				
			||||||
        member.Account = Pass.Account.Account.FromProtoValue(account);
 | 
					        member.Account = AccountReference.FromProtoValue(account);
 | 
				
			||||||
        return member;
 | 
					        return member;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,7 +86,7 @@ public class RealmService(
 | 
				
			|||||||
        return members.Select(m =>
 | 
					        return members.Select(m =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (accounts.TryGetValue(m.AccountId, out var account))
 | 
					            if (accounts.TryGetValue(m.AccountId, out var account))
 | 
				
			||||||
                m.Account = Pass.Account.Account.FromProtoValue(account);
 | 
					                m.Account = AccountReference.FromProtoValue(account);
 | 
				
			||||||
            return m;
 | 
					            return m;
 | 
				
			||||||
        }).ToList();
 | 
					        }).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.AddLocalization(options => options.ResourcesPath = "Resources");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        services.AddDbContext<AppDatabase>();
 | 
					        services.AddDbContext<AppDatabase>();
 | 
				
			||||||
        services.AddSingleton<IConnectionMultiplexer>(_ =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var connection = configuration.GetConnectionString("FastRetrieve")!;
 | 
					 | 
				
			||||||
            return ConnectionMultiplexer.Connect(connection);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
					        services.AddSingleton<IClock>(SystemClock.Instance);
 | 
				
			||||||
        services.AddHttpContextAccessor();
 | 
					        services.AddHttpContextAccessor();
 | 
				
			||||||
        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
					        services.AddSingleton<ICacheService, CacheServiceRedis>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations;
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
using DysonNetwork.Pass.Account;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Data;
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,5 +45,5 @@ public class StickerPackOwnership : ModelBase
 | 
				
			|||||||
    public Guid PackId { get; set; }
 | 
					    public Guid PackId { get; set; }
 | 
				
			||||||
    public StickerPack Pack { get; set; } = null!;
 | 
					    public StickerPack Pack { get; set; } = null!;
 | 
				
			||||||
    public Guid AccountId { get; set; }
 | 
					    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;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
using DysonNetwork.Pass.Account;
 | 
					 | 
				
			||||||
using DysonNetwork.Shared.Data;
 | 
					using DysonNetwork.Shared.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Sphere.WebReader;
 | 
					namespace DysonNetwork.Sphere.WebReader;
 | 
				
			||||||
@@ -54,5 +53,5 @@ public class WebFeedSubscription : ModelBase
 | 
				
			|||||||
    public Guid FeedId { get; set; }
 | 
					    public Guid FeedId { get; set; }
 | 
				
			||||||
    public WebFeed Feed { get; set; } = null!;
 | 
					    public WebFeed Feed { get; set; } = null!;
 | 
				
			||||||
    public Guid AccountId { get; set; }
 | 
					    public Guid AccountId { get; set; }
 | 
				
			||||||
    [NotMapped] public Account Account { get; set; } = null!;
 | 
					    [NotMapped] public AccountReference Account { get; set; } = null!;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -10,10 +10,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "AllowedHosts": "*",
 | 
					  "AllowedHosts": "*",
 | 
				
			||||||
  "ConnectionStrings": {
 | 
					  "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",
 | 
					    "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"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "GeoIp": {
 | 
					  "GeoIp": {
 | 
				
			||||||
    "DatabasePath": "./Keys/GeoLite2-City.mmdb"
 | 
					    "DatabasePath": "./Keys/GeoLite2-City.mmdb"
 | 
				
			||||||
@@ -39,8 +36,6 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "Service": {
 | 
					  "Service": {
 | 
				
			||||||
    "Name": "DysonNetwork.Sphere",
 | 
					    "Name": "DysonNetwork.Sphere",
 | 
				
			||||||
    "Url": "https://localhost:7099",
 | 
					    "Url": "https://localhost:7099"
 | 
				
			||||||
    "ClientCert": "../Certificates/client.crt",
 | 
					 | 
				
			||||||
    "ClientKey": "../Certificates/client.key"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,10 +16,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Ring", "DysonN
 | 
				
			|||||||
EndProject
 | 
					EndProject
 | 
				
			||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Drive", "DysonNetwork.Drive\DysonNetwork.Drive.csproj", "{8DE0B783-8852-494D-B90A-201ABBB71202}"
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Drive", "DysonNetwork.Drive\DysonNetwork.Drive.csproj", "{8DE0B783-8852-494D-B90A-201ABBB71202}"
 | 
				
			||||||
EndProject
 | 
					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}"
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Develop", "DysonNetwork.Develop\DysonNetwork.Develop.csproj", "{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}"
 | 
				
			||||||
EndProject
 | 
					EndProject
 | 
				
			||||||
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Control", "DysonNetwork.Control\DysonNetwork.Control.csproj", "{7FFED190-51C7-4302-A8B5-96C839463458}"
 | 
				
			||||||
 | 
					EndProject
 | 
				
			||||||
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.ServiceDefaults", "DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj", "{877AAD96-C257-4305-9F1C-C9D9C9BEE615}"
 | 
				
			||||||
 | 
					EndProject
 | 
				
			||||||
Global
 | 
					Global
 | 
				
			||||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
						GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
				
			||||||
		Debug|Any CPU = Debug|Any CPU
 | 
							Debug|Any CPU = Debug|Any CPU
 | 
				
			||||||
@@ -46,13 +48,17 @@ Global
 | 
				
			|||||||
		{8DE0B783-8852-494D-B90A-201ABBB71202}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
							{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.ActiveCfg = Release|Any CPU
 | 
				
			||||||
		{8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
 | 
				
			||||||
		{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
							{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
		{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
							{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
		{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
							{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
 | 
							{7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
 | 
							{877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
	EndGlobalSection
 | 
						EndGlobalSection
 | 
				
			||||||
EndGlobal
 | 
					EndGlobal
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@
 | 
				
			|||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F0f_003F51443844_003FApnSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F0f_003F51443844_003FApnSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArgumentNullException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003Faf_003F30ff0e5c_003FArgumentNullException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArgumentNullException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003Faf_003F30ff0e5c_003FArgumentNullException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArraySegment_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003F44_003Fb46e5e1b_003FArraySegment_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArraySegment_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003F44_003Fb46e5e1b_003FArraySegment_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAspireNatsClientExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F991136f2b57436db7b12f422388f1384993f9288637d2c461e0e9da6b55a88b_003FAspireNatsClientExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationHandler_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1f1354e4dbf943ecb04840af5ff9a527fa20_003F5d_003F1fb111f6_003FAuthenticationHandler_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationHandler_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1f1354e4dbf943ecb04840af5ff9a527fa20_003F5d_003F1fb111f6_003FAuthenticationHandler_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9b24a56e61ae4d86a9e8ba13482a2db924600_003F5b_003F9e854504_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9b24a56e61ae4d86a9e8ba13482a2db924600_003F5b_003F9e854504_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
@@ -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_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_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_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_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_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>
 | 
						<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_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_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_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_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_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_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_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>
 | 
						<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_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_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_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_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_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>
 | 
						<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_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_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_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_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_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>
 | 
						<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_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_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_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_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_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>
 | 
						<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_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffac2de31b5cc4ce09217dd126ac6f67b22000_003F5f_003Fd31c33fc_003FServiceCollectionServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffac2de31b5cc4ce09217dd126ac6f67b22000_003F5f_003Fd31c33fc_003FServiceCollectionServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceDiscoveryServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F2ce4b29a828792eb9a714f13dc06413f31e473999dea7bb4b1116c5fc0b3b3_003FServiceDiscoveryServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fce37be1a06b16c6faa02038d2cc477dd3bca5b217ceeb41c5f2ad45c1bf9_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fce37be1a06b16c6faa02038d2cc477dd3bca5b217ceeb41c5f2ad45c1bf9_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
						<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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