Мелкий рефактор

This commit is contained in:
Халимов Рустам
2026-03-19 01:49:55 +03:00
parent feef15c796
commit 4410f2d98b
44 changed files with 129 additions and 64 deletions

View File

@@ -1,6 +0,0 @@
root = true
[*.cs]
csharp_prefer_braces = true:warning
csharp_style_namespace_declarations = file_scoped:warning
dotnet_diagnostic.IDE0011.severity = warning

View File

@@ -28,11 +28,11 @@ internal sealed class ResetUserPasswordCommandHandler : ICommandHandler<ResetUse
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return Result.Failure<SuccessResponse>(new Error("User.NotFound", "User not found"));
return Result.Failure<SuccessResponse>(IdentityErrors.UserNotFound);
}
if (string.IsNullOrWhiteSpace(request.NewPassword))
return Result.Failure<SuccessResponse>(new Error("InvalidPassword", "Password cannot be empty"));
return Result.Failure<SuccessResponse>(DomainErrors.InvalidPassword);
var hash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
user.ChangePassword(hash);

View File

@@ -29,7 +29,7 @@ internal sealed class GetUserDetailsQueryHandler : IQueryHandler<GetUserDetailsQ
var targetUser = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (targetUser == null)
{
return Result.Failure<AdminUserDetailsDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<AdminUserDetailsDto>(IdentityErrors.UserNotFound);
}
var messagesCount = await _chatsDbContext.Messages.CountAsync(m => m.SenderId == request.UserId, cancellationToken);

View File

@@ -1,3 +1,4 @@
using Host.Application.Stories;
using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Configuration;
using Knot.Shared.Kernel.Constants;
@@ -31,11 +32,11 @@ internal sealed class HandshakeFederationCommandHandler : ICommandHandler<Handsh
return Task.FromResult(Result.Failure<HandshakeResponse>(new Error(Errors.DisabledByAdmin, "Federation is disabled")));
if (string.IsNullOrEmpty(request.Request.Domain) || string.IsNullOrEmpty(request.Request.PublicKey))
return Task.FromResult(Result.Failure<HandshakeResponse>(new Error("Request.Invalid", "Domain and PublicKey are required")));
return Task.FromResult(Result.Failure<HandshakeResponse>(DomainErrors.RequestInvalid));
var allowedList = conf.AllowedDomains?.Select(d => d.Trim().ToLower()).ToList() ?? new System.Collections.Generic.List<string>();
if (!allowedList.Contains(request.Request.Domain.ToLowerInvariant()))
return Task.FromResult(Result.Failure<HandshakeResponse>(new Error("Unauthorized", "Domain is not in the whitelist")));
return Task.FromResult(Result.Failure<HandshakeResponse>(StoryErrors.Unauthorized));
using var rsa = RSA.Create(2048);
var selfPublicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey());

View File

@@ -1,3 +1,4 @@
using Host.Application.Stories;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -49,7 +50,7 @@ internal sealed class AddStoryReactionCommandHandler : ICommandHandler<AddStoryR
var story = await _context.Stories.FindAsync(new object[] { request.StoryId }, cancellationToken);
if (story == null)
{
return Result.Failure<MessageResponse>(new Error("Story.NotFound", "Story not found"));
return Result.Failure<MessageResponse>(StoryErrors.StoryNotFound);
}
var existing = await _context.StoryReactions

View File

@@ -1,3 +1,4 @@
using Host.Application.Stories;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -49,7 +50,7 @@ internal sealed class AddStoryReplyCommandHandler : ICommandHandler<AddStoryRepl
var story = await _context.Stories.FindAsync(new object[] { request.StoryId }, cancellationToken);
if (story == null)
{
return Result.Failure<MessageResponse>(new Error("Story.NotFound", "Story not found"));
return Result.Failure<MessageResponse>(StoryErrors.StoryNotFound);
}
var reply = new StoryReply(request.StoryId, request.UserId, request.Content);

View File

@@ -1,3 +1,4 @@
using Host.Application.Stories;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -24,11 +25,11 @@ internal sealed class DeleteStoryCommandHandler : ICommandHandler<DeleteStoryCom
var story = await _context.Stories.FindAsync(new object[] { request.StoryId }, cancellationToken);
if (story == null)
{
return Result.Failure<MessageResponse>(new Error("Story.NotFound", "Story not found"));
return Result.Failure<MessageResponse>(StoryErrors.StoryNotFound);
}
if (story.UserId != request.UserId)
{
return Result.Failure<MessageResponse>(new Error("Unauthorized", "Unauthorized"));
return Result.Failure<MessageResponse>(StoryErrors.Unauthorized);
}
_context.Stories.Remove(story);

