mirror of
https://github.com/Govor-team/Govor.git
synced 2026-09-23 02:43:18 +00:00
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:
@@ -6,6 +6,7 @@ using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Govor.Domain.Models.Users;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Authentication;
|
||||
|
||||
@@ -28,18 +29,18 @@ public class AuthService : IAccountService
|
||||
_usernameValidator = usernameValidator;
|
||||
}
|
||||
|
||||
public async Task<Result<User>> RegistrationAsync(string name, string password, Invitation invitation)
|
||||
public async Task<Result<User, Error>> RegistrationAsync(string name, string password, Invitation invitation)
|
||||
{
|
||||
|
||||
var validationResult = _usernameValidator.Validate(name);
|
||||
if (validationResult.IsFailure)
|
||||
{
|
||||
return Result<User>.Failure(validationResult.Error);
|
||||
return Result.Failure<User>(validationResult.Error);
|
||||
}
|
||||
|
||||
if (await _userNameExistValidator.IsUsernameExistsAsync(name))
|
||||
{
|
||||
return Result<User>.Failure(new Error(
|
||||
return Result.Failure<User>(Error.Conflict(
|
||||
nameof(UserAlreadyExistException),
|
||||
$"User with username '{name}' already exists."));
|
||||
}
|
||||
@@ -63,12 +64,14 @@ public class AuthService : IAccountService
|
||||
|
||||
await SetRoleAsync(user, invitation);
|
||||
|
||||
// TODO: inv.participantCount -= 1; db.save();
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return user; // Success
|
||||
}
|
||||
|
||||
public async Task<Result<User>> LoginAsync(string name, string password)
|
||||
public async Task<Result<User, Error>> LoginAsync(string name, string password)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
@@ -76,14 +79,14 @@ public class AuthService : IAccountService
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return Result<User>.Failure(new Error(
|
||||
return Result.Failure<User>(Error.NotFound(
|
||||
nameof(UserNotRegisteredException),
|
||||
$"User '{name}' is not registered."));
|
||||
}
|
||||
|
||||
if (!_passwordHasher.Verify(password, user.PasswordHash))
|
||||
{
|
||||
return Result<User>.Failure(new Error(
|
||||
return Result.Failure<User>(Error.Failure(
|
||||
nameof(InvalidOperationException),
|
||||
"The password provided is incorrect."));
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Govor.Domain.Models.Users;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Authentication;
|
||||
|
||||
public interface IAccountService
|
||||
{
|
||||
public Task<Result<User>> RegistrationAsync(string name, string password, Invitation invitation);
|
||||
public Task<Result<User>> LoginAsync(string name, string password);
|
||||
public Task<Result<User, Error>> RegistrationAsync(string name, string password, Invitation invitation);
|
||||
public Task<Result<User, Error>> LoginAsync(string name, string password);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Govor.Domain.Models.Users;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Authentication;
|
||||
|
||||
@@ -8,5 +9,5 @@ public interface IInvitesService
|
||||
{
|
||||
public Task<string> GetRoleNameAsync(User user);
|
||||
public Task<string> GetRoleNameAsync(Guid sessionId);
|
||||
public Task<Result<Invitation>> ValidateAsync(string inviteCode);
|
||||
public Task<Result<Invitation, Error>> ValidateAsync(string inviteCode);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Govor.Domain.Models.Users;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Authentication;
|
||||
|
||||
@@ -31,22 +32,24 @@ public class InvitesService : IInvitesService
|
||||
return invitation.IsAdmin ? "Admin" : "User";
|
||||
}
|
||||
|
||||
public async Task<Result<Invitation>> ValidateAsync(string inviteCode)
|
||||
public async Task<Result<Invitation, Error>> ValidateAsync(string inviteCode)
|
||||
{
|
||||
var invite = await _context.Invitations
|
||||
.Include(s => s.Users)
|
||||
.FirstOrDefaultAsync(s => s.Code == inviteCode);
|
||||
|
||||
if (invite == null)
|
||||
return Result<Invitation>.Failure(Error.Null);
|
||||
return Result.Failure<Invitation>(Error.NotFound("Auth.LinkNotFount","Invitation not found."));
|
||||
|
||||
if (invite.EndDate < DateTime.Now || invite.MaxParticipants <= invite.Users.Count)
|
||||
{
|
||||
invite.IsActive = false;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Result<Invitation>.Failure(new Error(
|
||||
"Auth.InviteLinkInvalid", $"Invite link invalid: {inviteCode}")
|
||||
return Result.Failure<Invitation>(
|
||||
Error.Failure(
|
||||
"Auth.InviteLinkInvalid", $"Invite link invalid: {inviteCode}"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Govor.Domain;
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Friends;
|
||||
|
||||
@@ -21,10 +22,10 @@ public class FriendRequestCommandService : IFriendRequestCommandService
|
||||
_privateChatsCreator = privateChatsCreator;
|
||||
}
|
||||
|
||||
public async Task<Result<Friendship>> SendAsync(Guid fromUserId, Guid toUserId)
|
||||
public async Task<Result<Friendship, Error>> SendAsync(Guid fromUserId, Guid toUserId)
|
||||
{
|
||||
if (fromUserId == toUserId)
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.Failure(
|
||||
"Friendship.Send",
|
||||
"Cannot send a request to self user")
|
||||
);
|
||||
@@ -50,7 +51,7 @@ public class FriendRequestCommandService : IFriendRequestCommandService
|
||||
friendship.Status == FriendshipStatus.Accepted ||
|
||||
friendship.Status == FriendshipStatus.Blocked)
|
||||
{
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.Conflict(
|
||||
"Friendship.Send",
|
||||
$"The request is already {friendship.Status}")
|
||||
);
|
||||
@@ -65,24 +66,24 @@ public class FriendRequestCommandService : IFriendRequestCommandService
|
||||
return friendship;
|
||||
}
|
||||
|
||||
public async Task<Result<Friendship>> AcceptAsync(Guid requestId, Guid currentUserId)
|
||||
public async Task<Result<Friendship, Error>> AcceptAsync(Guid requestId, Guid currentUserId)
|
||||
{
|
||||
var friendship = await _context.Friendships.FindAsync(requestId);
|
||||
|
||||
if (friendship is null)
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.NotFound(
|
||||
"Friendship.Accept",
|
||||
"Friendship not found! You cant accept request!")
|
||||
);
|
||||
|
||||
if (friendship.AddresseeId != currentUserId)
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.Forbidden(
|
||||
"Friendship.Accept",
|
||||
"You cannot accept this request!")
|
||||
);
|
||||
|
||||
if (friendship.Status != FriendshipStatus.Pending)
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.Forbidden(
|
||||
"Friendship.Accept",
|
||||
"Request is already accepted!")
|
||||
);
|
||||
@@ -96,24 +97,24 @@ public class FriendRequestCommandService : IFriendRequestCommandService
|
||||
return friendship;
|
||||
}
|
||||
|
||||
public async Task<Result<Friendship>> RejectAsync(Guid requestId, Guid currentUserId)
|
||||
public async Task<Result<Friendship, Error>> RejectAsync(Guid requestId, Guid currentUserId)
|
||||
{
|
||||
var friendship = await _context.Friendships.FindAsync(requestId);
|
||||
|
||||
if (friendship == null)
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.NotFound(
|
||||
"Friendship.Reject",
|
||||
"Friendship not found! You cant reject request!")
|
||||
);
|
||||
|
||||
if (friendship.AddresseeId != currentUserId)
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.Forbidden(
|
||||
"Friendship.Reject",
|
||||
"You cannot reject this request!")
|
||||
);
|
||||
|
||||
if (friendship.Status != FriendshipStatus.Pending && friendship.Status != FriendshipStatus.Rejected)
|
||||
return Result<Friendship>.Failure(new Error(
|
||||
return Result.Failure<Friendship>(Error.Conflict(
|
||||
"Friendship.Reject",
|
||||
$"Request is already {friendship.Status}")
|
||||
);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Friends;
|
||||
|
||||
public interface IFriendRequestCommandService
|
||||
{
|
||||
Task<Result<Friendship>> SendAsync(Guid fromUserId, Guid toUserId);
|
||||
Task<Result<Friendship>> AcceptAsync(Guid requestId, Guid currentUserId);
|
||||
Task<Result<Friendship>> RejectAsync(Guid requestId, Guid currentUserId);
|
||||
Task<Result<Friendship, Error>> SendAsync(Guid fromUserId, Guid toUserId);
|
||||
Task<Result<Friendship, Error>> AcceptAsync(Guid requestId, Guid currentUserId);
|
||||
Task<Result<Friendship, Error>> RejectAsync(Guid requestId, Guid currentUserId);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="FirebaseAdmin" Version="3.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="11.0.0-preview.1.26104.118" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.0.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||
<PackageReference Include="FirebaseAdmin" Version="3.6.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version = "8.19.2"/>
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.19.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="SmartRes">
|
||||
<HintPath>..\libs\SmartRes.dll</HintPath>
|
||||
</Reference>
|
||||
|
||||
<ProjectReference Include="..\Govor.Domain\Govor.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Govor.Domain.Models.Users;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Groups;
|
||||
|
||||
@@ -8,9 +9,9 @@ public interface IGroupService
|
||||
{
|
||||
Task<ChatGroup> GetGroupByIdAsync(Guid groupId);
|
||||
Task<ChatGroup> CreateGroupAsync(string name, Guid creatorId, IEnumerable<Guid> initialMemberIds);
|
||||
Task<Result> AddUserToGroupByInvitationAsync(Guid userId, string invitationCode);
|
||||
Task<Result> RemoveUserFromGroupAsync(Guid groupId, Guid userId, Guid removedByUserId);
|
||||
Task<Result> DeleteGroupAsync(Guid groupId, Guid userId);
|
||||
Task<Result<Unit, Error>> AddUserToGroupByInvitationAsync(Guid userId, string invitationCode);
|
||||
Task<Result<Unit, Error>> RemoveUserFromGroupAsync(Guid groupId, Guid userId, Guid removedByUserId);
|
||||
Task<Result<Unit, Error>> DeleteGroupAsync(Guid groupId, Guid userId);
|
||||
Task<List<User>> GetGroupMembersAsync(Guid groupId);
|
||||
Task<List<ChatGroup>>GetUserGroupsAsync(Guid userId);
|
||||
ChatGroup GetGroupByInviteCode(string code);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Infrastructure.AdminsStuff;
|
||||
|
||||
public interface IInvitationGetter
|
||||
{
|
||||
Task<List<Invitation>> GetAllAsync();
|
||||
Task<Result<Invitation>> FindByIdAsync(Guid id);
|
||||
Task<Result<Invitation, Error>> FindByIdAsync(Guid id);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Infrastructure.AdminsStuff;
|
||||
|
||||
@@ -25,13 +26,13 @@ public class InvitationGetter : IInvitationGetter
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Result<Invitation>> FindByIdAsync(Guid id)
|
||||
public async Task<Result<Invitation, Error>> FindByIdAsync(Guid id)
|
||||
{
|
||||
var res = await _context.Invitations.AsNoTracking()
|
||||
.FirstOrDefaultAsync(iv => iv.Id == id);
|
||||
|
||||
if (res is null)
|
||||
return Result<Invitation>.Failure(new Error(
|
||||
return Result.Failure<Invitation>(Error.NotFound(
|
||||
nameof(InvalidOperationException),
|
||||
"Invitation not found.")
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Govor.Domain.Common;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Infrastructure.Validators;
|
||||
|
||||
public interface IUsernameValidator
|
||||
{
|
||||
Result Validate(string username);
|
||||
Result<Unit, Error> Validate(string username);
|
||||
bool TryValidate(string username);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Govor.Application.Authentication.Exceptions;
|
||||
using Govor.Domain.Common.Constants;
|
||||
using Govor.Domain.Common;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Infrastructure.Validators;
|
||||
|
||||
@@ -39,52 +40,58 @@ public class UsernameValidator : IUsernameValidator
|
||||
?? throw new InvalidOperationException("Reserved not set");
|
||||
}
|
||||
|
||||
public Result Validate(string username)
|
||||
public Result<Unit, Error> Validate(string username)
|
||||
{
|
||||
if (username.Length < UserConstants.MIN_LENGHT_OF_NAME || username.Length > UserConstants.MAX_LENGHT_OF_NAME)
|
||||
{
|
||||
return new Error(
|
||||
return Result.Failure(Error.Validation(
|
||||
ErrorCode,
|
||||
$"Username must be between {UserConstants.MIN_LENGHT_OF_NAME} and {UserConstants.MAX_LENGHT_OF_NAME} characters.");
|
||||
$"Username must be between {UserConstants.MIN_LENGHT_OF_NAME} and {UserConstants.MAX_LENGHT_OF_NAME} characters.")
|
||||
);
|
||||
}
|
||||
|
||||
if (!_usernameRegex.IsMatch(username))
|
||||
{
|
||||
return new Error(
|
||||
return Result.Failure(Error.Validation(
|
||||
ErrorCode,
|
||||
"The username must be in Cyrillic and start with a letter.");
|
||||
"The username must be in Cyrillic and start with a letter.")
|
||||
);
|
||||
}
|
||||
|
||||
if (Regex.IsMatch(username, @"(.)\1{4,}"))
|
||||
{
|
||||
return new Error(
|
||||
return Result.Failure(Error.Validation(
|
||||
ErrorCode,
|
||||
"Too many repeating characters.");
|
||||
"Too many repeating characters.")
|
||||
);
|
||||
}
|
||||
|
||||
var normalized = Normalize(username);
|
||||
|
||||
if (_reserved.Contains(normalized))
|
||||
{
|
||||
return new Error(
|
||||
return Result.Failure(Error.Validation(
|
||||
ErrorCode,
|
||||
"This username is reserved.");
|
||||
"This username is reserved.")
|
||||
);
|
||||
}
|
||||
|
||||
if (_blockedExact.Contains(normalized))
|
||||
{
|
||||
return new Error(
|
||||
return Result.Failure(Error.Validation(
|
||||
ErrorCode,
|
||||
"This username is not allowed.");
|
||||
"This username is not allowed.")
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var banned in _blockedContains)
|
||||
{
|
||||
if (normalized.Contains(banned))
|
||||
{
|
||||
return new Error(
|
||||
return Result.Failure(Error.Validation(
|
||||
ErrorCode,
|
||||
"Username contains prohibited content.");
|
||||
"Username contains prohibited content.")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.PrivateUserChats;
|
||||
|
||||
public interface IUserPrivateChatsGetterService
|
||||
{
|
||||
Task<List<PrivateChat>> GetUserChatsAsync(Guid userId);
|
||||
Task<Result<PrivateChat>> GetPrivateChatAsync(Guid chatId);
|
||||
Task<Result<PrivateChat, Error>> GetPrivateChatAsync(Guid chatId);
|
||||
Task<bool> ExistChatAsync(Guid userIdA, Guid userIdB);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Govor.Domain;
|
||||
using Govor.Domain.Common;
|
||||
using Govor.Domain.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.PrivateUserChats;
|
||||
|
||||
@@ -22,14 +23,16 @@ public class UserPrivateChatsGetter : IUserPrivateChatsGetterService
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Result<PrivateChat>> GetPrivateChatAsync(Guid chatId)
|
||||
public async Task<Result<PrivateChat, Error>> GetPrivateChatAsync(Guid chatId)
|
||||
{
|
||||
var res = await _context.PrivateChats.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p => p.Id == chatId);
|
||||
|
||||
if (res == null)
|
||||
return Result<PrivateChat>.Failure(new Error(nameof(InvalidOperationException),
|
||||
"PrivateChat not found.")
|
||||
return Result.Failure<PrivateChat>(
|
||||
Error.Failure(
|
||||
nameof(InvalidOperationException),
|
||||
"PrivateChat not found.")
|
||||
);
|
||||
|
||||
return res;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using Govor.Domain.Common;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Profiles;
|
||||
|
||||
public interface IProfileService
|
||||
{
|
||||
public Task<Result<UserProfile>> GetUserProfileAsync(Guid userId);
|
||||
public Task<Result> SetDescription(string description, Guid userId);
|
||||
public Task<Result> SetNewIcon(Guid userId, Guid iconId);
|
||||
public Task<Result<UserProfile, Error>> GetUserProfileAsync(Guid userId);
|
||||
public Task<Result<Unit, Error>> SetDescription(string description, Guid userId);
|
||||
public Task<Result<Unit, Error>> SetNewIcon(Guid userId, Guid iconId);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Govor.Domain.Common;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.Profiles;
|
||||
|
||||
@@ -16,7 +17,7 @@ public class ProfileService : IProfileService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfile>> GetUserProfileAsync(Guid userId)
|
||||
public async Task<Result<UserProfile, Error>> GetUserProfileAsync(Guid userId)
|
||||
{
|
||||
_logger.LogInformation("Getting user {UserId} profile", userId);
|
||||
|
||||
@@ -34,13 +35,13 @@ public class ProfileService : IProfileService
|
||||
|
||||
if (profile is null)
|
||||
{
|
||||
return Result<UserProfile>.Failure(CreateNotFoundError(userId));
|
||||
return Result.Failure<UserProfile>(CreateNotFoundError(userId));
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public async Task<Result> SetDescription(string description, Guid userId)
|
||||
public async Task<Result<Unit, Error>> SetDescription(string description, Guid userId)
|
||||
{
|
||||
_logger.LogInformation("Updating description for user {UserId}", userId);
|
||||
|
||||
@@ -56,7 +57,7 @@ public class ProfileService : IProfileService
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public async Task<Result> SetNewIcon(Guid userId, Guid iconId)
|
||||
public async Task<Result<Unit, Error>> SetNewIcon(Guid userId, Guid iconId)
|
||||
{
|
||||
_logger.LogInformation("Updating icon for user {UserId}", userId);
|
||||
|
||||
@@ -74,5 +75,5 @@ public class ProfileService : IProfileService
|
||||
}
|
||||
|
||||
private static Error CreateNotFoundError(Guid userId) =>
|
||||
new("Profile.UserNotFound", $"User with ID {userId} was not found.");
|
||||
Error.NotFound("Profile.UserNotFound", $"User with ID {userId} was not found.");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using Govor.Domain.Common;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.PushNotifications;
|
||||
|
||||
public interface IPushNotificationService
|
||||
{
|
||||
Task<Result> SendToUserAsync(Guid userId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null);
|
||||
Task<Result<Unit, Error>> SendToUserAsync(Guid userId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null);
|
||||
|
||||
Task<Result> SendToUsersAsync(IEnumerable<Guid> userIds, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null);
|
||||
Task<Result<Unit, Error>> SendToUsersAsync(IEnumerable<Guid> userIds, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null);
|
||||
|
||||
Task<Result> SendToSessionAsync(Guid sessionId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null);
|
||||
Task<Result<Unit, Error>> SendToSessionAsync(Guid sessionId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null);
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
using Govor.Domain.Common;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.PushNotifications;
|
||||
|
||||
public interface IPushTokenService
|
||||
{
|
||||
Task<Result> DeactivateTokenBySessionAsync(Guid sessionId);
|
||||
Task<Result> DeactivateAllTokensByUserIdAsync(Guid userId);
|
||||
Task<Result<List<string>>> GetStringsActiveTokensAsync(Guid userId);
|
||||
Task<Result<List<string>>> GetUsersStringsActiveTokensAsync(IEnumerable<Guid> userIds);
|
||||
Task<Result<string?>> GetActiveTokenBySessionAsync(Guid sessionId);
|
||||
Task<Result> RemoveTokensAsync(IEnumerable<string> tokens);
|
||||
Task<Result> AddOrUpdateTokenAsync(Guid userId, Guid sessionId, string token, string platform);
|
||||
Task<Result<Unit, Error>> DeactivateTokenBySessionAsync(Guid sessionId);
|
||||
Task<Result<Unit, Error>> DeactivateAllTokensByUserIdAsync(Guid userId);
|
||||
Task<Result<List<string>, Error>> GetStringsActiveTokensAsync(Guid userId);
|
||||
Task<Result<List<string>, Error>> GetUsersStringsActiveTokensAsync(IEnumerable<Guid> userIds);
|
||||
Task<Result<string?, Error>> GetActiveTokenBySessionAsync(Guid sessionId);
|
||||
Task<Result<Unit, Error>> RemoveTokensAsync(IEnumerable<string> tokens);
|
||||
Task<Result<Unit, Error>> AddOrUpdateTokenAsync(Guid userId, Guid sessionId, string token, string platform);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Govor.Application.Interfaces.PushNotifications.Models;
|
||||
using Govor.Application.PushNotifications.Providers;
|
||||
using Govor.Domain.Common;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SmartRes;
|
||||
|
||||
namespace Govor.Application.PushNotifications;
|
||||
|
||||
@@ -21,7 +22,7 @@ public class PushNotificationService : IPushNotificationService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<Result> SendToUserAsync(Guid userId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null)
|
||||
public async Task<Result<Unit, Error>> SendToUserAsync(Guid userId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null)
|
||||
{
|
||||
var resultTokens = await _tokenService.GetStringsActiveTokensAsync(userId);
|
||||
if (resultTokens.IsFailure)
|
||||
@@ -37,10 +38,10 @@ public class PushNotificationService : IPushNotificationService
|
||||
return await SendMulticastInternalAsync(tokens, title, body, channelId, tag, data, userId.ToString());
|
||||
}
|
||||
|
||||
public async Task<Result> SendToUsersAsync(IEnumerable<Guid> userIds, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null)
|
||||
public async Task<Result<Unit, Error>> SendToUsersAsync(IEnumerable<Guid> userIds, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null)
|
||||
{
|
||||
if (userIds == null || !userIds.Any())
|
||||
return Result.Failure(new Error("Push.InvalidArgs", "User IDs collection cannot be empty."));
|
||||
return Result.Failure(Error.Failure("Push.InvalidArgs", "User IDs collection cannot be empty."));
|
||||
|
||||
var tokensResult = await _tokenService.GetUsersStringsActiveTokensAsync(userIds);
|
||||
if (tokensResult.IsFailure)
|
||||
@@ -53,7 +54,7 @@ public class PushNotificationService : IPushNotificationService
|
||||
return await SendMulticastInternalAsync(tokens, title, body, channelId, tag, data, "bulk_request");
|
||||
}
|
||||
|
||||
public async Task<Result> SendToSessionAsync(Guid sessionId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null)
|
||||
public async Task<Result<Unit, Error>> SendToSessionAsync(Guid sessionId, string title, string body, string channelId, string tag = "", Dictionary<string, string>? data = null)
|
||||
{
|
||||
var tokenResult = await _tokenService.GetActiveTokenBySessionAsync(sessionId);
|
||||
if (tokenResult.IsFailure)
|
||||
@@ -83,7 +84,7 @@ public class PushNotificationService : IPushNotificationService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Result> SendMulticastInternalAsync(List<string> tokens, string title, string body, string channelId, string tag, Dictionary<string, string>? data, string targetInfo)
|
||||
private async Task<Result<Unit, Error>> SendMulticastInternalAsync(List<string> tokens, string title, string body, string channelId, string tag, Dictionary<string, string>? data, string targetInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -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.PushNotifications;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class PushTokenService : IPushTokenService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<Result> DeactivateTokenBySessionAsync(Guid sessionId)
|
||||
public async Task<Result<Unit, Error>> DeactivateTokenBySessionAsync(Guid sessionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -34,7 +35,7 @@ public class PushTokenService : IPushTokenService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> DeactivateAllTokensByUserIdAsync(Guid userId)
|
||||
public async Task<Result<Unit, Error>> DeactivateAllTokensByUserIdAsync(Guid userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -51,7 +52,7 @@ public class PushTokenService : IPushTokenService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<List<string>>> GetStringsActiveTokensAsync(Guid userId)
|
||||
public async Task<Result<List<string>, Error>> GetStringsActiveTokensAsync(Guid userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -66,11 +67,11 @@ public class PushTokenService : IPushTokenService
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch active push tokens for user {UserId}", userId);
|
||||
return Result<List<string>>.Failure(ex);
|
||||
return Result.Failure<List<string>>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<List<string>>> GetUsersStringsActiveTokensAsync(IEnumerable<Guid> userIds)
|
||||
public async Task<Result<List<string>, Error>> GetUsersStringsActiveTokensAsync(IEnumerable<Guid> userIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -85,11 +86,11 @@ public class PushTokenService : IPushTokenService
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch active push tokens for bulk users");
|
||||
return Result<List<string>>.Failure(ex);
|
||||
return Result.Failure<List<string>>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<string?>> GetActiveTokenBySessionAsync(Guid sessionId)
|
||||
public async Task<Result<string?, Error>> GetActiveTokenBySessionAsync(Guid sessionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -104,11 +105,11 @@ public class PushTokenService : IPushTokenService
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch active push token for session {SessionId}", sessionId);
|
||||
return Result<string?>.Failure(ex);
|
||||
return Result.Failure<string?>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> RemoveTokensAsync(IEnumerable<string> tokens)
|
||||
public async Task<Result<Unit, Error>> RemoveTokensAsync(IEnumerable<string> tokens)
|
||||
{
|
||||
if (tokens is null || !tokens.Any())
|
||||
return Result.Success();
|
||||
@@ -128,11 +129,11 @@ public class PushTokenService : IPushTokenService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> AddOrUpdateTokenAsync(Guid userId, Guid sessionId, string token, string platform)
|
||||
public async Task<Result<Unit, Error>> AddOrUpdateTokenAsync(Guid userId, Guid sessionId, string token, string platform)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
return new Error("PushToken.Empty", "Push token cannot be empty.");
|
||||
return Result<Unit, Error>.Failure(Error.Failure("PushToken.Empty", "Push token cannot be empty."));
|
||||
}
|
||||
|
||||
var existingToken = await _context.UserPushTokens
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user