From 564cadb94580b69bcedd5fa083e34177b3555f09 Mon Sep 17 00:00:00 2001 From: egamorf76 Date: Mon, 28 Jul 2025 23:10:43 +0200 Subject: [PATCH 1/2] Add rider files and use options --- .../.idea/.idea.GameIdeas/.idea/.gitignore | 13 +++++ .../.idea.GameIdeas/.idea/indexLayout.xml | 8 +++ .../.idea/.idea.GameIdeas/.idea/vcs.xml | 7 +++ src/GameIdeas/.vscode/launch.json | 51 +++++++++++++++++++ src/GameIdeas/.vscode/tasks.json | 27 ++++++++++ .../Exceptions/UserInvalidException.cs | 2 +- .../Exceptions/UserUnauthorizedException.cs | 2 +- .../Options/GameIdeasOptions.cs | 12 +++++ .../Controllers/UserController.cs | 2 +- .../GameIdeas.WebAPI/GameIdeas.WebAPI.csproj | 4 ++ .../Server/GameIdeas.WebAPI/Program.cs | 1 - .../Services/Users/UserReadService.cs | 2 +- .../Services/Users/UserWriteService.cs | 2 +- 13 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 src/GameIdeas/.idea/.idea.GameIdeas/.idea/.gitignore create mode 100644 src/GameIdeas/.idea/.idea.GameIdeas/.idea/indexLayout.xml create mode 100644 src/GameIdeas/.idea/.idea.GameIdeas/.idea/vcs.xml create mode 100644 src/GameIdeas/.vscode/launch.json create mode 100644 src/GameIdeas/.vscode/tasks.json rename src/GameIdeas/{Server/GameIdeas.WebAPI => GameIdeas.Shared}/Exceptions/UserInvalidException.cs (63%) rename src/GameIdeas/{Server/GameIdeas.WebAPI => GameIdeas.Shared}/Exceptions/UserUnauthorizedException.cs (65%) create mode 100644 src/GameIdeas/GameIdeas.Shared/Options/GameIdeasOptions.cs diff --git a/src/GameIdeas/.idea/.idea.GameIdeas/.idea/.gitignore b/src/GameIdeas/.idea/.idea.GameIdeas/.idea/.gitignore new file mode 100644 index 0000000..225498d --- /dev/null +++ b/src/GameIdeas/.idea/.idea.GameIdeas/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/.idea.GameIdeas.iml +/modules.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/GameIdeas/.idea/.idea.GameIdeas/.idea/indexLayout.xml b/src/GameIdeas/.idea/.idea.GameIdeas/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/src/GameIdeas/.idea/.idea.GameIdeas/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/GameIdeas/.idea/.idea.GameIdeas/.idea/vcs.xml b/src/GameIdeas/.idea/.idea.GameIdeas/.idea/vcs.xml new file mode 100644 index 0000000..8fe5bdb --- /dev/null +++ b/src/GameIdeas/.idea/.idea.GameIdeas/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/GameIdeas/.vscode/launch.json b/src/GameIdeas/.vscode/launch.json new file mode 100644 index 0000000..8bd454a --- /dev/null +++ b/src/GameIdeas/.vscode/launch.json @@ -0,0 +1,51 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "dotNetConfig": {}, + "name": "Debug Blazor Application", + "type": "blazorwasm", + "request": "launch", + "browser": "chrome", + "cwd": "${workspaceFolder}/Client/GameIdeas.BlazorApp", + "url": "http://localhost:5172", + "presentation": { + "group": "group 2: Single", + "order": 2 + }, + }, + { + "name": "Debug API Server", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "Build API Server", + "program": "${workspaceFolder}/Server/GameIdeas.WebAPI/bin/Debug/net9.0/GameIdeas.WebAPI.dll", + "cwd": "${workspaceFolder}/Server/GameIdeas.WebAPI", + "stopAtEntry": false, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "presentation": { + "group": "group 2: Single", + "order": 2 + } + } + ], + "compounds": [ + { + "name": "Launch GameIdeas", + "configurations": [ + "Debug API Server", + "Debug Blazor Application" + ], + "stopAll": true, + "presentation": { + "group": "group 1: Group", + "order": 1 + }, + } + ] +} \ No newline at end of file diff --git a/src/GameIdeas/.vscode/tasks.json b/src/GameIdeas/.vscode/tasks.json new file mode 100644 index 0000000..ab8801a --- /dev/null +++ b/src/GameIdeas/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "dotnet build", + "label": "Build API Server", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher":{ + "pattern": { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + }, + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Compiled|Failed|compiled|failed|ready" + } + } + } + ] +} \ No newline at end of file diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Exceptions/UserInvalidException.cs b/src/GameIdeas/GameIdeas.Shared/Exceptions/UserInvalidException.cs similarity index 63% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Exceptions/UserInvalidException.cs rename to src/GameIdeas/GameIdeas.Shared/Exceptions/UserInvalidException.cs index a5ca0c0..1e6d328 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Exceptions/UserInvalidException.cs +++ b/src/GameIdeas/GameIdeas.Shared/Exceptions/UserInvalidException.cs @@ -1,3 +1,3 @@ -namespace GameIdeas.WebAPI.Exceptions; +namespace GameIdeas.Shared.Exceptions; public class UserInvalidException(string message) : Exception(message); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Exceptions/UserUnauthorizedException.cs b/src/GameIdeas/GameIdeas.Shared/Exceptions/UserUnauthorizedException.cs similarity index 65% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Exceptions/UserUnauthorizedException.cs rename to src/GameIdeas/GameIdeas.Shared/Exceptions/UserUnauthorizedException.cs index 95006e1..cacdacd 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Exceptions/UserUnauthorizedException.cs +++ b/src/GameIdeas/GameIdeas.Shared/Exceptions/UserUnauthorizedException.cs @@ -1,3 +1,3 @@ -namespace GameIdeas.WebAPI.Exceptions; +namespace GameIdeas.Shared.Exceptions; public class UserUnauthorizedException(string message) : Exception(message); diff --git a/src/GameIdeas/GameIdeas.Shared/Options/GameIdeasOptions.cs b/src/GameIdeas/GameIdeas.Shared/Options/GameIdeasOptions.cs new file mode 100644 index 0000000..dd9df72 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Options/GameIdeasOptions.cs @@ -0,0 +1,12 @@ +namespace GameIdeas.Shared.Options; + +public class GameIdeasOptions +{ + public string DbHost { get; set; } = string.Empty; + public string DbUsername { get; set; } = string.Empty; + public string DbPassword { get; set; } = string.Empty; + public string DbDatabase { get; set; } = string.Empty; + public string JwtKey { get; set; } = string.Empty; + public string JwtIssuer { get; set; } = string.Empty; + public string JwtAudience { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs index 4266b7d..dd804b1 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs @@ -1,6 +1,6 @@ using GameIdeas.Shared.Constants; using GameIdeas.Shared.Dto; -using GameIdeas.WebAPI.Exceptions; +using GameIdeas.Shared.Exceptions; using GameIdeas.WebAPI.Services.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj b/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj index 32e89b5..f776445 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj @@ -28,4 +28,8 @@ + + + + diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index 6f1e4e2..449dd0b 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -134,7 +134,6 @@ async Task LoadTranslations() app.Services.GetRequiredService().Initialize(dictionary); ResourcesKey.Initialize(app.Services.GetRequiredService()); - } string GetConnectionString() diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs index 6342c6a..6b53fff 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs @@ -4,13 +4,13 @@ using GameIdeas.Shared.Constants; using GameIdeas.Shared.Dto; using GameIdeas.Shared.Model; using GameIdeas.WebAPI.Context; -using GameIdeas.WebAPI.Exceptions; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; +using GameIdeas.Shared.Exceptions; namespace GameIdeas.WebAPI.Services.Users; diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs index 5a7f041..2c11c68 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs @@ -1,7 +1,7 @@ using GameIdeas.Resources; using GameIdeas.Shared.Dto; +using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; -using GameIdeas.WebAPI.Exceptions; using Microsoft.AspNetCore.Identity; namespace GameIdeas.WebAPI.Services.Users; -- 2.39.5 From 36360efa6fb00f17af749a6e46028e1d4bd304b9 Mon Sep 17 00:00:00 2001 From: egamorf76 Date: Mon, 28 Jul 2025 23:41:52 +0200 Subject: [PATCH 2/2] Configure game ideas options --- .../EnvironmentVariableMissingException.cs | 3 ++ .../Extensions/ServiceCollectionExtension.cs | 47 ++++++++++++++++ .../GameIdeas.WebAPI/GameIdeas.WebAPI.csproj | 4 -- .../Server/GameIdeas.WebAPI/Program.cs | 54 +++++-------------- .../Services/Users/UserReadService.cs | 12 ++--- 5 files changed, 69 insertions(+), 51 deletions(-) create mode 100644 src/GameIdeas/GameIdeas.Shared/Exceptions/EnvironmentVariableMissingException.cs create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Extensions/ServiceCollectionExtension.cs diff --git a/src/GameIdeas/GameIdeas.Shared/Exceptions/EnvironmentVariableMissingException.cs b/src/GameIdeas/GameIdeas.Shared/Exceptions/EnvironmentVariableMissingException.cs new file mode 100644 index 0000000..50b7746 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Exceptions/EnvironmentVariableMissingException.cs @@ -0,0 +1,3 @@ +namespace GameIdeas.Shared.Exceptions; + +public class EnvironmentVariableMissingException(string message) : Exception(message); \ No newline at end of file diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Extensions/ServiceCollectionExtension.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Extensions/ServiceCollectionExtension.cs new file mode 100644 index 0000000..8714909 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Extensions/ServiceCollectionExtension.cs @@ -0,0 +1,47 @@ +using GameIdeas.Shared.Exceptions; +using GameIdeas.Shared.Options; + +namespace GameIdeas.WebAPI.Extensions; + +public static class ServiceCollectionExtension +{ + public static IServiceCollection AddGameIdeasOptions(this IServiceCollection services) + { +#if DEBUG + var dictionary = LoadEnvironmentVariable("../../../../.env"); +#else + var dictionary = LoadEnvironmentVariable("../.env"); +#endif + + services.Configure(options => + { + options.DbHost = GetEnvVar("DB_HOST", dictionary); + options.DbUsername = GetEnvVar("DB_USERNAME", dictionary); + options.DbPassword = GetEnvVar("DB_PASSWORD", dictionary); + options.DbDatabase = GetEnvVar("DB_DATABASE", dictionary); + options.JwtKey = GetEnvVar("JWT_KEY", dictionary); + options.JwtIssuer = GetEnvVar("JWT_ISSUER", dictionary); + options.JwtAudience = GetEnvVar("JWT_AUDIENCE", dictionary); + }); + + return services; + } + + private static string GetEnvVar(string name, Dictionary dictionary) + { + return Environment.GetEnvironmentVariable(name) + ?? dictionary.GetValueOrDefault(name) + ?? throw new EnvironmentVariableMissingException($"Missing environment variable with key: {name}"); + } + + private static Dictionary LoadEnvironmentVariable(string filePath) + { + if (!File.Exists(filePath)) + return []; + + return File.ReadAllLines(filePath) + .Select(line => line.Split('=', StringSplitOptions.RemoveEmptyEntries)) + .Where(parts => parts.Length == 2) + .ToDictionary(parts => parts[0], parts => parts[1]); + } +} \ No newline at end of file diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj b/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj index f776445..32e89b5 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj @@ -28,8 +28,4 @@ - - - - diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index 449dd0b..a691143 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -10,37 +10,18 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.Text; +using GameIdeas.WebAPI.Extensions; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; -#if DEBUG -LoadEnvironmentVariable("../../../../.env"); -#else -LoadEnvironmentVariable("../.env"); -#endif - -Action dbContextOptions = options => -{ - options.UseNpgsql( - GetConnectionString(), - npgOption => - { - npgOption.CommandTimeout(60); - npgOption.MigrationsAssembly("GameIdeas.WebAPI"); - }); -}; - -// Add services to the container. -services.AddDbContext(dbContextOptions); +services.AddGameIdeasOptions(); +services.AddDbContext(ContextOptions); services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); -var jwtKey = Environment.GetEnvironmentVariable("JWT_KEY") - ?? throw new ArgumentNullException(message: "Invalid key for JWT token", null); - services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; @@ -86,7 +67,6 @@ services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); services.AddControllers(); -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi services.AddOpenApi(); services.AddCors(option => option.AddDefaultPolicy(policy => @@ -118,6 +98,16 @@ app.UseAuthorization(); app.MapControllers(); app.Run(); +return; + +void ContextOptions(DbContextOptionsBuilder options) +{ + options.UseNpgsql(GetConnectionString(), npgOption => + { + npgOption.CommandTimeout(60); + npgOption.MigrationsAssembly("GameIdeas.WebAPI"); + }); +} async Task LoadTranslations() { @@ -145,21 +135,3 @@ string GetConnectionString() return $"Host={host};Username={login};Password={pass};Database={database}"; } - -static void LoadEnvironmentVariable(string filePath) -{ - if (!File.Exists(filePath)) - return; - - foreach (var line in File.ReadAllLines(filePath)) - { - var parts = line.Split( - '=', - StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length != 2) - continue; - - Environment.SetEnvironmentVariable(parts[0], parts[1]); - } -} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs index 6b53fff..8dd196e 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs @@ -11,12 +11,15 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using GameIdeas.Shared.Exceptions; +using GameIdeas.Shared.Options; +using Microsoft.Extensions.Options; namespace GameIdeas.WebAPI.Services.Users; public class UserReadService( UserManager userManager, GameIdeasContext context, + IOptions options, IMapper mapper) : IUserReadService { public async Task> GetRoles() @@ -125,14 +128,11 @@ public class UserReadService( authClaims.AddRange((await userManager.GetRolesAsync(user)) .Select(r => new Claim(ClaimTypes.Role, r))); - var jwtKey = Environment.GetEnvironmentVariable("JWT_KEY") - ?? throw new ArgumentNullException(message: ResourcesKey.InvalidToken, null); - - var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)); + var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(options.Value.JwtKey)); var token = new JwtSecurityToken( - issuer: Environment.GetEnvironmentVariable("JWT_ISSUER"), - audience: Environment.GetEnvironmentVariable("JWT_AUDIENCE"), + issuer: options.Value.JwtIssuer, + audience: options.Value.JwtAudience, expires: DateTime.Now.AddHours(GlobalConstants.JWT_DURATION_HOUR), claims: authClaims, signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256) -- 2.39.5