diff --git a/Directory.Packages.props b/Directory.Packages.props
index 98a3848..822d193 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -31,7 +31,7 @@
-
+
@@ -39,13 +39,13 @@
-
+
-
+
-
+
@@ -63,8 +63,8 @@
-
-
-
+
+
+
diff --git a/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs b/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs
index ec4cc91..d94a412 100644
--- a/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/GoogleOAuthEndpoints.cs
@@ -29,6 +29,7 @@ private static void MapGetAuthUrl(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -64,6 +65,7 @@ private static void MapRevoke(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
}
diff --git a/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs b/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs
index 58c31d5..f4b8a94 100644
--- a/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/InvoiceEndpoints.cs
@@ -34,6 +34,7 @@ private static void MapGetInvoices(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -51,6 +52,7 @@ private static void MapGetInvoice(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -70,6 +72,7 @@ private static void MapGetInvoiceByBarcode(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -89,6 +92,7 @@ private static void MapCreateInvoice(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -107,6 +111,7 @@ private static void MapUpdateInvoice(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -125,6 +130,7 @@ private static void MapDeleteInvoice(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
}
diff --git a/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs b/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs
index ed9e853..10e9199 100644
--- a/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/JobScheduleEndpoints.cs
@@ -34,6 +34,7 @@ private static void MapGetJobSchedules(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -51,8 +52,9 @@ private static void MapGetJobSchedule(RouteGroupBuilder endpoint)
.WithName("GetJobSchedule")
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
- .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
+ .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -70,8 +72,9 @@ private static void MapGetJobScheduleByUserId(RouteGroupBuilder endpoint)
.WithName("GetJobScheduleByUserId")
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
- .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
+ .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -91,6 +94,7 @@ private static void MapCreateJobSchedule(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -110,6 +114,7 @@ private static void MapUpdateJobSchedule(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -129,6 +134,7 @@ private static void MapDeleteJobSchedule(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
}
diff --git a/InvoiceReminder.API/Endpoints/LoginEndpoints.cs b/InvoiceReminder.API/Endpoints/LoginEndpoints.cs
index 9f085ea..43d3254 100644
--- a/InvoiceReminder.API/Endpoints/LoginEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/LoginEndpoints.cs
@@ -31,7 +31,13 @@ private static void MapLogin(IEndpointRouteBuilder endpoints)
var result = await appService.ValidateUserPasswordAsync(request.Email, request.Password, ct);
return result.IsSuccess
- ? Results.Ok(jwtProvider.Generate(new UserClaims { Id = result.Value.Id, Email = result.Value.Email }))
+ ? Results.Ok(jwtProvider.Generate(new UserClaims
+ {
+ Id = result.Value.Id,
+ Name = result.Value.Name,
+ Email = result.Value.Email,
+ TelegramChatId = result.Value.TelegramChatId
+ }))
: Results.Unauthorized();
})
.WithName("Login")
diff --git a/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs b/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs
index 6986288..bd65d6c 100644
--- a/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/ScanEmailDefinitionEndpoints.cs
@@ -35,6 +35,7 @@ private static void MapGetScanEmailDefinitions(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -53,6 +54,7 @@ private static void MapGetScanEmailDefinition(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -72,6 +74,7 @@ private static void MapGetByUserId(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -91,6 +94,7 @@ private static void MapGetBySenderEmailAddress(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -111,6 +115,7 @@ private static void MapCreateScanEmailDefinition(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -130,6 +135,7 @@ private static void MapUpdateScanEmailDefinition(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -149,6 +155,7 @@ private static void MapDeleteScanEmailDefinition(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
}
diff --git a/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs b/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs
index a52811d..2f84fdd 100644
--- a/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/SendMessageEndpoints.cs
@@ -30,6 +30,7 @@ private static void MapSendMessage(RouteGroupBuilder endpoint)
.WithName("SendMessage")
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status500InternalServerError);
}
diff --git a/InvoiceReminder.API/Endpoints/UserEndpoints.cs b/InvoiceReminder.API/Endpoints/UserEndpoints.cs
index fd6b971..3b0985d 100644
--- a/InvoiceReminder.API/Endpoints/UserEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/UserEndpoints.cs
@@ -18,6 +18,7 @@ public void RegisterEndpoints(IEndpointRouteBuilder endpoints)
MapCreateUser(endpoint);
MapCreateUsers(endpoint);
MapUpdateUser(endpoint);
+ MapUpdateBasicUserInfo(endpoint);
MapDeleteUser(endpoint);
}
@@ -35,6 +36,7 @@ private static void MapGetUsers(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -51,6 +53,8 @@ private static void MapGetUser(RouteGroupBuilder endpoint)
.WithName("GetUser")
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
+ .Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -69,6 +73,8 @@ private static void MapGetUserByEmail(RouteGroupBuilder endpoint)
.WithName("GetUserByEmail")
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
+ .Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -85,7 +91,7 @@ private static void MapCreateUser(RouteGroupBuilder endpoint)
: Results.Problem(result.Error);
})
.WithName("CreateUser")
- .RequireAuthorization()
+ .AllowAnonymous()
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status500InternalServerError);
@@ -107,6 +113,7 @@ private static void MapCreateUsers(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -125,6 +132,26 @@ private static void MapUpdateUser(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
+ .Produces(StatusCodes.Status500InternalServerError);
+ }
+
+ private static void MapUpdateBasicUserInfo(RouteGroupBuilder endpoint)
+ {
+ _ = endpoint.MapPatch("/",
+ async (IUserAppService appService, [FromBody] UserViewModel viewModel, CancellationToken ct) =>
+ {
+ var result = await appService.UpdateBasicUserInfoAsync(viewModel, ct);
+
+ return result.IsSuccess
+ ? Results.Ok(result.Value)
+ : Results.Problem(result.Error);
+ })
+ .WithName("UpdateBasicUserInfo")
+ .RequireAuthorization()
+ .Produces(StatusCodes.Status200OK)
+ .Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -143,6 +170,7 @@ private static void MapDeleteUser(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
}
diff --git a/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs b/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs
index 997330e..fbe8c57 100644
--- a/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs
+++ b/InvoiceReminder.API/Endpoints/UserPasswordEndpoints.cs
@@ -14,6 +14,7 @@ public void RegisterEndpoints(IEndpointRouteBuilder endpoints)
MapCreateUserPassword(endpoint);
MapCreateUsersPassword(endpoint);
+ MapChangeUserPassword(endpoint);
MapDeleteUserPassword(endpoint);
MapUpdateUserPassword(endpoint);
}
@@ -33,6 +34,7 @@ private static void MapCreateUserPassword(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -52,6 +54,27 @@ private static void MapCreateUsersPassword(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
+ .Produces(StatusCodes.Status500InternalServerError);
+ }
+
+ private static void MapChangeUserPassword(RouteGroupBuilder endpoint)
+ {
+ _ = endpoint.MapPatch("/",
+ async (IUserPasswordAppService appService, [FromBody] UserPasswordViewModel viewModel,
+ CancellationToken ct) =>
+ {
+ var result = await appService.ChangePasswordAsync(viewModel, ct);
+
+ return result.IsSuccess
+ ? Results.Ok(result.Value)
+ : Results.Problem(result.Error);
+ })
+ .WithName("ChangeUserPassword")
+ .RequireAuthorization()
+ .Produces(StatusCodes.Status200OK)
+ .Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -71,6 +94,7 @@ private static void MapUpdateUserPassword(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
@@ -90,6 +114,7 @@ private static void MapDeleteUserPassword(RouteGroupBuilder endpoint)
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
+ .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status500InternalServerError);
}
}
diff --git a/InvoiceReminder.API/Program.cs b/InvoiceReminder.API/Program.cs
index 40fc1dd..2c3a427 100644
--- a/InvoiceReminder.API/Program.cs
+++ b/InvoiceReminder.API/Program.cs
@@ -11,6 +11,21 @@
builder.Services.AddExceptionHandler();
builder.Services.AddInfrastructure();
builder.Services.AddOpenApi(opt => opt.AddDocumentTransformer());
+
+builder.Services.AddCors(opt =>
+ opt.AddPolicy("CorsPolicy", policy =>
+ {
+ var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? [];
+
+ _ = policy
+ .AllowAnyMethod()
+ .AllowCredentials()
+ .WithOrigins(allowedOrigins)
+ .WithHeaders("Content-Type", "Authorization");
+ }
+ )
+);
+
builder.Services.AddHealthChecks().AddNpgSql
(
connectionString: builder.Configuration.GetConnectionString("DataBaseConnection"),
@@ -32,6 +47,7 @@
_ = app.UseHsts();
}
+app.UseCors("CorsPolicy");
app.RegisterEndpoints();
app.UseExceptionHandler();
app.UseStatusCodePages();
diff --git a/InvoiceReminder.API/appsettings.json b/InvoiceReminder.API/appsettings.json
index 766550e..37ff50a 100644
--- a/InvoiceReminder.API/appsettings.json
+++ b/InvoiceReminder.API/appsettings.json
@@ -8,6 +8,11 @@
"AllowedHosts": "*",
"Timeout": 120,
"ProviderName": "Npgsql",
+ "Cors": {
+ "AllowedOrigins": [
+ "SECRET_ORIGIN"
+ ]
+ },
"appKeys": {
"googleOauthClientId": "SECRET_CLIENT_ID",
"googleOauthClientSecret": "SECRET_CLIENT_SECRET",
diff --git a/InvoiceReminder.Application/AppServices/UserAppService.cs b/InvoiceReminder.Application/AppServices/UserAppService.cs
index c42829e..c97d675 100644
--- a/InvoiceReminder.Application/AppServices/UserAppService.cs
+++ b/InvoiceReminder.Application/AppServices/UserAppService.cs
@@ -74,4 +74,21 @@ public async Task> ValidateUserPasswordAsync(
? Result.Failure("User not Found.")
: Result.Success(entity.Adapt());
}
+
+ public async Task> UpdateBasicUserInfoAsync(
+ UserViewModel viewModel,
+ CancellationToken cancellationToken = default)
+ {
+ if (viewModel is null)
+ {
+ return Result.Failure($"Parameter {nameof(viewModel)} was Null.");
+ }
+
+ var entity = viewModel.Adapt();
+ var result = await _repository.UpdateBasicUserInfoAsync(entity, cancellationToken);
+
+ return result
+ ? Result.Success(result)
+ : Result.Failure("User not Found!");
+ }
}
diff --git a/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs b/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs
index 337bd2d..ba9f73f 100644
--- a/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs
+++ b/InvoiceReminder.Application/AppServices/UserPasswordAppService.cs
@@ -15,8 +15,10 @@ public class UserPasswordAppService : BaseAppService("Security:ParallelismFactor");
_repository = repository;
@@ -78,6 +80,31 @@ public override async Task> BulkInsertAsync(
return Result.Success(result);
}
+ public async Task> ChangePasswordAsync(
+ UserPasswordViewModel viewModel,
+ CancellationToken cancellationToken = default)
+ {
+ if (viewModel is null)
+ {
+ return Result.Failure("The provided object data was Null.");
+ }
+
+ if (string.IsNullOrWhiteSpace(viewModel.PasswordHash))
+ {
+ return Result.Failure("Password is required.");
+ }
+
+ (var pHash, var pSalt) = viewModel.PasswordHash.HashPassword(_parallelismFactor);
+ viewModel.PasswordHash = pHash;
+ viewModel.PasswordSalt = pSalt;
+
+ var result = await _repository.ChangePasswordAsync(viewModel.Adapt(), cancellationToken);
+
+ return result
+ ? Result.Success(result)
+ : Result.Failure("Failed to change the password.");
+ }
+
public async Task> GetByUserIdAsync(
Guid userId,
CancellationToken cancellationToken = default)
diff --git a/InvoiceReminder.Application/Interfaces/IUserAppService.cs b/InvoiceReminder.Application/Interfaces/IUserAppService.cs
index 7c2fc1a..f22a857 100644
--- a/InvoiceReminder.Application/Interfaces/IUserAppService.cs
+++ b/InvoiceReminder.Application/Interfaces/IUserAppService.cs
@@ -7,6 +7,7 @@ namespace InvoiceReminder.Application.Interfaces;
public interface IUserAppService : IBaseAppService
{
Task> GetByEmailAsync(string value, CancellationToken cancellationToken = default);
+ Task> UpdateBasicUserInfoAsync(UserViewModel viewModel, CancellationToken cancellationToken = default);
Task> ValidateUserPasswordAsync(string email, string password,
- CancellationToken cancellationToken = default);
+ CancellationToken cancellationToken = default);
}
diff --git a/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs b/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs
index 1823030..67e486d 100644
--- a/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs
+++ b/InvoiceReminder.Application/Interfaces/IUserPasswordAppService.cs
@@ -7,4 +7,5 @@ namespace InvoiceReminder.Application.Interfaces;
public interface IUserPasswordAppService : IBaseAppService
{
Task> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default);
+ Task> ChangePasswordAsync(UserPasswordViewModel viewModel, CancellationToken cancellationToken = default);
}
diff --git a/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj b/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj
index 70853db..803b79a 100644
--- a/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj
+++ b/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj
@@ -1,4 +1,4 @@
-
+
true
diff --git a/InvoiceReminder.Authentication/Abstractions/UserClaims.cs b/InvoiceReminder.Authentication/Abstractions/UserClaims.cs
index 7824f0f..0595e4b 100644
--- a/InvoiceReminder.Authentication/Abstractions/UserClaims.cs
+++ b/InvoiceReminder.Authentication/Abstractions/UserClaims.cs
@@ -3,5 +3,7 @@ namespace InvoiceReminder.Authentication.Abstractions;
public record UserClaims
{
public required Guid Id { get; init; }
+ public required string Name { get; init; }
public required string Email { get; init; }
+ public required long TelegramChatId { get; set; }
}
diff --git a/InvoiceReminder.Authentication/Jwt/JwtProvider.cs b/InvoiceReminder.Authentication/Jwt/JwtProvider.cs
index 8c57650..36e7002 100644
--- a/InvoiceReminder.Authentication/Jwt/JwtProvider.cs
+++ b/InvoiceReminder.Authentication/Jwt/JwtProvider.cs
@@ -37,7 +37,9 @@ public JwtObject Generate(UserClaims user)
var claims = new Claim[]
{
new (JwtRegisteredClaimNames.Sub, user.Id.ToString()),
- new (JwtRegisteredClaimNames.Email, user.Email)
+ new (JwtRegisteredClaimNames.Name, user.Name),
+ new (JwtRegisteredClaimNames.Email, user.Email),
+ new ("telegramChatId", user.TelegramChatId.ToString())
};
var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecretKey));
diff --git a/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs b/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs
index e990407..1b4041f 100644
--- a/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs
+++ b/InvoiceReminder.Data/Interfaces/IUserPasswordRepository.cs
@@ -5,4 +5,5 @@ namespace InvoiceReminder.Data.Interfaces;
public interface IUserPasswordRepository : IBaseRepository
{
Task GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default);
+ Task ChangePasswordAsync(UserPassword userPassword, CancellationToken cancellationToken = default);
}
diff --git a/InvoiceReminder.Data/Interfaces/IUserRepository.cs b/InvoiceReminder.Data/Interfaces/IUserRepository.cs
index c381d5c..86c3352 100644
--- a/InvoiceReminder.Data/Interfaces/IUserRepository.cs
+++ b/InvoiceReminder.Data/Interfaces/IUserRepository.cs
@@ -5,4 +5,5 @@ namespace InvoiceReminder.Data.Interfaces;
public interface IUserRepository : IBaseRepository
{
Task GetByEmailAsync(string value, CancellationToken cancellationToken = default);
+ Task UpdateBasicUserInfoAsync(User user, CancellationToken cancellationToken = default);
}
diff --git a/InvoiceReminder.Data/Repository/UserPasswordRepository.cs b/InvoiceReminder.Data/Repository/UserPasswordRepository.cs
index 9828bd5..fcb68f6 100644
--- a/InvoiceReminder.Data/Repository/UserPasswordRepository.cs
+++ b/InvoiceReminder.Data/Repository/UserPasswordRepository.cs
@@ -21,6 +21,58 @@ public UserPasswordRepository(CoreDbContext dbContext, ILogger ChangePasswordAsync(UserPassword userPassword, CancellationToken cancellationToken = default)
+ {
+ var result = false;
+ var query = """
+ update invoice_reminder.user_password
+ set
+ password_hash = @passwordHash,
+ password_salt = @passwordSalt,
+ updated_at = now()
+ where user_id = @userId
+ """;
+
+ try
+ {
+ var parameters = new DynamicParameters(new
+ {
+ passwordHash = userPassword.PasswordHash,
+ passwordSalt = userPassword.PasswordSalt,
+ userId = userPassword.UserId
+ });
+ var command = new CommandDefinition(query, parameters, cancellationToken: cancellationToken);
+
+ result = await _dbConnection.ExecuteAsync(command) > 0;
+ }
+ catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested)
+ {
+ var method = $"{nameof(UserPasswordRepository)}.{nameof(ChangePasswordAsync)}";
+ var contextualInfo = $"Method {method} execution was interrupted by a CancellationToken Request...";
+
+ if (_logger.IsEnabled(LogLevel.Warning))
+ {
+ _logger.LogWarning(ex, LogExceptionMessage, contextualInfo, ex.Message);
+ }
+
+ throw new OperationCanceledException(contextualInfo, ex, cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ var method = $"{nameof(UserPasswordRepository)}.{nameof(ChangePasswordAsync)}";
+ var contextualInfo = $"Exception raised while updating DB >> {method}(...)";
+
+ if (_logger.IsEnabled(LogLevel.Error))
+ {
+ _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message);
+ }
+
+ throw new DataLayerException(contextualInfo, ex);
+ }
+
+ return result;
+ }
+
public async Task GetByUserIdAsync(Guid userId, CancellationToken cancellationToken = default)
{
UserPassword userPassword = default;
diff --git a/InvoiceReminder.Data/Repository/UserRepository.cs b/InvoiceReminder.Data/Repository/UserRepository.cs
index 0c2b93e..94d74a7 100644
--- a/InvoiceReminder.Data/Repository/UserRepository.cs
+++ b/InvoiceReminder.Data/Repository/UserRepository.cs
@@ -7,6 +7,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Data;
+using System.Data.Common;
namespace InvoiceReminder.Data.Repository;
@@ -145,4 +146,63 @@ public override async Task GetByIdAsync(Guid id, CancellationToken cancell
return result.FirstOrDefault().Value;
}
+
+ public async Task UpdateBasicUserInfoAsync(User user, CancellationToken cancellationToken = default)
+ {
+ var result = false;
+ var query = """
+ update invoice_reminder.user
+ set name = @Name,
+ email = @Email,
+ telegram_chat_id = @TelegramChatId,
+ updated_at = now()
+ where id = @Id
+ """;
+
+ try
+ {
+ var parameters = new DynamicParameters(new { user.Name, user.Email, user.TelegramChatId, user.Id });
+ var command = new CommandDefinition(query, parameters, cancellationToken: cancellationToken);
+
+ result = await _dbConnection.ExecuteAsync(command) > 0;
+ }
+ catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested)
+ {
+ var method = $"{nameof(UserRepository)}.{nameof(UpdateBasicUserInfoAsync)}";
+ var contextualInfo = $"Method {method} execution was interrupted by a CancellationToken Request...";
+
+ if (_logger.IsEnabled(LogLevel.Warning))
+ {
+ _logger.LogWarning(ex, LogExceptionMessage, contextualInfo, ex.Message);
+ }
+
+ throw new OperationCanceledException(contextualInfo, ex, cancellationToken);
+ }
+ catch (DbException ex)
+ {
+ var method = $"{nameof(UserRepository)}.{nameof(UpdateBasicUserInfoAsync)}";
+ var contextualInfo = $"Database update failed while executing >> {method}(...)";
+
+ if (_logger.IsEnabled(LogLevel.Error))
+ {
+ _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message);
+ }
+
+ throw new DataLayerException(contextualInfo, ex);
+ }
+ catch (Exception ex)
+ {
+ var method = $"{nameof(UserRepository)}.{nameof(UpdateBasicUserInfoAsync)}";
+ var contextualInfo = $"Exception raised while updating Entity >> {method}(...)";
+
+ if (_logger.IsEnabled(LogLevel.Error))
+ {
+ _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message);
+ }
+
+ throw new DataLayerException(contextualInfo, ex);
+ }
+
+ return result;
+ }
}
diff --git a/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj b/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj
index 4e07c9e..9e79e2e 100644
--- a/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj
+++ b/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj
@@ -1,4 +1,4 @@
-
+
CA1873
diff --git a/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs
index 93e2a3c..1a85a20 100644
--- a/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs
+++ b/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs
@@ -385,11 +385,10 @@ public async Task GetUserByEmail_WhenUserIsAuthenticatedButNotExists_ShouldRetur
#region MapPost Tests
[TestMethod]
- public async Task CreateUser_WhenUserIsAuthenticated_ShouldReturnCreated()
+ public async Task CreateUser_ShouldReturnCreated()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, basepath);
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "test_token");
var userViewModel = _userViewModelFaker.Generate();
var expectedResult = Result.Success(userViewModel);
@@ -397,10 +396,6 @@ public async Task CreateUser_WhenUserIsAuthenticated_ShouldReturnCreated()
_ = _userAppService.AddAsync(Arg.Any(), Arg.Any())
.Returns(expectedResult);
- _ = _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any