View File

@@ -1,3 +1,4 @@
using Host.Application.Stories;
using System;
using System.Linq;
using System.Threading;
@@ -38,7 +39,7 @@ internal sealed class ViewStoryCommandHandler : ICommandHandler<ViewStoryCommand
if (story == null)
{
return Result.Failure<MessageResponse>(new Error("Story.NotFound", "Story not found"));
return Result.Failure<MessageResponse>(StoryErrors.StoryNotFound);
}
if (story.UserId == request.UserId)

View File

@@ -1,3 +1,4 @@
using Host.Application.Stories;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -33,11 +34,11 @@ internal sealed class GetStoryRepliesQueryHandler : IQueryHandler<GetStoryReplie
if (story == null)
{
return Result.Failure<List<StoryReplyDto>>(new Error("Story.NotFound", "Story not found"));
return Result.Failure<List<StoryReplyDto>>(StoryErrors.StoryNotFound);
}
if (story.UserId != request.UserId)
{
return Result.Failure<List<StoryReplyDto>>(new Error("Unauthorized", "Unauthorized"));
return Result.Failure<List<StoryReplyDto>>(StoryErrors.Unauthorized);
}
var replies = new List<StoryReplyDto>();

View File

@@ -1,3 +1,4 @@
using Host.Application.Stories;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -33,11 +34,11 @@ internal sealed class GetStoryViewersQueryHandler : IQueryHandler<GetStoryViewer
if (story == null)
{
return Result.Failure<List<StoryViewerDto>>(new Error("Story.NotFound", "Story not found"));
return Result.Failure<List<StoryViewerDto>>(StoryErrors.StoryNotFound);
}
if (story.UserId != request.UserId)
{
return Result.Failure<List<StoryViewerDto>>(new Error("Unauthorized", "Unauthorized"));
return Result.Failure<List<StoryViewerDto>>(StoryErrors.Unauthorized);
}
var viewerIds = story.Viewers.Select(v => v.UserId).ToList();

View File

@@ -39,7 +39,7 @@ internal sealed class GetUserStoriesQueryHandler : IQueryHandler<GetUserStoriesQ
var user = await _userRepository.GetByIdAsync(request.TargetUserId, cancellationToken);
if (user == null)
{
return Result.Failure<StoryGroupDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<StoryGroupDto>(IdentityErrors.UserNotFound);
}
var result = new StoryGroupDto(

View File

@@ -0,0 +1,9 @@
using Knot.Shared.Kernel;
namespace Host.Application.Stories;
public static class StoryErrors
{
public static readonly Error Unauthorized = new Error("Unauthorized", "Access denied");
public static readonly Error StoryNotFound = new Error("Story.NotFound", "Story not found");
}

View File

@@ -33,7 +33,7 @@ internal sealed class UploadGroupAvatarCommandHandler : ICommandHandler<UploadGr
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<Guid>(new Error("Chat.NotFound", "Chat not found or access denied"));
return Result.Failure<Guid>(ChatErrors.ChatNotFound);
}
var fileId = await _fileStorage.UploadFileAsync(request.FileStream, request.FileName, request.ContentType);
@@ -67,7 +67,7 @@ internal sealed class CropGroupAvatarCommandHandler : ICommandHandler<CropGroupA
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<Guid>(new Error("Chat.NotFound", "Chat not found or access denied"));
return Result.Failure<Guid>(ChatErrors.ChatNotFound);
}
string url;
@@ -117,7 +117,7 @@ internal sealed class RemoveGroupAvatarCommandHandler : ICommandHandler<RemoveGr
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<Guid>(new Error("Chat.NotFound", "Chat not found or access denied"));
return Result.Failure<Guid>(ChatErrors.ChatNotFound);
}
chat.UpdateAvatar(null);

View File

@@ -25,7 +25,7 @@ internal sealed class ClearChatCommandHandler : ICommandHandler<ClearChatCommand
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<MessageResponse>(new Error("Chat.NotFound", "Chat not found or access denied"));
return Result.Failure<MessageResponse>(ChatErrors.ChatNotFound);
}
// Currently a placeholder

