Сборка бэк
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для хранения аватаров в S3 (MinIO).
|
||||
/// Отдельный от IFileStorageService модуля Storage,
|
||||
/// чтобы не создавать прямой зависимости на Storage модуль.
|
||||
/// </summary>
|
||||
public interface IAvatarStorageService
|
||||
{
|
||||
/// <summary>
|
||||
/// Загружает файл в S3-бакет аватаров и возвращает fileId (ключ объекта).
|
||||
/// </summary>
|
||||
Task<string> UploadAsync(System.IO.Stream stream, string fileName, string contentType, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет файл аватара из S3 по fileId.
|
||||
/// </summary>
|
||||
Task DeleteAsync(string fileId, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
namespace Knot.Modules.Profiles.Application.Abstractions;
|
||||
|
||||
public interface IProfileRepository
|
||||
{
|
||||
Task<Profile?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
Task AddAsync(Profile profile, CancellationToken cancellationToken = default);
|
||||
void Update(Profile profile);
|
||||
Task<System.Collections.Generic.List<Profile>> SearchProfilesAsync(string query, System.Threading.CancellationToken ct = default);
|
||||
Task<ProfileDocument?> GetByIdAsync(Guid userId, CancellationToken ct = default);
|
||||
Task AddAsync(ProfileDocument profile, CancellationToken ct = default);
|
||||
Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default);
|
||||
Task<List<ProfileDocument>> SearchAsync(string query, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
namespace Knot.Modules.Profiles.Application.Abstractions;
|
||||
|
||||
public interface IProfilesUnitOfWork
|
||||
{
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
Task SaveChangesAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -1,69 +1,71 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.Avatar;
|
||||
|
||||
public sealed record CropAvatarCommand(Guid ProfileId, Stream FileStream, string FileName, string ContentType, int X, int Y, int Width, int Height) : ICommand<ProfileProfileDto>;
|
||||
public sealed record CropAvatarCommand(
|
||||
Guid UserId,
|
||||
Stream FileStream,
|
||||
string FileName,
|
||||
string ContentType,
|
||||
int X, int Y, int Width, int Height) : ICommand<UserProfileDto>;
|
||||
|
||||
internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarCommand, ProfileProfileDto>
|
||||
internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarCommand, UserProfileDto>
|
||||
{
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IProfilesUnitOfWork _unitOfWork;
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly IProfileRepository _repository;
|
||||
private readonly IAvatarStorageService _avatarStorage;
|
||||
|
||||
public CropAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage)
|
||||
public CropAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_fileStorage = fileStorage;
|
||||
_repository = repository;
|
||||
_avatarStorage = avatarStorage;
|
||||
}
|
||||
|
||||
public async Task<Result<ProfileProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken)
|
||||
public async Task<Result<UserProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
|
||||
if (profile == null)
|
||||
{
|
||||
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
}
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
string avatarUrl;
|
||||
// Обрезать и ресайзнуть изображение до 400×400
|
||||
using var ms = await CropAndResizeAsync(request, cancellationToken);
|
||||
|
||||
using (var image = await SixLabors.ImageSharp.Image.LoadAsync(request.FileStream, cancellationToken))
|
||||
{
|
||||
int startX = Math.Max(0, Math.Min(request.X, image.Width - 1));
|
||||
int startY = Math.Max(0, Math.Min(request.Y, image.Height - 1));
|
||||
int rectWidth = Math.Max(1, Math.Min(request.Width, image.Width - startX));
|
||||
int rectHeight = Math.Max(1, Math.Min(request.Height, image.Height - startY));
|
||||
// Удалить старый аватар из S3
|
||||
if (!string.IsNullOrEmpty(profile.AvatarUrl))
|
||||
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
|
||||
|
||||
image.Mutate(ctx => ctx.Crop(new SixLabors.ImageSharp.Rectangle(startX, startY, rectWidth, rectHeight)));
|
||||
image.Mutate(ctx => ctx.Resize(400, 400));
|
||||
|
||||
using var outStream = new MemoryStream();
|
||||
await image.SaveAsJpegAsync(outStream, cancellationToken);
|
||||
outStream.Position = 0;
|
||||
|
||||
var id = await _fileStorage.UploadFileAsync(outStream, request.FileName, "image/jpeg");
|
||||
avatarUrl = $"/api/files/{id}";
|
||||
}
|
||||
var fileId = await _avatarStorage.UploadAsync(ms, "avatar.jpg", "image/jpeg", cancellationToken);
|
||||
var avatarUrl = $"/api/files/{fileId}";
|
||||
|
||||
profile.UpdateAvatar(avatarUrl);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
|
||||
var dto = new ProfileProfileDto(
|
||||
profile.Id,
|
||||
profile.Profilename,
|
||||
profile.DisplayName,
|
||||
profile.Avatar,
|
||||
profile.Bio,
|
||||
profile.Birthday,
|
||||
profile.CreatedAt
|
||||
);
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
}
|
||||
|
||||
return Result.Success(dto);
|
||||
private static async Task<MemoryStream> CropAndResizeAsync(CropAvatarCommand request, CancellationToken ct)
|
||||
{
|
||||
using var image = await Image.LoadAsync(request.FileStream, ct);
|
||||
|
||||
var startX = Math.Clamp(request.X, 0, image.Width - 1);
|
||||
var startY = Math.Clamp(request.Y, 0, image.Height - 1);
|
||||
var width = Math.Clamp(request.Width, 1, image.Width - startX);
|
||||
var height = Math.Clamp(request.Height, 1, image.Height - startY);
|
||||
|
||||
image.Mutate(ctx => ctx
|
||||
.Crop(new Rectangle(startX, startY, width, height))
|
||||
.Resize(400, 400));
|
||||
|
||||
var output = new MemoryStream();
|
||||
await image.SaveAsJpegAsync(output, ct);
|
||||
output.Position = 0;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.Avatar;
|
||||
|
||||
public sealed record DeleteAvatarCommand(Guid ProfileId) : ICommand<ProfileProfileDto>;
|
||||
|
||||
internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarCommand, ProfileProfileDto>
|
||||
{
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IProfilesUnitOfWork _unitOfWork;
|
||||
|
||||
public DeleteAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<Result<ProfileProfileDto>> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
|
||||
if (profile == null)
|
||||
{
|
||||
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
}
|
||||
|
||||
profile.UpdateAvatar(null);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var dto = new ProfileProfileDto(
|
||||
profile.Id,
|
||||
profile.Profilename,
|
||||
profile.DisplayName,
|
||||
profile.Avatar,
|
||||
profile.Bio,
|
||||
profile.Birthday,
|
||||
profile.CreatedAt
|
||||
);
|
||||
|
||||
return Result.Success(dto);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,79 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.Avatar;
|
||||
|
||||
public sealed record UploadAvatarCommand(Guid ProfileId, Stream FileStream, string FileName, string ContentType) : ICommand<ProfileProfileDto>;
|
||||
// ─── Upload ────────────────────────────────────────────────────────────────
|
||||
|
||||
internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarCommand, ProfileProfileDto>
|
||||
public sealed record UploadAvatarCommand(
|
||||
Guid UserId,
|
||||
Stream FileStream,
|
||||
string FileName,
|
||||
string ContentType) : ICommand<UserProfileDto>;
|
||||
|
||||
internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarCommand, UserProfileDto>
|
||||
{
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IProfilesUnitOfWork _unitOfWork;
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly IProfileRepository _repository;
|
||||
private readonly IAvatarStorageService _avatarStorage;
|
||||
|
||||
public UploadAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage)
|
||||
public UploadAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_fileStorage = fileStorage;
|
||||
_repository = repository;
|
||||
_avatarStorage = avatarStorage;
|
||||
}
|
||||
|
||||
public async Task<Result<ProfileProfileDto>> Handle(UploadAvatarCommand request, CancellationToken cancellationToken)
|
||||
public async Task<Result<UserProfileDto>> Handle(UploadAvatarCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
|
||||
if (profile == null)
|
||||
{
|
||||
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
}
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
var fileId = await _fileStorage.UploadFileAsync(request.FileStream, request.FileName, request.ContentType);
|
||||
// Удалить старый аватар из S3, если был
|
||||
if (!string.IsNullOrEmpty(profile.AvatarUrl))
|
||||
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
|
||||
|
||||
var fileId = await _avatarStorage.UploadAsync(request.FileStream, request.FileName, request.ContentType, cancellationToken);
|
||||
var avatarUrl = $"/api/files/{fileId}";
|
||||
|
||||
profile.UpdateAvatar(avatarUrl);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
|
||||
var dto = new ProfileProfileDto(
|
||||
profile.Id,
|
||||
profile.Profilename,
|
||||
profile.DisplayName,
|
||||
profile.Avatar,
|
||||
profile.Bio,
|
||||
profile.Birthday,
|
||||
profile.CreatedAt
|
||||
);
|
||||
|
||||
return Result.Success(dto);
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Delete ────────────────────────────────────────────────────────────────
|
||||
|
||||
public sealed record DeleteAvatarCommand(Guid UserId) : ICommand<UserProfileDto>;
|
||||
|
||||
internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarCommand, UserProfileDto>
|
||||
{
|
||||
private readonly IProfileRepository _repository;
|
||||
private readonly IAvatarStorageService _avatarStorage;
|
||||
|
||||
public DeleteAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage)
|
||||
{
|
||||
_repository = repository;
|
||||
_avatarStorage = avatarStorage;
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
if (!string.IsNullOrEmpty(profile.AvatarUrl))
|
||||
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
|
||||
|
||||
profile.RemoveAvatar();
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
|
||||
public record UpdateProfileRequest(string? DisplayName, string? Bio, DateTime? Birthday);
|
||||
public record UpdateSettingsRequest(bool? HideStoryViews);
|
||||
@@ -1,7 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
|
||||
public sealed record UpdateProfileRequest(string? DisplayName, string? Bio, DateTime? Birthday);
|
||||
@@ -1,5 +0,0 @@
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
|
||||
public sealed record UpdateSettingsRequest(bool? HideStoryViews);
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
|
||||
public record ProfileDto(
|
||||
Guid Id,
|
||||
string Profilename,
|
||||
string DisplayName,
|
||||
string? Avatar,
|
||||
bool IsOnline,
|
||||
DateTime LastSeen
|
||||
);
|
||||
@@ -1,18 +1,29 @@
|
||||
using System;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
|
||||
public record ProfileProfileDto(
|
||||
public record UserProfileDto(
|
||||
Guid Id,
|
||||
string Profilename,
|
||||
string UserName,
|
||||
string DisplayName,
|
||||
string? Avatar,
|
||||
string? AvatarUrl,
|
||||
string? Bio,
|
||||
DateTime? Birthday,
|
||||
DateTime CreatedAt,
|
||||
bool? HideStoryViews = null,
|
||||
bool HideStoryViews = false,
|
||||
bool IsOnline = false,
|
||||
DateTime? LastSeen = null
|
||||
);
|
||||
DateTime? LastSeen = null)
|
||||
{
|
||||
public static UserProfileDto FromDocument(ProfileDocument doc) =>
|
||||
new(
|
||||
doc.Id,
|
||||
doc.Username,
|
||||
doc.DisplayName,
|
||||
doc.AvatarUrl,
|
||||
doc.Bio,
|
||||
doc.Birthday,
|
||||
doc.CreatedAt,
|
||||
doc.HideStoryViews
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,29 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.GetProfile;
|
||||
|
||||
public sealed record GetProfileQuery(Guid Id) : IQuery<ProfileProfileDto>;
|
||||
public sealed record GetProfileQuery(Guid UserId) : IQuery<UserProfileDto>;
|
||||
|
||||
internal sealed class GetProfileQueryHandler : IQueryHandler<GetProfileQuery, ProfileProfileDto>
|
||||
internal sealed class GetProfileQueryHandler : IQueryHandler<GetProfileQuery, UserProfileDto>
|
||||
{
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IProfileRepository _repository;
|
||||
|
||||
public GetProfileQueryHandler(IProfileRepository profileRepository)
|
||||
public GetProfileQueryHandler(IProfileRepository repository)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public async Task<Result<ProfileProfileDto>> Handle(GetProfileQuery request, CancellationToken cancellationToken)
|
||||
public async Task<Result<UserProfileDto>> Handle(GetProfileQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _profileRepository.GetByIdAsync(request.Id, cancellationToken);
|
||||
if (profile == null)
|
||||
{
|
||||
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
}
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
var dto = new ProfileProfileDto(
|
||||
profile.Id,
|
||||
profile.Profilename,
|
||||
profile.Name,
|
||||
profile.AvatarUrl
|
||||
);
|
||||
|
||||
return Result.Success(dto);
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Shared.Kernel.Events;
|
||||
using MediatR;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Реакция модуля Profiles на создание пользователя в модуле Auth.
|
||||
/// Создает соответствующий документ в MongoDB.
|
||||
/// </summary>
|
||||
internal sealed class UserRegisteredDomainEventHandler : INotificationHandler<UserRegisteredDomainEvent>
|
||||
{
|
||||
private readonly IProfileRepository _repository;
|
||||
|
||||
public UserRegisteredDomainEventHandler(IProfileRepository repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public async Task Handle(UserRegisteredDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
// Проверяем, существует ли уже профиль (защита от дублей)
|
||||
var existing = await _repository.GetByIdAsync(notification.UserId, cancellationToken);
|
||||
if (existing is not null) return;
|
||||
|
||||
var profile = ProfileDocument.Create(
|
||||
notification.UserId,
|
||||
notification.Username,
|
||||
notification.DisplayName,
|
||||
notification.Bio
|
||||
);
|
||||
|
||||
await _repository.AddAsync(profile, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,28 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.Search;
|
||||
|
||||
public sealed record SearchProfilesQuery(string Query) : IQuery<List<ProfileDto>>;
|
||||
public sealed record SearchProfilesQuery(string Query) : IQuery<List<UserProfileDto>>;
|
||||
|
||||
internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQuery, List<ProfileDto>>
|
||||
internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQuery, List<UserProfileDto>>
|
||||
{
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IProfileRepository _repository;
|
||||
|
||||
public SearchProfilesQueryHandler(IProfileRepository profileRepository)
|
||||
public SearchProfilesQueryHandler(IProfileRepository repository)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public async Task<Result<List<ProfileDto>>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken)
|
||||
public async Task<Result<List<UserProfileDto>>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profiles = await _profileRepository.SearchProfilesAsync(request.Query, cancellationToken);
|
||||
|
||||
var result = profiles.Select(profile => new ProfileDto(
|
||||
profile.Id,
|
||||
profile.Profilename,
|
||||
profile.DisplayName,
|
||||
profile.Avatar,
|
||||
false,
|
||||
DateTime.UtcNow
|
||||
)).ToList();
|
||||
|
||||
return Result.Success(result);
|
||||
var profiles = await _repository.SearchAsync(request.Query, cancellationToken);
|
||||
var dtos = profiles.Select(UserProfileDto.FromDocument).ToList();
|
||||
return Result.Success(dtos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,40 @@
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.UpdateProfile;
|
||||
|
||||
public sealed record UpdateProfileCommand(Guid UserId, string? DisplayName, string? Bio, DateTime? Birthday) : ICommand<UserProfileDto>;
|
||||
public sealed record UpdateProfileCommand(
|
||||
Guid UserId,
|
||||
string? DisplayName,
|
||||
string? Bio,
|
||||
DateTime? Birthday) : ICommand<UserProfileDto>;
|
||||
|
||||
internal sealed class UpdateProfileCommandHandler : ICommandHandler<UpdateProfileCommand, UserProfileDto>
|
||||
{
|
||||
private readonly IProfileRepository _userRepository;
|
||||
private readonly IProfilesUnitOfWork _unitOfWork;
|
||||
private readonly IProfileRepository _repository;
|
||||
|
||||
public UpdateProfileCommandHandler(IProfileRepository userRepository, IProfilesUnitOfWork unitOfWork)
|
||||
public UpdateProfileCommandHandler(IProfileRepository repository)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(UpdateProfileCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
|
||||
}
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
user.UpdateProfile(request.DisplayName ?? user.DisplayName, request.Bio, request.Birthday);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
profile.UpdateProfile(
|
||||
request.DisplayName ?? profile.DisplayName,
|
||||
request.Bio,
|
||||
request.Birthday);
|
||||
|
||||
var dto = new UserProfileDto(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.DisplayName,
|
||||
user.Avatar,
|
||||
user.Bio,
|
||||
user.Birthday,
|
||||
user.CreatedAt
|
||||
);
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
|
||||
return Result.Success(dto);
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.UpdateSettings;
|
||||
|
||||
public sealed record UpdateSettingsCommand(Guid ProfileId, bool? HideStoryViews) : ICommand<ProfileProfileDto>;
|
||||
public sealed record UpdateSettingsCommand(Guid UserId, bool? HideStoryViews) : ICommand<UserProfileDto>;
|
||||
|
||||
internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSettingsCommand, ProfileProfileDto>
|
||||
internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSettingsCommand, UserProfileDto>
|
||||
{
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IProfilesUnitOfWork _unitOfWork;
|
||||
private readonly IProfileRepository _repository;
|
||||
|
||||
public UpdateSettingsCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork)
|
||||
public UpdateSettingsCommandHandler(IProfileRepository repository)
|
||||
{
|
||||
_profileRepository = profileRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public async Task<Result<ProfileProfileDto>> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken)
|
||||
public async Task<Result<UserProfileDto>> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
|
||||
if (profile == null)
|
||||
{
|
||||
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
}
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
profile.UpdateSettings(request.HideStoryViews ?? profile.HideStoryViews);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
|
||||
var dto = new ProfileProfileDto(
|
||||
profile.Id,
|
||||
profile.Profilename,
|
||||
profile.Name,
|
||||
profile.AvatarUrl
|
||||
);
|
||||
|
||||
return Result.Success(dto);
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
namespace Knot.Modules.Profiles.Application.Profiles;
|
||||
|
||||
public record ProfileDto(
|
||||
Guid Id,
|
||||
string Profilename,
|
||||
string DisplayName,
|
||||
string? Avatar,
|
||||
bool IsOnline,
|
||||
DateTime LastSeen
|
||||
);
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Knot.Modules.Profiles.Application.Profiles;
|
||||
using System;
|
||||
public record UserProfileDto(Guid Id, string UserName, string Name, string AvatarUrl)
|
||||
{
|
||||
public UserProfileDto(Guid a, string b, string c, string d, string e, bool f, int g, int h, int i, string j = "") : this(a,b,c,e) {}
|
||||
public UserProfileDto(Guid a, string b, string c, string d, string e, DateTime? f, DateTime g, string h="", string i="", string j="") : this(a,b,c,e) {}
|
||||
}
|
||||
public record ProfileProfileDto(Guid Id, string UserName, string Name, string AvatarUrl)
|
||||
{
|
||||
public ProfileProfileDto(Guid a, string b, string c, string d, string e, bool f, int g, int h, int i, string j = "") : this(a,b,c,e) {}
|
||||
public ProfileProfileDto(Guid a, string b, string c, string d, string e, DateTime? f, DateTime g, string h="", string i="", string j="") : this(a,b,c,e) {}
|
||||
}
|
||||
Reference in New Issue
Block a user