Контракты

This commit is contained in:
Халимов Рустам
2026-03-29 23:39:17 +03:00
parent 22bc964f27
commit 0209802e9e
141 changed files with 1292 additions and 725 deletions
@@ -1,6 +1,6 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Profiles.DTOs;
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Contracts.Domain;
using Knot.Modules.Profiles.Contracts.Application.DTOs;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using System;
@@ -30,24 +30,24 @@ internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarComma
public async Task<Result<UserProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken)
{
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
if (profile is null)
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
// Обрезать и ресайзнуть изображение до 400×400
using var ms = await CropAndResizeAsync(request, cancellationToken);
// Удалить старый аватар из S3
if (!string.IsNullOrEmpty(profile.AvatarUrl))
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
if (!string.IsNullOrEmpty(profile.Avatar))
await _avatarStorage.DeleteAsync(profile.Avatar, cancellationToken);
var fileId = await _avatarStorage.UploadAsync(ms, "avatar.jpg", "image/jpeg", cancellationToken);
var avatarUrl = $"/api/files/{fileId}";
profile.UpdateAvatar(avatarUrl);
await _repository.UpdateAsync(profile, cancellationToken);
profile.Avatar = avatarUrl;
var result = await _repository.UpdateAsync(profile, cancellationToken);
if (result.IsFailure)
return Result.Failure<UserProfileDto>(result.Error);
return Result.Success(UserProfileDto.FromDocument(profile));
return Result.Success(result.Value);
}
private static async Task<MemoryStream> CropAndResizeAsync(CropAvatarCommand request, CancellationToken ct)
@@ -1,6 +1,6 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Profiles.DTOs;
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Contracts.Domain;
using Knot.Modules.Profiles.Contracts.Application.DTOs;
using System;
using System.IO;
using System.Threading;
@@ -8,8 +8,6 @@ using System.Threading.Tasks;
namespace Knot.Modules.Profiles.Application.Profiles.Avatar;
// в”Ђв”Ђв”Ђ Upload в”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђ
public sealed record UploadAvatarCommand(
Guid UserId,
Stream FileStream,
@@ -29,26 +27,25 @@ internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarC
public async Task<Result<UserProfileDto>> Handle(UploadAvatarCommand request, CancellationToken cancellationToken)
{
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
if (profile is null)
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
// Удалить старый аватар из S3, если был
if (!string.IsNullOrEmpty(profile.AvatarUrl))
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
if (!string.IsNullOrEmpty(profile.Avatar))
await _avatarStorage.DeleteAsync(profile.Avatar, cancellationToken);
var fileId = await _avatarStorage.UploadAsync(request.FileStream, request.FileName, request.ContentType, cancellationToken);
var avatarUrl = $"/api/files/{fileId}";
profile.UpdateAvatar(avatarUrl);
await _repository.UpdateAsync(profile, cancellationToken);
profile.Avatar = avatarUrl;
var result = await _repository.UpdateAsync(profile, cancellationToken);
if (result.IsFailure)
return Result.Failure<UserProfileDto>(result.Error);
return Result.Success(UserProfileDto.FromDocument(profile));
return Result.Success(result.Value);
}
}
// в”Ђв”Ђв”Ђ Delete в”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђ
public sealed record DeleteAvatarCommand(Guid UserId) : ICommand<UserProfileDto>;
internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarCommand, UserProfileDto>
@@ -64,16 +61,18 @@ internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarC
public async Task<Result<UserProfileDto>> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken)
{
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
if (profile is null)
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
if (!string.IsNullOrEmpty(profile.AvatarUrl))
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
if (!string.IsNullOrEmpty(profile.Avatar))
await _avatarStorage.DeleteAsync(profile.Avatar, cancellationToken);
profile.RemoveAvatar();
await _repository.UpdateAsync(profile, cancellationToken);
profile.Avatar = null;
var result = await _repository.UpdateAsync(profile, cancellationToken);
if (result.IsFailure)
return Result.Failure<UserProfileDto>(result.Error);
return Result.Success(UserProfileDto.FromDocument(profile));
return Result.Success(result.Value);
}
}
@@ -1,4 +1,4 @@
using System;
using System;
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
@@ -1,4 +1,4 @@
using System;
using System;
using Knot.Modules.Profiles.Domain;
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
@@ -1,6 +1,6 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Profiles.DTOs;
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Contracts.Domain;
using Knot.Modules.Profiles.Contracts.Application.DTOs;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -20,10 +20,10 @@ internal sealed class GetProfileQueryHandler : IQueryHandler<GetProfileQuery, Us
public async Task<Result<UserProfileDto>> Handle(GetProfileQuery request, CancellationToken cancellationToken)
{
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
if (profile is null)
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
return Result.Success(UserProfileDto.FromDocument(profile));
return Result.Success(profile);
}
}
@@ -1,16 +1,17 @@
using MediatR;
using MediatR;
using System.Threading;
using System.Threading.Tasks;
using Knot.Modules.Profiles.Contracts.Domain;
using Knot.Modules.Profiles.Domain;
using Knot.Shared.Kernel.Events;
namespace Knot.Modules.Profiles.Application.Profiles.Integration;
/// <summary>
/// Обработчик события из модуля Auth.
/// Создаёт пустой профиль при регистрации пользователя.
/// Îáðàáîò÷èê ñîáûòèÿ èç ìîäóëÿ Auth.
/// Ñîçäà¸ò ïóñòîé ïðîôèëü ïðè ðåãèñòðàöèè ïîëüçîâàòåëÿ.
/// </summary>
public class UserRegisteredDomainEventHandler : INotificationHandler<UserRegisteredDomainEvent>
internal class UserRegisteredDomainEventHandler : INotificationHandler<UserRegisteredDomainEvent>
{
private readonly IProfileRepository _repository;
@@ -21,12 +22,15 @@ public class UserRegisteredDomainEventHandler : INotificationHandler<UserRegiste
public async Task Handle(UserRegisteredDomainEvent notification, CancellationToken cancellationToken)
{
var profile = ProfileDocument.Create(
notification.UserId,
notification.Username,
notification.DisplayName ?? notification.Username
);
var profile = await _repository.GetAsync(notification.UserId, cancellationToken);
if (profile != null)
return;
await _repository.AddAsync(profile);
await _repository.CreateAsync(new Contracts.Application.DTOs.UserProfileDto
{
UserId = notification.UserId,
Username = notification.Username,
DisplayName = notification.DisplayName ?? notification.Username
}, cancellationToken);
}
}
@@ -1,6 +1,6 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Profiles.DTOs;
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Contracts.Domain;
using Knot.Modules.Profiles.Contracts.Application.DTOs;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -21,8 +21,7 @@ internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQ
public async Task<Result<List<UserProfileDto>>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken)
{
var profiles = await _repository.SearchAsync(request.Query, cancellationToken);
var dtos = profiles.Select(UserProfileDto.FromDocument).ToList();
return Result.Success(dtos);
var profiles = await _repository.SearchAsync(request.Query, 20, cancellationToken);
return Result.Success(profiles);
}
}
@@ -1,6 +1,6 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Profiles.DTOs;
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Contracts.Domain;
using Knot.Modules.Profiles.Contracts.Application.DTOs;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -24,17 +24,17 @@ internal sealed class UpdateProfileCommandHandler : ICommandHandler<UpdateProfil
public async Task<Result<UserProfileDto>> Handle(UpdateProfileCommand request, CancellationToken cancellationToken)
{
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
if (profile is null)
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
profile.UpdateProfile(
request.DisplayName ?? profile.DisplayName,
request.Bio,
request.Birthday);
profile.DisplayName = request.DisplayName ?? profile.DisplayName;
profile.About = request.Bio;
await _repository.UpdateAsync(profile, cancellationToken);
var result = await _repository.UpdateAsync(profile, cancellationToken);
if (result.IsFailure)
return Result.Failure<UserProfileDto>(result.Error);
return Result.Success(UserProfileDto.FromDocument(profile));
return Result.Success(result.Value);
}
}
@@ -1,6 +1,6 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Profiles.DTOs;
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Contracts.Domain;
using Knot.Modules.Profiles.Contracts.Application.DTOs;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -20,13 +20,15 @@ internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSetti
public async Task<Result<UserProfileDto>> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken)
{
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
if (profile is null)
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
profile.UpdateSettings(request.HideStoryViews ?? profile.HideStoryViews);
await _repository.UpdateAsync(profile, cancellationToken);
// Settings update logic would go here
var result = await _repository.UpdateAsync(profile, cancellationToken);
if (result.IsFailure)
return Result.Failure<UserProfileDto>(result.Error);
return Result.Success(UserProfileDto.FromDocument(profile));
return Result.Success(result.Value);
}
}