View File

@@ -36,7 +36,7 @@ internal sealed class GetChatByIdQueryHandler : IQueryHandler<GetChatByIdQuery,
if (!chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<ChatDto?>(new Error("Chats.Forbidden", "Вы не являетесь участником этого чата."));
return Result.Failure<ChatDto?>(ChatErrors.ChatsForbidden);
}
var userIdsToFetch = new HashSet<Guid>();

View File

@@ -1,3 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -32,7 +33,7 @@ internal sealed class LeaveOrDeleteChatCommandHandler : ICommandHandler<LeaveOrD
if (!chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<SuccessResponse>(new Error("Unauthorized", "Access denied"));
return Result.Failure<SuccessResponse>(ChatErrors.Unauthorized);
}
if (chat.Type == ChatType.Group)

View File

@@ -28,7 +28,7 @@ internal sealed class AddMembersCommandHandler : ICommandHandler<AddMembersComma
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<Guid>(new Error("Chat.NotFound", "Chat not found or access denied"));
return Result.Failure<Guid>(ChatErrors.ChatNotFound);
}
foreach (var userId in request.UserIdsToAdd)
@@ -60,7 +60,7 @@ internal sealed class RemoveMemberCommandHandler : ICommandHandler<RemoveMemberC
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<Guid>(new Error("Chat.NotFound", "Chat not found or access denied"));
return Result.Failure<Guid>(ChatErrors.ChatNotFound);
}
chat.RemoveMember(request.UserIdToRemove);

View File

@@ -28,13 +28,13 @@ internal sealed class TogglePinCommandHandler : ICommandHandler<TogglePinCommand
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null)
{
return Result.Failure<TogglePinResponse>(new Error("Chat.NotFound", "Chat not found"));
return Result.Failure<TogglePinResponse>(ChatErrors.ChatNotFound);
}
var member = chat.Members.FirstOrDefault(m => m.UserId == request.UserId);
if (member == null)
{
return Result.Failure<TogglePinResponse>(new Error("Chat.NotFound", "Member not found"));
return Result.Failure<TogglePinResponse>(ChatErrors.ChatNotFound);
}
member.TogglePin();

View File

@@ -27,7 +27,7 @@ internal sealed class UpdateChatCommandHandler : ICommandHandler<UpdateChatComma
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<Guid>(new Error("Chat.NotFound", "Chat not found or access denied"));
return Result.Failure<Guid>(ChatErrors.ChatNotFound);
}
if (request.Name != null)

View File

@@ -32,7 +32,7 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<List<MessageDetailDto>>(new Error("Chats.Forbidden", "Вы не являетесь участником этого чата."));
return Result.Failure<List<MessageDetailDto>>(ChatErrors.ChatsForbidden);
}
DateTime? cursorDate = null;
@@ -41,7 +41,7 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
cursorDate = parsed.ToUniversalTime();
}
var messages = await _messageRepository.GetChatMessagesCursorAsync(request.ChatId, cursorDate, 100, cancellationToken);
var messages = await _messageRepository.GetChatMessagesCursorAsync(request.ChatId, cursorDate, ChatConstants.DefaultMessageQueryLimit, cancellationToken);
var result = new List<MessageDetailDto>();
var userIdsToFetch = new HashSet<Guid>();

View File

@@ -33,10 +33,10 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler<GetSharedMediaQ
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat == null || !chat.Members.Any(m => m.UserId == request.UserId))
{
return Result.Failure<List<SharedMediaDto>>(new Error("Chats.Forbidden", "Вы не являетесь участником этого чата."));
return Result.Failure<List<SharedMediaDto>>(ChatErrors.ChatsForbidden);
}
var messages = await _messageRepository.GetChatMessagesAsync(request.ChatId, 300, 0, cancellationToken);
var messages = await _messageRepository.GetChatMessagesAsync(request.ChatId, ChatConstants.MaxSharedMediaQueryLimit, 0, cancellationToken);
messages = messages.Where(m => !m.IsDeleted && !m.DeletedByUsers.Contains(request.UserId)).ToList();
var result = new List<SharedMediaDto>();

View File

@@ -57,7 +57,7 @@ public sealed class AddReactionCommandHandler : ICommandHandler<AddReactionComma
if (!success)
{
_logger.LogWarning("AddReaction: Message not found {MessageId}", request.MessageId);
return Result.Failure(new Error("Messages.NotFound", "Message not found."));
return Result.Failure(ChatErrors.MessagesNotFound);
}
await _unitOfWork.SaveChangesAsync(cancellationToken);

