Switch to SmartRes Result/Error, upgrade to .NET 10

Migrate codebase to use SmartRes Result<T, Error> (and Unit) across services and interfaces, replacing previous Result usage and updating many method signatures and implementations. Add ResultExtensions to convert SmartRes results to ASP.NET ActionResult and refactor AuthController to use functional Bind/Tap chains for registration/login flows. Upgrade projects to .NET 10 and bump related NuGet packages; add libs/SmartRes.dll and project references. Simplify DbContext registration to use Npgsql only with retry policies and enable detailed logging. Update Swagger/launch settings and other minor fixes (AutoMapper registration change, whitespace/exception handling, and removal of the Govor.ConsoleClient files).
This commit is contained in:
Artemy
2026-07-25 20:20:45 +07:00
parent 6d1c53beeb
commit 3c785d5c89
65 changed files with 376 additions and 838 deletions
@@ -1,11 +1,12 @@
using Govor.Domain.Common;
using Govor.Domain.Models.Users;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
public interface IUserSessionOpener
{
Task<Result<RefreshResult>> OpenSessionAsync(User user, string deviceInfo);
Task<Result<RefreshResult, Error>> OpenSessionAsync(User user, string deviceInfo);
}
@@ -1,8 +1,9 @@
using Govor.Domain.Common;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
public interface IUserSessionReader
{
Task<Result<List<Domain.Models.Users.UserSession>>> GetAllSessionsAsync(Guid userId);
Task<Result<List<Domain.Models.Users.UserSession>, Error>> GetAllSessionsAsync(Guid userId);
}
@@ -1,8 +1,9 @@
using Govor.Domain.Common;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
public interface IUserSessionRefresher
{
Task<Result<RefreshResult>> RefreshTokenAsync(string refreshToken);
Task<Result<RefreshResult, Error>> RefreshTokenAsync(string refreshToken);
}
@@ -1,10 +1,11 @@
using Govor.Domain.Common;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
public interface IUserSessionRevoker
{
Task<Result> CloseSessionByIdAsync(Guid sessionId, Guid userId);
Task<Result> CloseAllSessionsAsync(Guid userId);
Task<Result<Unit, Error>> CloseSessionByIdAsync(Guid sessionId, Guid userId);
Task<Result<Unit, Error>> CloseAllSessionsAsync(Guid userId);
}
@@ -1,9 +1,10 @@
using Govor.Application.Authentication.JWT;
using Govor.Domain;
using Govor.Domain.Common; // Путь к вашему Result и Error
using Govor.Domain.Common;
using Govor.Domain.Models.Users;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
@@ -32,7 +33,7 @@ public class UserSessionOpener : IUserSessionOpener
_jwtService = jwtService;
}
public async Task<Result<RefreshResult>> OpenSessionAsync(User user, string deviceInfo)
public async Task<Result<RefreshResult, Error>> OpenSessionAsync(User user, string deviceInfo)
{
_logger.LogInformation("Opening session for user {UserId} on device '{DeviceInfo}'", user.Id, deviceInfo);
@@ -41,7 +42,7 @@ public class UserSessionOpener : IUserSessionOpener
if (result.IsFailure)
{
_logger.LogError("Failed to fetch sessions for user {UserId}: {Error}", user.Id, result.Error.Message);
return Result<RefreshResult>.Failure(result.Error);
return Result.Failure<RefreshResult>(result.Error);
}
var sessions = result.Value;
@@ -67,7 +68,7 @@ public class UserSessionOpener : IUserSessionOpener
catch (Exception ex)
{
_logger.LogError(ex, "Database error while opening session for user {UserId}", user.Id);
return Result<RefreshResult>.Failure(ex);
return Result.Failure<RefreshResult>(ex);
}
}
@@ -3,6 +3,7 @@ using Govor.Domain.Common;
using Govor.Domain.Models.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
@@ -17,13 +18,14 @@ public class UserSessionReader : IUserSessionReader
_logger = logger;
}
public async Task<Result<List<UserSession>>> GetAllSessionsAsync(Guid userId)
public async Task<Result<List<UserSession>, Error>> GetAllSessionsAsync(Guid userId)
{
if (userId == Guid.Empty)
{
return Result<List<UserSession>>.Failure(new Error(
"UserSession.InvalidUserId",
"Provided User ID cannot be empty."));
return Result.Failure<List<UserSession>>(Error.Conflict("UserSession.InvalidUserId",
"Provided User ID cannot be empty.")
);
}
_logger.LogInformation("Getting all active sessions for user {UserId}", userId);
@@ -40,7 +42,7 @@ public class UserSessionReader : IUserSessionReader
catch (Exception ex)
{
_logger.LogError(ex, "Failed to fetch user sessions for user {UserId}", userId);
return Result<List<UserSession>>.Failure(ex);
return Result.Failure<List<UserSession>>(ex);
}
}
}
@@ -4,6 +4,7 @@ using Govor.Domain.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
@@ -29,11 +30,11 @@ public class UserSessionRefresher : IUserSessionRefresher
_context = context;
}
public async Task<Result<RefreshResult>> RefreshTokenAsync(string refreshToken)
public async Task<Result<RefreshResult, Error>> RefreshTokenAsync(string refreshToken)
{
if (string.IsNullOrWhiteSpace(refreshToken))
{
return Result<RefreshResult>.Failure(new Error("Auth.EmptyToken", "Refresh token cannot be empty."));
return Result.Failure<RefreshResult>(Error.Failure("Auth.EmptyToken", "Refresh token cannot be empty."));
}
var hashedToken = _jwtTokenHasher.HashToken(refreshToken);
@@ -48,13 +49,13 @@ public class UserSessionRefresher : IUserSessionRefresher
if (session is null)
{
_logger.LogWarning("Refresh token session not found for hashed token");
return Result<RefreshResult>.Failure(new Error("Auth.InvalidToken", "Invalid refresh token."));
return Result.Failure<RefreshResult>(Error.Failure("Auth.InvalidToken", "Invalid refresh token."));
}
if (session.IsRevoked || session.ExpiresAt <= DateTime.UtcNow)
{
_logger.LogWarning("Attempted to refresh an expired or revoked session: {SessionId}", session.Id);
return Result<RefreshResult>.Failure(new Error("Auth.InvalidToken", "Refresh token is invalid or expired."));
return Result.Failure<RefreshResult>(Error.Failure("Auth.InvalidToken", "Refresh token is invalid or expired."));
}
var newAccessToken = await _jwtService.GenerateAccessTokenAsync(session.User, session.Id);
@@ -74,7 +75,7 @@ public class UserSessionRefresher : IUserSessionRefresher
catch (Exception ex)
{
_logger.LogError(ex, "Database error occurred during token refresh execution");
return Result<RefreshResult>.Failure(ex);
return Result.Failure<RefreshResult>(ex);
}
}
}
@@ -3,6 +3,7 @@ using Govor.Domain;
using Govor.Domain.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SmartRes;
namespace Govor.Application.Users.UserSessions;
@@ -22,7 +23,7 @@ public class UserSessionRevoker : IUserSessionRevoker
_logger = logger;
}
public async Task<Result> CloseSessionByIdAsync(Guid sessionId, Guid userId)
public async Task<Result<Unit, Error>> CloseSessionByIdAsync(Guid sessionId, Guid userId)
{
_logger.LogInformation("Attempting to close session {SessionId} for user {UserId}", sessionId, userId);
@@ -34,9 +35,13 @@ public class UserSessionRevoker : IUserSessionRevoker
if (session is null)
{
_logger.LogWarning("Active session {SessionId} not found or doesn't belong to user {UserId}", sessionId, userId);
return Result.Failure(new Error(
"UserSession.NotFoundOrUnauthorized",
$"Active session {sessionId} for user {userId} was not found."));
return Result.Failure(
Error.NotFound(
"UserSession.NotFoundOrUnauthorized",
$"Active session {sessionId} for user {userId} was not found."
)
);
}
session.IsRevoked = true;
@@ -55,7 +60,7 @@ public class UserSessionRevoker : IUserSessionRevoker
}
}
public async Task<Result> CloseAllSessionsAsync(Guid userId)
public async Task<Result<Unit, Error>> CloseAllSessionsAsync(Guid userId)
{
_logger.LogInformation("Attempting to close all active sessions for user {UserId}", userId);