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/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/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/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/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs
index 6f1e4e2..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()
{
@@ -134,7 +124,6 @@ async Task LoadTranslations()
app.Services.GetRequiredService().Initialize(dictionary);
ResourcesKey.Initialize(app.Services.GetRequiredService());
-
}
string GetConnectionString()
@@ -146,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 6342c6a..8dd196e 100644
--- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs
+++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs
@@ -4,19 +4,22 @@ 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;
+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)
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;