From b62e8d5ccad8c2e745cc939c54be335702b48cfd Mon Sep 17 00:00:00 2001 From: Ivan Sentemon Date: Sun, 5 Jan 2025 10:39:50 +0100 Subject: [PATCH 1/7] Add Gateway.Api projects to the solution --- FitnessApp.sln | 10 +++++ .../Gateway/Gateway.Api/Gateway.Api.csproj | 14 +++++++ backend/src/Gateway/Gateway.Api/Program.cs | 7 ++++ .../Properties/launchSettings.json | 41 +++++++++++++++++++ .../src/Gateway/Gateway.Api/appsettings.json | 9 ++++ 5 files changed, 81 insertions(+) create mode 100644 backend/src/Gateway/Gateway.Api/Gateway.Api.csproj create mode 100644 backend/src/Gateway/Gateway.Api/Program.cs create mode 100644 backend/src/Gateway/Gateway.Api/Properties/launchSettings.json create mode 100644 backend/src/Gateway/Gateway.Api/appsettings.json diff --git a/FitnessApp.sln b/FitnessApp.sln index d1498e5..3be196b 100644 --- a/FitnessApp.sln +++ b/FitnessApp.sln @@ -76,6 +76,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build-and-test", "build-and EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.DTO", "backend\src\Shared\Shared.DTO\Shared.DTO.csproj", "{334DF17C-BD69-47B9-B4D0-ACB5AD6223CF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "backend\src\Gateway", "{DC9B9F00-24BF-4AD3-B45A-38BFCFC309D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gateway.Api", "backend\src\Gateway\Gateway.Api\Gateway.Api.csproj", "{830B4F05-7C89-4C0F-B27D-B1008B605E79}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -142,6 +146,10 @@ Global {334DF17C-BD69-47B9-B4D0-ACB5AD6223CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {334DF17C-BD69-47B9-B4D0-ACB5AD6223CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {334DF17C-BD69-47B9-B4D0-ACB5AD6223CF}.Release|Any CPU.Build.0 = Release|Any CPU + {830B4F05-7C89-4C0F-B27D-B1008B605E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {830B4F05-7C89-4C0F-B27D-B1008B605E79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {830B4F05-7C89-4C0F-B27D-B1008B605E79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {830B4F05-7C89-4C0F-B27D-B1008B605E79}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {2E44C019-ED30-4671-A878-D2F7B6071024} = {84AE3F00-9A59-4A8D-A737-67AB74E5D1FD} @@ -167,5 +175,7 @@ Global {499BA3B4-A81F-4079-B256-CC04927BB06D} = {D5A12D19-86E3-4E83-8BF0-D9AF8C2477F3} {BC8EDBE4-B8BD-4934-B096-0C2E1C1E84C7} = {499BA3B4-A81F-4079-B256-CC04927BB06D} {334DF17C-BD69-47B9-B4D0-ACB5AD6223CF} = {112E9C63-BF62-49C1-AD19-03855535225B} + {DC9B9F00-24BF-4AD3-B45A-38BFCFC309D7} = {B7E4E4CF-E7A9-4DAC-807E-DFF17553BAE8} + {830B4F05-7C89-4C0F-B27D-B1008B605E79} = {DC9B9F00-24BF-4AD3-B45A-38BFCFC309D7} EndGlobalSection EndGlobal diff --git a/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj b/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj new file mode 100644 index 0000000..48e39a5 --- /dev/null +++ b/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/backend/src/Gateway/Gateway.Api/Program.cs b/backend/src/Gateway/Gateway.Api/Program.cs new file mode 100644 index 0000000..816f3ac --- /dev/null +++ b/backend/src/Gateway/Gateway.Api/Program.cs @@ -0,0 +1,7 @@ +var builder = WebApplication.CreateBuilder(args); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.Run(); \ No newline at end of file diff --git a/backend/src/Gateway/Gateway.Api/Properties/launchSettings.json b/backend/src/Gateway/Gateway.Api/Properties/launchSettings.json new file mode 100644 index 0000000..dbdd1bc --- /dev/null +++ b/backend/src/Gateway/Gateway.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:37518", + "sslPort": 44340 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5215", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7204;http://localhost:5215", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/src/Gateway/Gateway.Api/appsettings.json b/backend/src/Gateway/Gateway.Api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/backend/src/Gateway/Gateway.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From cad0f82d3b828c88f725a27a164c85d2b8e34cfc Mon Sep 17 00:00:00 2001 From: Ivan Sentemon Date: Sun, 5 Jan 2025 15:28:37 +0100 Subject: [PATCH 2/7] Add configuration constants for CORS and web host settings --- .../Constants/AppSettingsConstants.cs | 7 +++++ .../Gateway.Api/Constants/CorsConstants.cs | 6 ++++ .../Gateway/Gateway.Api/Gateway.Api.csproj | 5 ---- backend/src/Gateway/Gateway.Api/Program.cs | 28 ++++++++++++++++++- .../src/Gateway/Gateway.Api/appsettings.json | 4 ++- 5 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 backend/src/Gateway/Gateway.Api/Constants/AppSettingsConstants.cs create mode 100644 backend/src/Gateway/Gateway.Api/Constants/CorsConstants.cs diff --git a/backend/src/Gateway/Gateway.Api/Constants/AppSettingsConstants.cs b/backend/src/Gateway/Gateway.Api/Constants/AppSettingsConstants.cs new file mode 100644 index 0000000..82b1114 --- /dev/null +++ b/backend/src/Gateway/Gateway.Api/Constants/AppSettingsConstants.cs @@ -0,0 +1,7 @@ +namespace Gateway.Api.Constants; + +public class AppSettingsConstants +{ + public const string WebHostUrl = "WebHostUrl"; + public const string AllowedOrigins = "AllowedOrigins"; +} \ No newline at end of file diff --git a/backend/src/Gateway/Gateway.Api/Constants/CorsConstants.cs b/backend/src/Gateway/Gateway.Api/Constants/CorsConstants.cs new file mode 100644 index 0000000..ca86a11 --- /dev/null +++ b/backend/src/Gateway/Gateway.Api/Constants/CorsConstants.cs @@ -0,0 +1,6 @@ +namespace Gateway.Api.Constants; + +public class CorsConstants +{ + public const string CorsPolicy = "CorsPolicy"; +} \ No newline at end of file diff --git a/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj b/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj index 48e39a5..c150414 100644 --- a/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj +++ b/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj @@ -6,9 +6,4 @@ enable - - - - - diff --git a/backend/src/Gateway/Gateway.Api/Program.cs b/backend/src/Gateway/Gateway.Api/Program.cs index 816f3ac..7973115 100644 --- a/backend/src/Gateway/Gateway.Api/Program.cs +++ b/backend/src/Gateway/Gateway.Api/Program.cs @@ -1,7 +1,33 @@ +using Gateway.Api.Constants; + var builder = WebApplication.CreateBuilder(args); +var allowedOrigins = builder.Configuration.GetSection(AppSettingsConstants.AllowedOrigins).Get(); +var hostingUrl = builder.Configuration[AppSettingsConstants.WebHostUrl]; + +builder.WebHost.UseUrls(hostingUrl ?? throw new ArgumentNullException(nameof(hostingUrl), "Hosting URL is not configured.")); + +builder.Services + .AddCors(options => + { + options.AddPolicy(CorsConstants.CorsPolicy, policyBuilder => + { + policyBuilder + .WithOrigins(allowedOrigins ?? throw new ArgumentNullException(nameof(allowedOrigins), + "Allowed Origin URLs are not configured.")) + .AllowCredentials() + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }); + var app = builder.Build(); -app.UseHttpsRedirection(); +app.UseCors(CorsConstants.CorsPolicy); + +if (!app.Environment.IsDevelopment()) +{ + app.UseHttpsRedirection(); +} app.Run(); \ No newline at end of file diff --git a/backend/src/Gateway/Gateway.Api/appsettings.json b/backend/src/Gateway/Gateway.Api/appsettings.json index 10f68b8..21eab83 100644 --- a/backend/src/Gateway/Gateway.Api/appsettings.json +++ b/backend/src/Gateway/Gateway.Api/appsettings.json @@ -5,5 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "WebHostUrl": "http://0.0.0.0:8000", + "AllowedOrigins": [ "http://localhost:4000", "http://localhost:4200", "http://localhost:8080" ] } From 441c48d40a78ab65bf12f40c8db75713f2fa4d36 Mon Sep 17 00:00:00 2001 From: Ivan Sentemon Date: Sun, 5 Jan 2025 16:20:18 +0100 Subject: [PATCH 3/7] Add YARP configuration for reverse proxy --- FitnessApp.sln | 2 +- .../src/Gateway/Gateway.Api/appsettings.json | 11 ---- .../Constants/AppSettingsConstants.cs | 3 +- .../Constants/CorsConstants.cs | 2 +- .../Gateway.csproj} | 4 ++ .../{Gateway.Api => Gateway}/Program.cs | 13 +++-- .../Properties/launchSettings.json | 0 backend/src/Gateway/Gateway/appsettings.json | 54 +++++++++++++++++++ 8 files changed, 72 insertions(+), 17 deletions(-) delete mode 100644 backend/src/Gateway/Gateway.Api/appsettings.json rename backend/src/Gateway/{Gateway.Api => Gateway}/Constants/AppSettingsConstants.cs (63%) rename backend/src/Gateway/{Gateway.Api => Gateway}/Constants/CorsConstants.cs (71%) rename backend/src/Gateway/{Gateway.Api/Gateway.Api.csproj => Gateway/Gateway.csproj} (68%) rename backend/src/Gateway/{Gateway.Api => Gateway}/Program.cs (80%) rename backend/src/Gateway/{Gateway.Api => Gateway}/Properties/launchSettings.json (100%) create mode 100644 backend/src/Gateway/Gateway/appsettings.json diff --git a/FitnessApp.sln b/FitnessApp.sln index 3be196b..004ee3f 100644 --- a/FitnessApp.sln +++ b/FitnessApp.sln @@ -78,7 +78,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.DTO", "backend\src\S EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "backend\src\Gateway", "{DC9B9F00-24BF-4AD3-B45A-38BFCFC309D7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gateway.Api", "backend\src\Gateway\Gateway.Api\Gateway.Api.csproj", "{830B4F05-7C89-4C0F-B27D-B1008B605E79}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gateway", "backend\src\Gateway\Gateway\Gateway.csproj", "{830B4F05-7C89-4C0F-B27D-B1008B605E79}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/backend/src/Gateway/Gateway.Api/appsettings.json b/backend/src/Gateway/Gateway.Api/appsettings.json deleted file mode 100644 index 21eab83..0000000 --- a/backend/src/Gateway/Gateway.Api/appsettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "WebHostUrl": "http://0.0.0.0:8000", - "AllowedOrigins": [ "http://localhost:4000", "http://localhost:4200", "http://localhost:8080" ] -} diff --git a/backend/src/Gateway/Gateway.Api/Constants/AppSettingsConstants.cs b/backend/src/Gateway/Gateway/Constants/AppSettingsConstants.cs similarity index 63% rename from backend/src/Gateway/Gateway.Api/Constants/AppSettingsConstants.cs rename to backend/src/Gateway/Gateway/Constants/AppSettingsConstants.cs index 82b1114..cbe2b14 100644 --- a/backend/src/Gateway/Gateway.Api/Constants/AppSettingsConstants.cs +++ b/backend/src/Gateway/Gateway/Constants/AppSettingsConstants.cs @@ -1,7 +1,8 @@ -namespace Gateway.Api.Constants; +namespace Gateway.Constants; public class AppSettingsConstants { public const string WebHostUrl = "WebHostUrl"; public const string AllowedOrigins = "AllowedOrigins"; + public const string ReverseProxy = "ReverseProxy"; } \ No newline at end of file diff --git a/backend/src/Gateway/Gateway.Api/Constants/CorsConstants.cs b/backend/src/Gateway/Gateway/Constants/CorsConstants.cs similarity index 71% rename from backend/src/Gateway/Gateway.Api/Constants/CorsConstants.cs rename to backend/src/Gateway/Gateway/Constants/CorsConstants.cs index ca86a11..2a9aad7 100644 --- a/backend/src/Gateway/Gateway.Api/Constants/CorsConstants.cs +++ b/backend/src/Gateway/Gateway/Constants/CorsConstants.cs @@ -1,4 +1,4 @@ -namespace Gateway.Api.Constants; +namespace Gateway.Constants; public class CorsConstants { diff --git a/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj b/backend/src/Gateway/Gateway/Gateway.csproj similarity index 68% rename from backend/src/Gateway/Gateway.Api/Gateway.Api.csproj rename to backend/src/Gateway/Gateway/Gateway.csproj index c150414..c95b170 100644 --- a/backend/src/Gateway/Gateway.Api/Gateway.Api.csproj +++ b/backend/src/Gateway/Gateway/Gateway.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/backend/src/Gateway/Gateway.Api/Program.cs b/backend/src/Gateway/Gateway/Program.cs similarity index 80% rename from backend/src/Gateway/Gateway.Api/Program.cs rename to backend/src/Gateway/Gateway/Program.cs index 7973115..688d6f7 100644 --- a/backend/src/Gateway/Gateway.Api/Program.cs +++ b/backend/src/Gateway/Gateway/Program.cs @@ -1,9 +1,10 @@ -using Gateway.Api.Constants; +using Gateway.Constants; var builder = WebApplication.CreateBuilder(args); var allowedOrigins = builder.Configuration.GetSection(AppSettingsConstants.AllowedOrigins).Get(); var hostingUrl = builder.Configuration[AppSettingsConstants.WebHostUrl]; +var reverseProxyConfig = builder.Configuration.GetSection(AppSettingsConstants.ReverseProxy); builder.WebHost.UseUrls(hostingUrl ?? throw new ArgumentNullException(nameof(hostingUrl), "Hosting URL is not configured.")); @@ -21,13 +22,19 @@ }); }); -var app = builder.Build(); +builder.Services + .AddReverseProxy() + .LoadFromConfig(reverseProxyConfig); -app.UseCors(CorsConstants.CorsPolicy); +var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } +app.UseCors(CorsConstants.CorsPolicy); + +app.MapReverseProxy(); + app.Run(); \ No newline at end of file diff --git a/backend/src/Gateway/Gateway.Api/Properties/launchSettings.json b/backend/src/Gateway/Gateway/Properties/launchSettings.json similarity index 100% rename from backend/src/Gateway/Gateway.Api/Properties/launchSettings.json rename to backend/src/Gateway/Gateway/Properties/launchSettings.json diff --git a/backend/src/Gateway/Gateway/appsettings.json b/backend/src/Gateway/Gateway/appsettings.json new file mode 100644 index 0000000..9bc3602 --- /dev/null +++ b/backend/src/Gateway/Gateway/appsettings.json @@ -0,0 +1,54 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "WebHostUrl": "http://0.0.0.0:8000", + "AllowedOrigins": [ "http://localhost:4200", "http://localhost:8080" ], + "ReverseProxy": { + "Routes": { + "AuthService": { + "ClusterId": "auth-service", + "Match": { + "Path": "/auth/{endpoint:regex(^graphql$|^health$)}" + }, + "Transforms": [ + { + "PathRemovePrefix": "/auth" + } + ] + }, + "PostService": { + "ClusterId": "post-service", + "Match": { + "Path": "/post/{endpoint:regex(^graphql$|^health$)}" + }, + "Transforms": [ + { + "PathRemovePrefix": "/post" + } + ], + "WebSocket": true + } + }, + "Clusters": { + "auth-service": { + "Destinations": { + "destination1": { + "Address": "http://localhost:8082" + } + } + }, + "post-service": { + "Destinations": { + "destination1": { + "Address": "http://localhost:8081" + } + } + } + } + } +} From 630be92fdbd22a4546b4bc61fa396607859a9fd3 Mon Sep 17 00:00:00 2001 From: Ivan Sentemon Date: Sun, 5 Jan 2025 16:46:54 +0100 Subject: [PATCH 4/7] Fix update user event in PostService --- .../Consumers/UserUpdatedEventConsumer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/PostService/PostService.Application/Consumers/UserUpdatedEventConsumer.cs b/backend/src/PostService/PostService.Application/Consumers/UserUpdatedEventConsumer.cs index e60edbd..1d017ba 100644 --- a/backend/src/PostService/PostService.Application/Consumers/UserUpdatedEventConsumer.cs +++ b/backend/src/PostService/PostService.Application/Consumers/UserUpdatedEventConsumer.cs @@ -26,5 +26,7 @@ public async Task Consume(ConsumeContext context) @event.Username, @event.ImageUrl ); + + await _dbContext.SaveChangesAsync(); } } \ No newline at end of file From 7897f305adfd3469f15781753de08d0c9d6a8053 Mon Sep 17 00:00:00 2001 From: Ivan Sentemon Date: Sun, 5 Jan 2025 19:43:13 +0100 Subject: [PATCH 5/7] Update service ports and environment configurations --- .github/workflows/compose-build.yml | 8 +++--- .../Properties/launchSettings.json | 4 +-- .../AuthService.Api/appsettings.json | 2 +- backend/src/AuthService/Dockerfile | 2 +- .../Gateway/Properties/launchSettings.json | 8 +++--- backend/src/Gateway/Gateway/appsettings.json | 4 +-- backend/src/PostService/Dockerfile | 2 +- .../Properties/launchSettings.json | 4 +-- .../PostService.Api/appsettings.json | 2 +- docker-compose.yml | 8 +++--- frontend/src/app/app.config.ts | 25 +++++++++++-------- .../features/auth/services/auth.service.ts | 11 ++++++-- .../features/auth/services/user.service.ts | 5 +++- frontend/src/environments/environment.ts | 3 ++- 14 files changed, 52 insertions(+), 36 deletions(-) diff --git a/.github/workflows/compose-build.yml b/.github/workflows/compose-build.yml index 6eb6b5a..2dd5721 100644 --- a/.github/workflows/compose-build.yml +++ b/.github/workflows/compose-build.yml @@ -26,14 +26,14 @@ jobs: run: | echo "Checking health..." for i in {1..10}; do - curl --fail http://0.0.0.0:8082/health && break || sleep 5 + curl --fail http://0.0.0.0:8000/auth/health && break || sleep 5 done - curl --fail http://0.0.0.0:8082/health || exit 1 + curl --fail http://0.0.0.0:8000/auth/health || exit 1 - name: Health Check PostService run: | echo "Checking health..." for i in {1..10}; do - curl --fail http://0.0.0.0:8081/health && break || sleep 5 + curl --fail http://0.0.0.0:8000/post/health && break || sleep 5 done - curl --fail http://0.0.0.0:8081/health || exit 1 \ No newline at end of file + curl --fail http://0.0.0.0:8000/post/health || exit 1 \ No newline at end of file diff --git a/backend/src/AuthService/AuthService.Api/Properties/launchSettings.json b/backend/src/AuthService/AuthService.Api/Properties/launchSettings.json index 1a0ab26..a626a88 100644 --- a/backend/src/AuthService/AuthService.Api/Properties/launchSettings.json +++ b/backend/src/AuthService/AuthService.Api/Properties/launchSettings.json @@ -14,7 +14,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "graphql", - "applicationUrl": "http://localhost:8082", + "applicationUrl": "http://localhost:8001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -24,7 +24,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "graphql", - "applicationUrl": "https://localhost:7165;http://localhost:8082", + "applicationUrl": "https://localhost:7165;http://localhost:8001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/backend/src/AuthService/AuthService.Api/appsettings.json b/backend/src/AuthService/AuthService.Api/appsettings.json index 398010b..612994a 100644 --- a/backend/src/AuthService/AuthService.Api/appsettings.json +++ b/backend/src/AuthService/AuthService.Api/appsettings.json @@ -8,7 +8,7 @@ "AllowedHosts": "*", "AllowedOrigins": [ "http://localhost:4000", "http://localhost:4200", "http://localhost:8080" ], "DatabaseConnectionString": "Host=localhost;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=AuthDb", - "WebHostUrl": "http://0.0.0.0:8082", + "WebHostUrl": "http://0.0.0.0:8001", "Keycloak": { "Url": "http://localhost:8080", "Realm": "fitness-app-realm", diff --git a/backend/src/AuthService/Dockerfile b/backend/src/AuthService/Dockerfile index 89ad510..d012ee2 100644 --- a/backend/src/AuthService/Dockerfile +++ b/backend/src/AuthService/Dockerfile @@ -1,6 +1,6 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -EXPOSE 8081 +EXPOSE 8001 RUN apt-get update && apt-get install -y curl && apt-get clean diff --git a/backend/src/Gateway/Gateway/Properties/launchSettings.json b/backend/src/Gateway/Gateway/Properties/launchSettings.json index dbdd1bc..5947b26 100644 --- a/backend/src/Gateway/Gateway/Properties/launchSettings.json +++ b/backend/src/Gateway/Gateway/Properties/launchSettings.json @@ -13,8 +13,8 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5215", + "launchUrl": "", + "applicationUrl": "http://localhost:8000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -23,8 +23,8 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7204;http://localhost:5215", + "launchUrl": "", + "applicationUrl": "https://localhost:7204;http://localhost:8000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/backend/src/Gateway/Gateway/appsettings.json b/backend/src/Gateway/Gateway/appsettings.json index 9bc3602..f51fe42 100644 --- a/backend/src/Gateway/Gateway/appsettings.json +++ b/backend/src/Gateway/Gateway/appsettings.json @@ -38,14 +38,14 @@ "auth-service": { "Destinations": { "destination1": { - "Address": "http://localhost:8082" + "Address": "http://localhost:8001" } } }, "post-service": { "Destinations": { "destination1": { - "Address": "http://localhost:8081" + "Address": "http://localhost:8002" } } } diff --git a/backend/src/PostService/Dockerfile b/backend/src/PostService/Dockerfile index b5de60d..1b4b50d 100644 --- a/backend/src/PostService/Dockerfile +++ b/backend/src/PostService/Dockerfile @@ -1,6 +1,6 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -EXPOSE 8081 +EXPOSE 8002 RUN apt-get update && apt-get install -y curl && apt-get clean diff --git a/backend/src/PostService/PostService.Api/Properties/launchSettings.json b/backend/src/PostService/PostService.Api/Properties/launchSettings.json index 6b5b134..a08192a 100644 --- a/backend/src/PostService/PostService.Api/Properties/launchSettings.json +++ b/backend/src/PostService/PostService.Api/Properties/launchSettings.json @@ -14,7 +14,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "graphql", - "applicationUrl": "http://localhost:8081", + "applicationUrl": "http://localhost:8002", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -24,7 +24,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "graphql", - "applicationUrl": "https://localhost:7112;http://localhost:8081", + "applicationUrl": "https://localhost:7112;http://localhost:8002", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/backend/src/PostService/PostService.Api/appsettings.json b/backend/src/PostService/PostService.Api/appsettings.json index 78e7ac5..74ec804 100644 --- a/backend/src/PostService/PostService.Api/appsettings.json +++ b/backend/src/PostService/PostService.Api/appsettings.json @@ -8,7 +8,7 @@ "AllowedHosts": "*", "AllowedOrigins": [ "http://localhost:4000", "http://localhost:4200", "http://localhost:8080" ], "DatabaseConnectionString": "Host=localhost;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=PostsDb", - "WebHostUrl": "http://0.0.0.0:8081", + "WebHostUrl": "http://0.0.0.0:8002", "RabbitMq": { "Host": "localhost", "Username": "guest", diff --git a/docker-compose.yml b/docker-compose.yml index 587e0f6..9d84032 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,7 @@ services: context: ./backend/src dockerfile: AuthService/Dockerfile ports: - - "8082:8082" + - "8001:8001" environment: DatabaseConnectionString: Host=postgres;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=AuthDb Keycloak__Url: http://keycloak:8080 @@ -30,7 +30,7 @@ services: postgres: condition: service_healthy healthcheck: - test: [ "CMD-SHELL", "curl -f http://auth-service:8082/health || exit 1" ] + test: [ "CMD-SHELL", "curl -f http://auth-service:8001/health || exit 1" ] interval: 10s timeout: 5s retries: 3 @@ -43,14 +43,14 @@ services: context: ./backend/src dockerfile: PostService/Dockerfile ports: - - "8081:8081" + - "8002:8002" environment: DatabaseConnectionString: Host=postgres;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=PostsDb depends_on: postgres: condition: service_healthy healthcheck: - test: [ "CMD-SHELL", "curl -f http://post-service:8081/health || exit 1" ] + test: [ "CMD-SHELL", "curl -f http://post-service:8002/health || exit 1" ] interval: 10s timeout: 5s retries: 3 diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 0d85646..3c66cd2 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -1,23 +1,26 @@ -import { ApplicationConfig, provideZoneChangeDetection, inject } from '@angular/core'; +import {ApplicationConfig, inject, provideZoneChangeDetection} from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; -import { provideHttpClient} from '@angular/common/http'; +import {provideHttpClient, withFetch} from '@angular/common/http'; import { provideApollo } from 'apollo-angular'; -import { HttpLink } from 'apollo-angular/http'; -import {ApolloLink, InMemoryCache} from '@apollo/client/core'; -import {environment} from "../environments/environment"; +import {ApolloClient, ApolloLink, InMemoryCache} from '@apollo/client/core'; import {setContext} from "@apollo/client/link/context"; -import {DOCUMENT} from "@angular/common"; +import {environment} from "../environments/environment"; +import {HttpLink} from "apollo-angular/http"; +import {loadDevMessages, loadErrorMessages} from "@apollo/client/dev"; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), - provideHttpClient(), + provideHttpClient(withFetch()), provideApollo(() => { + if (!environment.production) { + loadDevMessages(); + loadErrorMessages(); + } const httpLink = inject(HttpLink); - const document = inject(DOCUMENT); const authLink = setContext(() => { const token = document.cookie @@ -25,19 +28,21 @@ export const appConfig: ApplicationConfig = { .find(row => row.startsWith('token=')) ?.split('=')[1]; return { + headers: { Authorization: token ? `Bearer ${token}` : '', - }, + } }; }); const link = ApolloLink.from([ authLink, httpLink.create({ uri: environment.auth_service }), + httpLink.create({ uri: environment.post_service }) ]); return { - link, + link: link, cache: new InMemoryCache(), }; }) diff --git a/frontend/src/app/features/auth/services/auth.service.ts b/frontend/src/app/features/auth/services/auth.service.ts index 050a1a6..9d176a0 100644 --- a/frontend/src/app/features/auth/services/auth.service.ts +++ b/frontend/src/app/features/auth/services/auth.service.ts @@ -6,13 +6,20 @@ import {TokenService} from "../../../core/services/token.service"; import {MutationResponse} from "../responses/mutation.response"; import {QueryResponses} from "../responses/query.responses"; import {IS_AUTHENTICATED} from "../requests/queries"; +import {environment} from "../../../../environments/environment"; +import {InMemoryCache} from "@apollo/client/core"; @Injectable({ providedIn: 'root' }) export class AuthService { - constructor(private apollo: Apollo, private tokenService: TokenService) { } + constructor(private apollo: Apollo, private tokenService: TokenService) { + // apollo.create({ + // uri: environment.auth_service, + // cache: new InMemoryCache() + // }); + } public login(username: string, password: string): Observable { return this.apollo.mutate({ @@ -60,7 +67,7 @@ export class AuthService { public isAuthenticated(): Observable { - return this.apollo.query({ + return this.apollo.query({ query: IS_AUTHENTICATED }).pipe( map(response => response.data.isAuthenticated)); diff --git a/frontend/src/app/features/auth/services/user.service.ts b/frontend/src/app/features/auth/services/user.service.ts index 7a4b7b8..efdd4dd 100644 --- a/frontend/src/app/features/auth/services/user.service.ts +++ b/frontend/src/app/features/auth/services/user.service.ts @@ -4,13 +4,16 @@ import {map, Observable} from "rxjs"; import {GET_USER_BY_USERNAME} from "../requests/queries"; import {User} from "../models/user.model"; import {QueryResponses} from "../responses/query.responses"; +import {ApolloLink} from "@apollo/client/core"; @Injectable({ providedIn: 'root' }) export class UserService { - constructor(private apollo: Apollo) { } + constructor(private apollo: Apollo) { + // apollo.client.setLink(ApolloLink.from()) + } getUserByUsername(username: string): Observable { return this.apollo.query({ diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index da2fa86..c1fd00c 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -1,4 +1,5 @@ export const environment = { - auth_service: "http://localhost:8082/graphql", + auth_service: "http://localhost:8000/auth/graphql", + post_service: "http://localhost:8000/post/", production: false }; From 3cb0e7e5a37ddddecdb28ad6df0437f895f4302a Mon Sep 17 00:00:00 2001 From: Ivan Sentemon Date: Sun, 5 Jan 2025 20:28:48 +0100 Subject: [PATCH 6/7] Add query base class and mutation base class --- .../PostService.Api/GraphQL/Mutation.cs | 126 ++++++++++++++++++ .../GraphQL/Mutations/CommentMutation.cs | 46 ------- .../GraphQL/Mutations/LikeMutation.cs | 46 ------- .../GraphQL/Mutations/PostMutation.cs | 62 --------- .../GraphQL/Queries/CommentQuery.cs | 21 --- .../GraphQL/Queries/LikeQuery.cs | 21 --- .../GraphQL/Queries/PostQuery.cs | 37 ----- .../PostService.Api/GraphQL/Query.cs | 69 ++++++++++ .../PostService/PostService.Api/Program.cs | 12 +- frontend/src/app/app.config.ts | 3 +- frontend/src/environments/environment.ts | 2 +- 11 files changed, 200 insertions(+), 245 deletions(-) create mode 100644 backend/src/PostService/PostService.Api/GraphQL/Mutation.cs delete mode 100644 backend/src/PostService/PostService.Api/GraphQL/Mutations/CommentMutation.cs delete mode 100644 backend/src/PostService/PostService.Api/GraphQL/Mutations/LikeMutation.cs delete mode 100644 backend/src/PostService/PostService.Api/GraphQL/Mutations/PostMutation.cs delete mode 100644 backend/src/PostService/PostService.Api/GraphQL/Queries/CommentQuery.cs delete mode 100644 backend/src/PostService/PostService.Api/GraphQL/Queries/LikeQuery.cs delete mode 100644 backend/src/PostService/PostService.Api/GraphQL/Queries/PostQuery.cs create mode 100644 backend/src/PostService/PostService.Api/GraphQL/Query.cs diff --git a/backend/src/PostService/PostService.Api/GraphQL/Mutation.cs b/backend/src/PostService/PostService.Api/GraphQL/Mutation.cs new file mode 100644 index 0000000..70c80f8 --- /dev/null +++ b/backend/src/PostService/PostService.Api/GraphQL/Mutation.cs @@ -0,0 +1,126 @@ +using System.Security.Claims; +using PostService.Application.Commands.AddComment; +using PostService.Application.Commands.AddLike; +using PostService.Application.Commands.AddPost; +using PostService.Application.Commands.DeleteComment; +using PostService.Application.Commands.DeleteLike; +using PostService.Application.Commands.DeletePost; +using PostService.Application.Commands.UpdatePost; +using PostService.Application.DTOs; + +namespace PostService.Api.GraphQL; + +public class Mutation +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public Mutation(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public async Task CreatePost(CreatePostDto input, [Service] AddPostCommandHandler addPostCommandHandler) + { + var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var command = new AddPostCommand(input, userId); + + var result = await addPostCommandHandler.HandleAsync(command); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task UpdatePost(UpdatePostDto input, [Service] UpdatePostCommandHandler updatePostCommandHandler) + { + var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var command = new UpdatePostCommand(input, userId); + + var result = await updatePostCommandHandler.HandleAsync(command); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task DeletePost(Guid id, [Service] DeletePostCommandHandler deletePostCommandHandler) + { + var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var command = new DeletePostCommand(id, userId); + + var result = await deletePostCommandHandler.HandleAsync(command); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task CreateComment(CreateCommentDto input, [Service] AddCommentCommandHandler addCommentCommandHandler) + { + var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var command = new AddCommentCommand(input, userId); + + var result = await addCommentCommandHandler.HandleAsync(command); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task DeleteComment(Guid id, Guid postId, [Service] DeleteCommentCommandHandler deleteCommentCommandHandler) + { + var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var command = new DeleteCommentCommand(id, postId, userId); + + var result = await deleteCommentCommandHandler.HandleAsync(command); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task AddLike(Guid postId, [Service] AddLikeCommandHandler addLikeCommandHandler) + { + var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var command = new AddLikeCommand(postId, userId); + + var result = await addLikeCommandHandler.HandleAsync(command); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task DeleteLike(Guid id, Guid postId, [Service] DeleteLikeCommandHandler deleteLikeCommandHandler) + { + var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + var command = new DeleteLikeCommand(id, postId, userId); + + var result = await deleteLikeCommandHandler.HandleAsync(command); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } +} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/GraphQL/Mutations/CommentMutation.cs b/backend/src/PostService/PostService.Api/GraphQL/Mutations/CommentMutation.cs deleted file mode 100644 index 32f3e71..0000000 --- a/backend/src/PostService/PostService.Api/GraphQL/Mutations/CommentMutation.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Security.Claims; -using PostService.Application.Commands.AddComment; -using PostService.Application.Commands.DeleteComment; -using PostService.Application.DTOs; - -namespace PostService.Api.GraphQL.Mutations; - -public class CommentMutation -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - public CommentMutation(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public async Task CreateComment(CreateCommentDto input, [Service] AddCommentCommandHandler addCommentCommandHandler) - { - var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var command = new AddCommentCommand(input, userId); - - var result = await addCommentCommandHandler.HandleAsync(command); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } - - public async Task DeleteComment(Guid id, Guid postId, [Service] DeleteCommentCommandHandler deleteCommentCommandHandler) - { - var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var command = new DeleteCommentCommand(id, postId, userId); - - var result = await deleteCommentCommandHandler.HandleAsync(command); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } -} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/GraphQL/Mutations/LikeMutation.cs b/backend/src/PostService/PostService.Api/GraphQL/Mutations/LikeMutation.cs deleted file mode 100644 index f1ca6b1..0000000 --- a/backend/src/PostService/PostService.Api/GraphQL/Mutations/LikeMutation.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Security.Claims; -using PostService.Application.Commands.AddLike; -using PostService.Application.Commands.DeleteLike; -using PostService.Application.DTOs; - -namespace PostService.Api.GraphQL.Mutations; - -public class LikeMutation -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - public LikeMutation(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public async Task AddLike(Guid postId, [Service] AddLikeCommandHandler addLikeCommandHandler) - { - var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var command = new AddLikeCommand(postId, userId); - - var result = await addLikeCommandHandler.HandleAsync(command); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } - - public async Task DeleteLike(Guid id, Guid postId, [Service] DeleteLikeCommandHandler deleteLikeCommandHandler) - { - var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var command = new DeleteLikeCommand(id, postId, userId); - - var result = await deleteLikeCommandHandler.HandleAsync(command); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } -} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/GraphQL/Mutations/PostMutation.cs b/backend/src/PostService/PostService.Api/GraphQL/Mutations/PostMutation.cs deleted file mode 100644 index 9617dec..0000000 --- a/backend/src/PostService/PostService.Api/GraphQL/Mutations/PostMutation.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Security.Claims; -using PostService.Application.Commands.AddPost; -using PostService.Application.Commands.DeletePost; -using PostService.Application.Commands.UpdatePost; -using PostService.Application.DTOs; - -namespace PostService.Api.GraphQL.Mutations; - -public class PostMutation -{ - private readonly IHttpContextAccessor _httpContextAccessor; - - public PostMutation(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public async Task CreatePost(CreatePostDto input, [Service] AddPostCommandHandler addPostCommandHandler) - { - var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var command = new AddPostCommand(input, userId); - - var result = await addPostCommandHandler.HandleAsync(command); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } - - public async Task UpdatePost(UpdatePostDto input, [Service] UpdatePostCommandHandler updatePostCommandHandler) - { - var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var command = new UpdatePostCommand(input, userId); - - var result = await updatePostCommandHandler.HandleAsync(command); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } - - public async Task DeletePost(Guid id, [Service] DeletePostCommandHandler deletePostCommandHandler) - { - var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var command = new DeletePostCommand(id, userId); - - var result = await deletePostCommandHandler.HandleAsync(command); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } -} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/GraphQL/Queries/CommentQuery.cs b/backend/src/PostService/PostService.Api/GraphQL/Queries/CommentQuery.cs deleted file mode 100644 index e7fd0b0..0000000 --- a/backend/src/PostService/PostService.Api/GraphQL/Queries/CommentQuery.cs +++ /dev/null @@ -1,21 +0,0 @@ -using PostService.Application.Queries.GetAllComments; -using PostService.Domain.Entities; - -namespace PostService.Api.GraphQL.Queries; - -public class CommentQuery -{ - public async Task> GetAllComments(Guid postId, int first, [Service] GetAllCommentsQueryHandler getAllCommentsQueryHandler) - { - var query = new GetAllCommentsQuery(postId, first); - - var result = await getAllCommentsQueryHandler.HandleAsync(query); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } -} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/GraphQL/Queries/LikeQuery.cs b/backend/src/PostService/PostService.Api/GraphQL/Queries/LikeQuery.cs deleted file mode 100644 index 3aeb908..0000000 --- a/backend/src/PostService/PostService.Api/GraphQL/Queries/LikeQuery.cs +++ /dev/null @@ -1,21 +0,0 @@ -using PostService.Application.Queries.GetAllLikes; -using PostService.Domain.Entities; - -namespace PostService.Api.GraphQL.Queries; - -public class LikeQuery -{ - public async Task> GetAllLikes(Guid postId, int first, [Service] GetAllLikesQueryHandler getAllLikesQueryHandler) - { - var query = new GetAllLikesQuery(postId, first); - - var result = await getAllLikesQueryHandler.HandleAsync(query); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } -} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/GraphQL/Queries/PostQuery.cs b/backend/src/PostService/PostService.Api/GraphQL/Queries/PostQuery.cs deleted file mode 100644 index 7c3f1d8..0000000 --- a/backend/src/PostService/PostService.Api/GraphQL/Queries/PostQuery.cs +++ /dev/null @@ -1,37 +0,0 @@ -using PostService.Application.DTOs; -using PostService.Application.Queries.GetAllPosts; -using PostService.Application.Queries.GetPost; -using PostService.Domain.Entities; - -namespace PostService.Api.GraphQL.Queries; - -public class PostQuery -{ - public async Task GetPost(Guid id, [Service] GetPostQueryHandler getPostQueryHandler) - { - var query = new GetPostQuery(id); - - var result = await getPostQueryHandler.HandleAsync(query); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } - - public async Task> GetAllPost(int first, Guid lastPostId, [Service] GetAllPostsQueryHandler getAllPostsQueryHandler) - { - var query = new GetAllPostsQuery(first, lastPostId); - - var result = await getAllPostsQueryHandler.HandleAsync(query); - - if (!result.IsSuccess) - { - throw new GraphQLException(new Error(result.Error.Message)); - } - - return result.Response; - } -} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/GraphQL/Query.cs b/backend/src/PostService/PostService.Api/GraphQL/Query.cs new file mode 100644 index 0000000..f9d2e7d --- /dev/null +++ b/backend/src/PostService/PostService.Api/GraphQL/Query.cs @@ -0,0 +1,69 @@ +using PostService.Application.DTOs; +using PostService.Application.Queries.GetAllComments; +using PostService.Application.Queries.GetAllLikes; +using PostService.Application.Queries.GetAllPosts; +using PostService.Application.Queries.GetPost; +using PostService.Domain.Entities; + +namespace PostService.Api.GraphQL; + +public class Query +{ + public async Task GetPost(Guid id, [Service] GetPostQueryHandler getPostQueryHandler) + { + var query = new GetPostQuery(id); + + var result = await getPostQueryHandler.HandleAsync(query); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task> GetAllPost(int first, Guid lastPostId, [Service] GetAllPostsQueryHandler getAllPostsQueryHandler) + { + var query = new GetAllPostsQuery(first, lastPostId); + + var result = await getAllPostsQueryHandler.HandleAsync(query); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task> GetAllComments(Guid postId, int first, [Service] GetAllCommentsQueryHandler getAllCommentsQueryHandler) + { + var query = new GetAllCommentsQuery(postId, first); + + var result = await getAllCommentsQueryHandler.HandleAsync(query); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public async Task> GetAllLikes(Guid postId, int first, [Service] GetAllLikesQueryHandler getAllLikesQueryHandler) + { + var query = new GetAllLikesQuery(postId, first); + + var result = await getAllLikesQueryHandler.HandleAsync(query); + + if (!result.IsSuccess) + { + throw new GraphQLException(new Error(result.Error.Message)); + } + + return result.Response; + } + + public string Test() => "Hello World"; +} \ No newline at end of file diff --git a/backend/src/PostService/PostService.Api/Program.cs b/backend/src/PostService/PostService.Api/Program.cs index 0801cc7..825b4da 100644 --- a/backend/src/PostService/PostService.Api/Program.cs +++ b/backend/src/PostService/PostService.Api/Program.cs @@ -1,9 +1,7 @@ using Microsoft.EntityFrameworkCore; -using PostService.Api.GraphQL.Mutations; -using PostService.Api.GraphQL.Queries; +using PostService.Api.GraphQL; using PostService.Application; using PostService.Domain.Constants; -using PostService.Domain.Entities; using PostService.Infrastructure; using PostService.Persistence; @@ -38,12 +36,8 @@ builder.Services .AddGraphQLServer() - .AddQueryType() - .AddQueryType() - .AddQueryType() - .AddMutationType() - .AddMutationType() - .AddMutationType() + .AddQueryType() + .AddMutationType() .AddType(new UuidType()) .AddType(); diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 3c66cd2..0f0784a 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -4,7 +4,7 @@ import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import {provideHttpClient, withFetch} from '@angular/common/http'; import { provideApollo } from 'apollo-angular'; -import {ApolloClient, ApolloLink, InMemoryCache} from '@apollo/client/core'; +import { ApolloLink, InMemoryCache} from '@apollo/client/core'; import {setContext} from "@apollo/client/link/context"; import {environment} from "../environments/environment"; import {HttpLink} from "apollo-angular/http"; @@ -38,7 +38,6 @@ export const appConfig: ApplicationConfig = { const link = ApolloLink.from([ authLink, httpLink.create({ uri: environment.auth_service }), - httpLink.create({ uri: environment.post_service }) ]); return { diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index c1fd00c..9796f82 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -1,5 +1,5 @@ export const environment = { auth_service: "http://localhost:8000/auth/graphql", - post_service: "http://localhost:8000/post/", + post_service: "http://localhost:8000/post/graphql", production: false }; From 38662e8e1a4c3ed9b6da185c2c0dc1fdb32da2e4 Mon Sep 17 00:00:00 2001 From: Ivan Sentemon Date: Sun, 5 Jan 2025 22:37:54 +0100 Subject: [PATCH 7/7] Add docker support for gateway --- FitnessApp.sln | 3 + .../AuthService.Api/GraphQL/Mutation.cs | 11 ++ .../AuthService.Api/appsettings.json | 4 +- backend/src/Gateway/Dockerfile | 28 +++ backend/src/Gateway/Gateway/Program.cs | 2 + docker-compose.yml | 36 +++- keycloak/fitness-app-realm.json | 185 +++++++++++++++--- 7 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 backend/src/Gateway/Dockerfile diff --git a/FitnessApp.sln b/FitnessApp.sln index 004ee3f..457ef88 100644 --- a/FitnessApp.sln +++ b/FitnessApp.sln @@ -77,6 +77,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.DTO", "backend\src\Shared\Shared.DTO\Shared.DTO.csproj", "{334DF17C-BD69-47B9-B4D0-ACB5AD6223CF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "backend\src\Gateway", "{DC9B9F00-24BF-4AD3-B45A-38BFCFC309D7}" + ProjectSection(SolutionItems) = preProject + backend\src\Gateway\Dockerfile = backend\src\Gateway\Dockerfile + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gateway", "backend\src\Gateway\Gateway\Gateway.csproj", "{830B4F05-7C89-4C0F-B27D-B1008B605E79}" EndProject diff --git a/backend/src/AuthService/AuthService.Api/GraphQL/Mutation.cs b/backend/src/AuthService/AuthService.Api/GraphQL/Mutation.cs index fadd74e..acd50d4 100644 --- a/backend/src/AuthService/AuthService.Api/GraphQL/Mutation.cs +++ b/backend/src/AuthService/AuthService.Api/GraphQL/Mutation.cs @@ -20,6 +20,17 @@ public Mutation(IHttpContextAccessor httpContextAccessor) _httpContextAccessor = httpContextAccessor; } + + /* ToDo: When all the services are running in the docker that's it + System.Net.Http.HttpClient.KeycloakClient.LogicalHandler[100] + 2025-01-05 22:30:39 Start processing HTTP request POST http://keycloak:8080/realms/fitness-app-realm/protocol/openid-connect/token + 2025-01-05 22:30:39 info: System.Net.Http.HttpClient.KeycloakClient.ClientHandler[100] + 2025-01-05 22:30:39 Sending HTTP request POST http://keycloak:8080/realms/fitness-app-realm/protocol/openid-connect/token + 2025-01-05 22:30:39 info: System.Net.Http.HttpClient.KeycloakClient.ClientHandler[101] + 2025-01-05 22:30:39 Received HTTP response headers after 91.8503ms - 404 + 2025-01-05 22:30:39 info: System.Net.Http.HttpClient.KeycloakClient.LogicalHandler[101] + 2025-01-05 22:30:39 End processing HTTP request after 100.8284ms - 404 + */ public async Task Register(RegisterDto input, [Service] RegisterCommandHandler registerCommandHandler) { var command = new RegisterCommand(input); diff --git a/backend/src/AuthService/AuthService.Api/appsettings.json b/backend/src/AuthService/AuthService.Api/appsettings.json index 612994a..37c1c7f 100644 --- a/backend/src/AuthService/AuthService.Api/appsettings.json +++ b/backend/src/AuthService/AuthService.Api/appsettings.json @@ -13,10 +13,10 @@ "Url": "http://localhost:8080", "Realm": "fitness-app-realm", "ClientId": "fitness-app-client", - "ClientSecret": "4mTuelQIWNfLw3HGfE3xAD3Bmyb5OHWN", + "ClientSecret": "7lGtugT2Dmwgi6gYJQnGZf5aO0Y9tzlh", "AdminUsername": "admin", "AdminPassword": "admin", - "PublicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Q/E+u/w56fl1C67jv2glu/wjy50re4UbVqJHX+T84hN8lGjiHRxqmEgdsyArhiXK0XBbbolPJ+jLTXY7KAKXXtsCjNOV/WLCKFi9Gdn2vgrmD+g2BatpRvO33nytwlDZSkZSnFpTY3io6ZcMCB+YQK5i+2QOz1gahJt5Dac+bwZ96d3x+dEP+0lZ3+3VTe0bKWRMDked2f8E4K9mvywyUEf3Ihe/2YVhXZyUnshMrRmn6ZZ4DvpQcrHMtGMCBiB5N6pllxBu5XAcxYPRxZtx+q4GSU1bCA9es4RNgLTBGP8GQXVVqlq80j8oxR1SSigqLNbA7qcGi4+rmWg/ohdkQIDAQAB" + "PublicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuQXYfCuREUGjALqZ/iPLvqPAEBcCP4Knbwixdbe+jwhrMjX7n/azi+htI3egSAjlSlVvc5olSU2ykydCD2BqBgxMpNGoTa8dp96vDdTeMBD/semyOiSmJq4tqeKw2n7cjpI7jvoDP/HvH5b90SIsmmmUEHnjie3fakb0VccQxJdQXolNlCZfBytD0L8bBmuvPlQdgSoOxCuRakVz2NBErjx0SRxn0EltHdu1dEA0Ya+I7jeX3ZgbKe+R/oIuggYnnlh//wNXsfHGKKYsxxp/FQjItS2oq29aGPjTA6t3nt0Rysg66IbNxdMFg1nfREFKV7Qpb2IsC01k0s/r/+2BlwIDAQAB" }, "RabbitMq": { "Host": "localhost", diff --git a/backend/src/Gateway/Dockerfile b/backend/src/Gateway/Dockerfile new file mode 100644 index 0000000..724a972 --- /dev/null +++ b/backend/src/Gateway/Dockerfile @@ -0,0 +1,28 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +WORKDIR /app +EXPOSE 8000 + +RUN apt-get update && apt-get install -y curl && apt-get clean + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build + +WORKDIR /src + +COPY Gateway/Gateway/Gateway.csproj Gateway/Gateway/ + +RUN dotnet restore Gateway/Gateway/Gateway.csproj + +COPY . . + +WORKDIR /src/Gateway/Gateway + +RUN dotnet build Gateway.csproj -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish Gateway.csproj -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +ENTRYPOINT ["dotnet", "Gateway.dll"] \ No newline at end of file diff --git a/backend/src/Gateway/Gateway/Program.cs b/backend/src/Gateway/Gateway/Program.cs index 688d6f7..d9a1fd7 100644 --- a/backend/src/Gateway/Gateway/Program.cs +++ b/backend/src/Gateway/Gateway/Program.cs @@ -37,4 +37,6 @@ app.MapReverseProxy(); +app.MapGet("/health" ,() => Results.Ok("Healthy")); + app.Run(); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 9d84032..699b58d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,11 +11,34 @@ services: ports: - "4200:80" depends_on: - post-service: + gateway: condition: service_healthy networks: - microservices + gateway: + container_name: gateway + build: + context: ./backend/src + dockerfile: Gateway/Dockerfile + ports: + - "8000:8000" + environment: + ReverseProxy__Clusters__auth-service__Destinations__destination1__Address: http://auth-service:8001 + ReverseProxy__Clusters__post-service__Destinations__destination1__Address: http://post-service:8002 + depends_on: + postgres: + condition: service_healthy + rabittmq: + condition: service_healthy + healthcheck: + test: [ "CMD-SHELL", "curl -f http://gateway:8000/health || exit 1" ] + interval: 10s + timeout: 5s + retries: 3 + networks: + - microservices + auth-service: container_name: auth-service build: @@ -26,8 +49,9 @@ services: environment: DatabaseConnectionString: Host=postgres;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=AuthDb Keycloak__Url: http://keycloak:8080 + RabbitMq__Host: rabbitmq depends_on: - postgres: + gateway: condition: service_healthy healthcheck: test: [ "CMD-SHELL", "curl -f http://auth-service:8001/health || exit 1" ] @@ -46,8 +70,9 @@ services: - "8002:8002" environment: DatabaseConnectionString: Host=postgres;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=PostsDb + RabbitMq__Host: rabbitmq depends_on: - postgres: + gateway: condition: service_healthy healthcheck: test: [ "CMD-SHELL", "curl -f http://post-service:8002/health || exit 1" ] @@ -100,6 +125,11 @@ services: ports: - 5672:5672 - 15672:15672 + healthcheck: + test: rabbitmq-diagnostics -q ping + interval: 30s + timeout: 10s + retries: 5 networks: - microservices diff --git a/keycloak/fitness-app-realm.json b/keycloak/fitness-app-realm.json index 0766401..a964d1f 100644 --- a/keycloak/fitness-app-realm.json +++ b/keycloak/fitness-app-realm.json @@ -99,6 +99,7 @@ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, "webAuthnPolicyPasswordlessAcceptableAaguids": [], "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [], "scopeMappings": [ { "clientScope": "offline_access", @@ -123,33 +124,100 @@ "id": "5f30eb33-363f-4e7b-b2f4-732e0d7143b1", "clientId": "account", "name": "${client_account}", + "description": "", "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/fitness-app-realm/account/", + "adminUrl": "", + "baseUrl": "", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", + "secret": "**********", "redirectUris": [ - "/realms/fitness-app-realm/account/*" + "*" + ], + "webOrigins": [ + "*" ], - "webOrigins": [], "notBefore": 0, "bearerOnly": false, "consentRequired": false, "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { + "client.secret.creation.time": "1736111053", + "client.introspection.response.allow.jwt.claim.enabled": "false", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", "realm_client": "false", - "post.logout.redirect.uris": "+" + "oidc.ciba.grant.enabled": "false", + "client.use.lightweight.access.token.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "tls.client.certificate.bound.access.tokens": "false", + "require.pushed.authorization.requests": "false", + "acr.loa.map": "{}", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "83e4c5a8-cbbf-4fcf-b45e-ace4ffbaa847", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "79ac7af9-893d-4f1d-98a2-f6b809c719e6", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "6b15965b-ba23-4553-bb34-c44b03ae8c1e", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + } + ], "defaultClientScopes": [ "web-origins", "acr", @@ -164,7 +232,47 @@ "offline_access", "organization", "microprofile-jwt" - ] + ], + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Default Resource", + "type": "urn:account:resources:default", + "ownerManagedAccess": false, + "attributes": {}, + "uris": [ + "/*" + ] + } + ], + "policies": [ + { + "name": "Default Policy", + "description": "A policy that grants access only for users within this realm", + "type": "js", + "logic": "POSITIVE", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n" + } + }, + { + "name": "Default Permission", + "description": "A permission that applies to the default resource type", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "defaultResourceType": "urn:account:resources:default", + "applyPolicies": "[\"Default Policy\"]" + } + } + ], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + } }, { "id": "03ae488f-7ca5-469a-8aba-90cb0df1a629", @@ -325,10 +433,10 @@ "clientAuthenticatorType": "client-secret", "secret": "**********", "redirectUris": [ - "http://0.0.0.0:8081/*" + "*" ], "webOrigins": [ - "http://0.0.0.0:8081" + "*" ], "notBefore": 0, "bearerOnly": false, @@ -341,7 +449,7 @@ "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { - "client.secret.creation.time": "1733417203", + "client.secret.creation.time": "1736108457", "client.introspection.response.allow.jwt.claim.enabled": "false", "post.logout.redirect.uris": "+", "oauth2.device.authorization.grant.enabled": "false", @@ -418,6 +526,7 @@ "defaultClientScopes": [ "web-origins", "acr", + "aud", "profile", "roles", "basic", @@ -1018,6 +1127,35 @@ "display.on.consent.screen": "true" } }, + { + "id": "724eace5-6ad6-4246-b1f4-5e4e3e1370ab", + "name": "aud", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "8d0b616f-50a0-433b-9441-43da45bc410b", + "name": "Audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "account", + "id.token.claim": "true", + "lightweight.claim": "false", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "false" + } + } + ] + }, { "id": "561ffffc-ed7a-44b8-b8ee-6e047e5b03d8", "name": "roles", @@ -1179,7 +1317,8 @@ "roles", "acr", "basic", - "web-origins" + "web-origins", + "aud" ], "defaultOptionalClientScopes": [ "offline_access", @@ -1272,13 +1411,13 @@ "config": { "allowed-protocol-mapper-types": [ "oidc-usermodel-attribute-mapper", - "oidc-address-mapper", - "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", - "oidc-usermodel-property-mapper", - "saml-role-list-mapper" + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper" ] } }, @@ -1290,14 +1429,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "oidc-usermodel-property-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-address-mapper", - "oidc-full-name-mapper", - "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", "saml-user-property-mapper", - "saml-user-attribute-mapper" + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper" ] } },