Перепиливание под чистый DDD

This commit is contained in:
Халимов Рустам
2026-03-22 23:59:33 +03:00
parent 5da1a2f45d
commit 6e532b021d
302 changed files with 3595 additions and 3679 deletions
@@ -0,0 +1,15 @@
using System;
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);
}
@@ -0,0 +1,14 @@
using Knot.Modules.Profiles.Domain;
using Microsoft.EntityFrameworkCore;
using System.Threading;
using System.Threading.Tasks;
using Knot.Modules.Profiles.Application.Abstractions;
namespace Knot.Modules.Profiles.Application.Abstractions;
public interface IProfilesDbContext
{
DbSet<Profile> Profiles { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,11 @@
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);
}
@@ -0,0 +1,69 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Shared.Kernel.Storage;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
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>;
internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarCommand, ProfileProfileDto>
{
private readonly IProfileRepository _profileRepository;
private readonly IProfilesUnitOfWork _unitOfWork;
private readonly IFileStorageService _fileStorage;
public CropAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage)
{
_profileRepository = profileRepository;
_unitOfWork = unitOfWork;
_fileStorage = fileStorage;
}
public async Task<Result<ProfileProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken)
{
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
if (profile == null)
{
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
}
string avatarUrl;
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));
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}";
}
profile.UpdateAvatar(avatarUrl);
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);
}
}
@@ -0,0 +1,44 @@
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);
}
}
@@ -0,0 +1,50 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Shared.Kernel.Storage;
using Knot.Modules.Profiles.Application.Abstractions;
namespace Knot.Modules.Profiles.Application.Profiles.Avatar;
public sealed record UploadAvatarCommand(Guid ProfileId, Stream FileStream, string FileName, string ContentType) : ICommand<ProfileProfileDto>;
internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarCommand, ProfileProfileDto>
{
private readonly IProfileRepository _profileRepository;
private readonly IProfilesUnitOfWork _unitOfWork;
private readonly IFileStorageService _fileStorage;
public UploadAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage)
{
_profileRepository = profileRepository;
_unitOfWork = unitOfWork;
_fileStorage = fileStorage;
}
public async Task<Result<ProfileProfileDto>> Handle(UploadAvatarCommand request, CancellationToken cancellationToken)
{
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
if (profile == null)
{
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
}
var fileId = await _fileStorage.UploadFileAsync(request.FileStream, request.FileName, request.ContentType);
var avatarUrl = $"/api/files/{fileId}";
profile.UpdateAvatar(avatarUrl);
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);
}
}
@@ -0,0 +1,7 @@
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);
@@ -0,0 +1,5 @@
using Knot.Modules.Profiles.Application.Abstractions;
using Knot.Modules.Profiles.Domain;
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
public sealed record UpdateSettingsRequest(bool? HideStoryViews);
@@ -0,0 +1,14 @@
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
);
@@ -0,0 +1,18 @@
using System;
using Knot.Modules.Profiles.Application.Abstractions;
using Knot.Modules.Profiles.Domain;
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
public record ProfileProfileDto(
Guid Id,
string Profilename,
string DisplayName,
string? Avatar,
string? Bio,
DateTime? Birthday,
DateTime CreatedAt,
bool? HideStoryViews = null,
bool IsOnline = false,
DateTime? LastSeen = null
);
@@ -0,0 +1,36 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Abstractions;
namespace Knot.Modules.Profiles.Application.Profiles.GetProfile;
public sealed record GetProfileQuery(Guid Id) : IQuery<ProfileProfileDto>;
internal sealed class GetProfileQueryHandler : IQueryHandler<GetProfileQuery, ProfileProfileDto>
{
private readonly IProfileRepository _profileRepository;
public GetProfileQueryHandler(IProfileRepository profileRepository)
{
_profileRepository = profileRepository;
}
public async Task<Result<ProfileProfileDto>> Handle(GetProfileQuery request, CancellationToken cancellationToken)
{
var profile = await _profileRepository.GetByIdAsync(request.Id, cancellationToken);
if (profile == null)
{
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
}
var dto = new ProfileProfileDto(
profile.Id,
profile.Profilename,
profile.Name,
profile.AvatarUrl
);
return Result.Success(dto);
}
}
@@ -0,0 +1,34 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Abstractions;
namespace Knot.Modules.Profiles.Application.Profiles.Search;
public sealed record SearchProfilesQuery(string Query) : IQuery<List<ProfileDto>>;
internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQuery, List<ProfileDto>>
{
private readonly IProfileRepository _profileRepository;
public SearchProfilesQueryHandler(IProfileRepository profileRepository)
{
_profileRepository = profileRepository;
}
public async Task<Result<List<ProfileDto>>> 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);
}
}
@@ -0,0 +1,43 @@
using Knot.Shared.Kernel;
namespace Knot.Modules.Profiles.Application.Profiles.UpdateProfile;
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;
public UpdateProfileCommandHandler(IProfileRepository userRepository, IProfilesUnitOfWork unitOfWork)
{
_userRepository = userRepository;
_unitOfWork = unitOfWork;
}
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);
}
user.UpdateProfile(request.DisplayName ?? user.DisplayName, request.Bio, request.Birthday);
await _unitOfWork.SaveChangesAsync(cancellationToken);
var dto = new UserProfileDto(
user.Id,
user.Username,
user.DisplayName,
user.Avatar,
user.Bio,
user.Birthday,
user.CreatedAt
);
return Result.Success(dto);
}
}
@@ -0,0 +1,41 @@
using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Abstractions;
namespace Knot.Modules.Profiles.Application.Profiles.UpdateSettings;
public sealed record UpdateSettingsCommand(Guid ProfileId, bool? HideStoryViews) : ICommand<ProfileProfileDto>;
internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSettingsCommand, ProfileProfileDto>
{
private readonly IProfileRepository _profileRepository;
private readonly IProfilesUnitOfWork _unitOfWork;
public UpdateSettingsCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork)
{
_profileRepository = profileRepository;
_unitOfWork = unitOfWork;
}
public async Task<Result<ProfileProfileDto>> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken)
{
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
if (profile == null)
{
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
}
profile.UpdateSettings(request.HideStoryViews ?? profile.HideStoryViews);
await _unitOfWork.SaveChangesAsync(cancellationToken);
var dto = new ProfileProfileDto(
profile.Id,
profile.Profilename,
profile.Name,
profile.AvatarUrl
);
return Result.Success(dto);
}
}
@@ -0,0 +1,14 @@
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
);
@@ -0,0 +1,12 @@
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) {}
}