View File

@@ -44,13 +44,13 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
if (chat is null)
{
return Result.Failure<Guid>(new Error("Chats.NotFound", "Чат не найден."));
return Result.Failure<Guid>(ChatErrors.ChatsNotFound);
}
// 2. Проверяем, является ли отправитель участником
if (!chat.Members.Any(m => m.UserId == request.SenderId))
{
return Result.Failure<Guid>(new Error("Chats.Forbidden", "Вы не являетесь участником этого чата."));
return Result.Failure<Guid>(ChatErrors.ChatsForbidden);
}
// 3. Создаем сообщение

View File

@@ -1,4 +1,5 @@
using System;
using Knot.Modules.Chats.Domain;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -27,13 +28,13 @@ internal sealed class UploadFileCommandHandler : ICommandHandler<UploadFileComma
{
if (request.Length == 0)
{
return Result.Failure<UploadFileResponseDto>(new Error("File.Empty", "No file uploaded"));
return Result.Failure<UploadFileResponseDto>(ChatErrors.FileEmpty);
}
var maxMb = _settingsService.Current.MaxFileSizeMb;
if (request.Length > maxMb * 1024 * 1024)
{
return Result.Failure<UploadFileResponseDto>(new Error("File.TooLarge", $"File exceeds the maximum allowed size of {maxMb}MB."));
return Result.Failure<UploadFileResponseDto>(ChatErrors.FileTooLarge(maxMb));
}
var fileId = await _fileStorage.UploadFileAsync(request.FileStream, request.FileName, request.ContentType);

View File

@@ -1,3 +1,4 @@
using Knot.Modules.Chats.Domain;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -28,12 +29,12 @@ internal sealed class AnalyzeImportCommandHandler : ICommandHandler<AnalyzeImpor
{
if (request.FileStream == null || request.FileStream.Length == 0)
{
return Result.Failure<AnalyzeImportResponseDto>(new Error("File.Empty", "No file uploaded"));
return Result.Failure<AnalyzeImportResponseDto>(ChatErrors.FileEmpty);
}
if (!request.FileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
{
return Result.Failure<AnalyzeImportResponseDto>(new Error("File.InvalidExtension", "Must be a ZIP archive"));
return Result.Failure<AnalyzeImportResponseDto>(ChatErrors.FileInvalidExtension);
}
var token = Guid.NewGuid();

View File

@@ -56,12 +56,12 @@ internal sealed class ExecuteImportCommandHandler : ICommandHandler<ExecuteImpor
{
if (!TelegramImportState.TempZips.TryGetValue(request.Token, out var tempPath))
{
return Result.Failure<ExecuteImportResponseDto>(new Error("Import.Expired", "Session not found or expired"));
return Result.Failure<ExecuteImportResponseDto>(ChatErrors.ImportExpired);
}
if (!System.IO.File.Exists(tempPath))
{
return Result.Failure<ExecuteImportResponseDto>(new Error("Import.Missing", "ZIP file lost"));
return Result.Failure<ExecuteImportResponseDto>(ChatErrors.ImportMissing);
}
var myId = request.CurrentUserId;
@@ -95,7 +95,7 @@ internal sealed class ExecuteImportCommandHandler : ICommandHandler<ExecuteImpor
var res = await _sender.Send(command, cancellationToken);
if (res.IsFailure)
{
return Result.Failure<ExecuteImportResponseDto>(new Error("Import.CreateChatFailed", res.Error.Description ?? res.Error.Code));
return Result.Failure<ExecuteImportResponseDto>(ChatErrors.ImportCreateChatFailed(res.Error.Description ?? res.Error.Code));
}
chatId = res.Value;
@@ -107,7 +107,7 @@ internal sealed class ExecuteImportCommandHandler : ICommandHandler<ExecuteImpor
var res = await _sender.Send(command, cancellationToken);
if (res.IsFailure)
{
return Result.Failure<ExecuteImportResponseDto>(new Error("Import.CreateChatFailed", res.Error.Description ?? res.Error.Code));
return Result.Failure<ExecuteImportResponseDto>(ChatErrors.ImportCreateChatFailed(res.Error.Description ?? res.Error.Code));
}
chatId = res.Value;

View File

@@ -0,0 +1,9 @@
namespace Knot.Modules.Chats.Domain;
public static class ChatConstants
{
public const int DefaultMessageQueryLimit = 100;
public const int MaxSharedMediaQueryLimit = 300;
public const int SearchMessagesLimit = 50;
public const int MaxFileUploadSizeMb = 50;
}

View File

@@ -0,0 +1,19 @@
using Knot.Shared.Kernel;
namespace Knot.Modules.Chats.Domain;
public static class ChatErrors
{
public static readonly Error FileEmpty = new Error("File.Empty", "No file uploaded");
public static readonly Error FileInvalidExtension = new Error("File.InvalidExtension", "Must be a ZIP archive");
public static readonly Error ImportExpired = new Error("Import.Expired", "Session not found or expired");
public static readonly Error ImportMissing = new Error("Import.Missing", "ZIP file lost");
public static readonly Error ChatNotFound = new Error("Chat.NotFound", "Chat not found or access denied");
public static readonly Error ChatsForbidden = new Error("Chats.Forbidden", "Вы не являетесь участником этого чата.");
public static readonly Error MessagesNotFound = new Error("Messages.NotFound", "Message not found.");
public static readonly Error ChatsNotFound = new Error("Chats.NotFound", "Чат не найден.");
public static readonly Error Unauthorized = new Error("Chats.Unauthorized", "Access denied");
public static Error ImportCreateChatFailed(string msg) => new Error("Import.CreateChatFailed", msg);
public static Error FileTooLarge(int maxMb) => new Error("File.TooLarge", $"File exceeds the maximum allowed size of {maxMb}MB.");
}

View File

@@ -74,7 +74,7 @@ public sealed class MessageRepository : IMessageRepository
return await q.Where(m => m.Content != null && m.Content.Contains(query))
.OrderByDescending(m => m.CreatedAt)
.Take(50)
.Take(ChatConstants.SearchMessagesLimit)
.Include(m => m.Media)
.Include(m => m.ReadBy)
.Include(m => m.Reactions)

View File

@@ -1,3 +1,4 @@
using Knot.Modules.Identity.Domain;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -24,7 +25,7 @@ internal sealed class AcceptFriendRequestCommandHandler : ICommandHandler<Accept
var friendship = await _context.Friendships.FindAsync(new object[] { request.FriendshipId }, cancellationToken);
if (friendship == null || friendship.FriendId != request.UserId)
{
return Result.Failure<Guid>(new Error("Friends.NotFound", "Request not found"));
return Result.Failure<Guid>(IdentityErrors.FriendsNotFound);
}
friendship.Accept();

View File

@@ -1,3 +1,4 @@
using Knot.Modules.Identity.Domain;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -24,7 +25,7 @@ internal sealed class DeclineFriendRequestCommandHandler : ICommandHandler<Decli
var friendship = await _context.Friendships.FindAsync(new object[] { request.FriendshipId }, cancellationToken);
if (friendship == null || friendship.FriendId != request.UserId)
{
return Result.Failure(new Error("Friends.NotFound", "Request not found"));
return Result.Failure(IdentityErrors.FriendsNotFound);
}
friendship.Decline();

View File

@@ -1,3 +1,4 @@
using Knot.Modules.Identity.Domain;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -24,7 +25,7 @@ internal sealed class RemoveFriendCommandHandler : ICommandHandler<RemoveFriendC
var friendship = await _context.Friendships.FindAsync(new object[] { request.FriendshipId }, cancellationToken);
if (friendship == null || (friendship.UserId != request.UserId && friendship.FriendId != request.UserId))
{
return Result.Failure(new Error("Friends.NotFound", "Friendship not found"));
return Result.Failure(IdentityErrors.FriendsNotFound);
}
_context.Friendships.Remove(friendship);

View File

@@ -25,7 +25,7 @@ internal sealed class SendFriendRequestCommandHandler : ICommandHandler<SendFrie
{
if (request.UserId == request.FriendId)
{
return Result.Failure(new Error("Friends.Self", "Cannot add yourself"));
return Result.Failure(IdentityErrors.FriendsSelf);
}
var existing = await _context.Friendships
@@ -34,7 +34,7 @@ internal sealed class SendFriendRequestCommandHandler : ICommandHandler<SendFrie
if (existing != null)
{
return Result.Failure(new Error("Friends.Exists", "Friendship already exists"));
return Result.Failure(IdentityErrors.FriendsExists);
}
var friendship = Friendship.Create(request.UserId, request.FriendId);

View File

@@ -27,7 +27,7 @@ internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarComma
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return Result.Failure<UserProfileDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
}
string avatarUrl;

View File

@@ -22,7 +22,7 @@ internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarC
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return Result.Failure<UserProfileDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
}
user.UpdateAvatar(null);

View File

@@ -25,7 +25,7 @@ internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarC
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return Result.Failure<UserProfileDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
}
var fileId = await _fileStorage.UploadFileAsync(request.FileStream, request.FileName, request.ContentType);

View File

@@ -21,7 +21,7 @@ internal sealed class GetMeQueryHandler : IQueryHandler<GetMeQuery, AuthResponse
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return Result.Failure<AuthResponseDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<AuthResponseDto>(IdentityErrors.UserNotFound);
}
var response = new AuthResponseDto(

View File

@@ -20,7 +20,7 @@ internal sealed class GetUserQueryHandler : IQueryHandler<GetUserQuery, UserProf
var user = await _userRepository.GetByIdAsync(request.Id, cancellationToken);
if (user == null)
{
return Result.Failure<UserProfileDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
}
var dto = new UserProfileDto(

View File

@@ -28,7 +28,7 @@ public sealed class LoginUserCommandHandler : ICommandHandler<LoginUserCommand,
if (user is null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return Result.Failure<AuthResponseDto>(new Error("Identity.InvalidCredentials", "Неверное имя пользователя или пароль."));
return Result.Failure<AuthResponseDto>(IdentityErrors.IdentityInvalidCredentials);
}
string token = _tokenProvider.Generate(user);

View File

@@ -43,13 +43,13 @@ public sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCom
{
if (!_settings.Current.EnableRegistration)
{
return Result.Failure<AuthResponseDto>(new Error("Identity.RegistrationDisabled", "Registration is disabled by the administrator."));
return Result.Failure<AuthResponseDto>(IdentityErrors.IdentityRegistrationDisabled);
}
// 1. Проверка уникальности username
if (!await _userRepository.IsUsernameUniqueAsync(request.Username, cancellationToken))
{
return Result.Failure<AuthResponseDto>(new Error("Identity.UsernameNotUnique", "Это имя пользователя уже занято."));
return Result.Failure<AuthResponseDto>(IdentityErrors.IdentityUsernameNotUnique);
}
// 2. Хеширование пароля (здесь будет вызов сервиса, пока заглушка)

View File

@@ -22,7 +22,7 @@ internal sealed class UpdateProfileCommandHandler : ICommandHandler<UpdateProfil
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return Result.Failure<UserProfileDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
}
user.UpdateProfile(request.DisplayName ?? user.DisplayName, request.Bio, request.Birthday);

View File

@@ -22,7 +22,7 @@ internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSetti
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return Result.Failure<UserProfileDto>(new Error("User.NotFound", "User not found"));
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
}
user.UpdateSettings(request.HideStoryViews ?? user.HideStoryViews);

View File

@@ -0,0 +1,14 @@
using Knot.Shared.Kernel;
namespace Knot.Modules.Identity.Domain;
public static class IdentityErrors
{
public static readonly Error FriendsNotFound = new Error("Friends.NotFound", "Friendship not found");
public static readonly Error FriendsSelf = new Error("Friends.Self", "Cannot add yourself");
public static readonly Error FriendsExists = new Error("Friends.Exists", "Friendship already exists");
public static readonly Error UserNotFound = new Error("User.NotFound", "User not found");
public static readonly Error IdentityInvalidCredentials = new Error("Identity.InvalidCredentials", "Неверное имя пользователя или пароль.");
public static readonly Error IdentityRegistrationDisabled = new Error("Identity.RegistrationDisabled", "Registration is disabled by the administrator.");
public static readonly Error IdentityUsernameNotUnique = new Error("Identity.UsernameNotUnique", "Это имя пользователя уже занято.");
}

View File

@@ -0,0 +1,7 @@
namespace Knot.Shared.Kernel;
public static class DomainErrors
{
public static readonly Error InvalidPassword = new Error("Admin.InvalidPassword", "Пароль не должен быть пустым.");
public static readonly Error RequestInvalid = new Error("Request.Invalid", "Invalid federation request");
}