Add rider files and use options #58

Open
Egamorf wants to merge 2 commits from feature/fix-editor-and-options into main
14 changed files with 192 additions and 53 deletions

13
src/GameIdeas/.idea/.idea.GameIdeas/.idea/.gitignore generated vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

51
src/GameIdeas/.vscode/launch.json vendored Normal file
View File

@@ -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
},
}
]
}

27
src/GameIdeas/.vscode/tasks.json vendored Normal file
View File

@@ -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"
}
}
}
]
}

View File

@@ -0,0 +1,3 @@
namespace GameIdeas.Shared.Exceptions;
public class EnvironmentVariableMissingException(string message) : Exception(message);

View File

@@ -1,3 +1,3 @@
namespace GameIdeas.WebAPI.Exceptions;
namespace GameIdeas.Shared.Exceptions;
public class UserInvalidException(string message) : Exception(message);

View File

@@ -1,3 +1,3 @@
namespace GameIdeas.WebAPI.Exceptions;
namespace GameIdeas.Shared.Exceptions;
public class UserUnauthorizedException(string message) : Exception(message);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<GameIdeasOptions>(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<string, string> dictionary)
{
return Environment.GetEnvironmentVariable(name)
?? dictionary.GetValueOrDefault(name)
?? throw new EnvironmentVariableMissingException($"Missing environment variable with key: {name}");
}
private static Dictionary<string, string> 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]);
}
}

View File

@@ -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<DbContextOptionsBuilder> dbContextOptions = options =>
{
options.UseNpgsql(
GetConnectionString(),
npgOption =>
{
npgOption.CommandTimeout(60);
npgOption.MigrationsAssembly("GameIdeas.WebAPI");
});
};
// Add services to the container.
services.AddDbContext<GameIdeasContext>(dbContextOptions);
services.AddGameIdeasOptions();
services.AddDbContext<GameIdeasContext>(ContextOptions);
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<GameIdeasContext>()
.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<TranslationService>().Initialize(dictionary);
ResourcesKey.Initialize(app.Services.GetRequiredService<Translations>());
}
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]);
}
}

View File

@@ -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<User> userManager,
GameIdeasContext context,
IOptions<GameIdeasOptions> options,
IMapper mapper) : IUserReadService
{
public async Task<IEnumerable<RoleDto>> 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)

View File

@@ -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;