Контракты
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Infrastructure.Database;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace Knot.Modules.Profiles.Domain.Events;
|
||||
namespace Knot.Modules.Profiles.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Профиль пользователя создан.
|
||||
/// Профиль пользователя создан.
|
||||
/// </summary>
|
||||
public record ProfileCreatedDomainEvent(Guid ProfileId) : IDomainEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Профиль пользователя обновлен.
|
||||
/// Профиль пользователя обновлен.
|
||||
/// </summary>
|
||||
public record ProfileUpdatedDomainEvent(Guid ProfileId) : IDomainEvent;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IAvatarStorageService
|
||||
{
|
||||
Task<string> UploadAsync(Stream content, string fileName, string contentType, CancellationToken ct = default);
|
||||
Task DeleteAsync(string fileId, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IProfileRepository
|
||||
{
|
||||
Task<ProfileDocument?> GetByIdAsync(Guid id, CancellationToken ct = default);
|
||||
Task<ProfileDocument?> GetByUsernameAsync(string username, 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,9 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IProfilesUnitOfWork
|
||||
{
|
||||
Task SaveChangesAsync(CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public sealed class Profile : AggregateRoot<Guid>
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// MongoDB-документ профиля пользователя.
|
||||
/// Id совпадает с UserId из модуля Auth (Postgres).
|
||||
/// MongoDB-документ профиля пользователя.
|
||||
/// Id совпадает с UserId из модуля Auth (Postgres).
|
||||
/// </summary>
|
||||
public sealed class ProfileDocument
|
||||
{
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
public static class ProfilesErrors
|
||||
{
|
||||
public static readonly Error ProfileNotFound = new("Profile.NotFound", "Profile not found");
|
||||
}
|
||||
public static class IdentityErrors
|
||||
{
|
||||
public static readonly Error UserNotFound = new("Profile.NotFound", "Profile not found");
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
public class AvatarStorageService : IAvatarStorageService
|
||||
internal class AvatarStorageService : IAvatarStorageService
|
||||
{
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
|
||||
@@ -15,15 +15,24 @@ public class AvatarStorageService : IAvatarStorageService
|
||||
_fileStorage = fileStorage;
|
||||
}
|
||||
|
||||
public async Task<string> UploadAsync(Stream content, string fileName, string contentType, CancellationToken ct = default)
|
||||
public async Task<string> UploadAsync(Stream stream, string fileName, string contentType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// IFileStorageService не принимает CancellationToken в UploadFileAsync
|
||||
return await _fileStorage.UploadFileAsync(content, fileName, contentType);
|
||||
return await _fileStorage.UploadFileAsync(stream, fileName, contentType);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string fileId, CancellationToken ct = default)
|
||||
public async Task DeleteAsync(string fileKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// IFileStorageService не принимает CancellationToken в DeleteFileAsync
|
||||
await _fileStorage.DeleteFileAsync(fileId);
|
||||
await _fileStorage.DeleteFileAsync(fileKey);
|
||||
}
|
||||
|
||||
public async Task<Stream?> GetAsync(string fileKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var (stream, _, _) = await _fileStorage.DownloadFileAsync(fileKey);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public string GetFileUrl(string fileKey)
|
||||
{
|
||||
return $"/api/files/{fileKey}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Infrastructure.Mappings;
|
||||
using Knot.Shared.Kernel;
|
||||
using ProfilesErrors = Knot.Modules.Profiles.Contracts.Application.DTOs.ProfilesErrors;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
public class ProfileRepository : IProfileRepository
|
||||
internal class ProfileRepository : IProfileRepository
|
||||
{
|
||||
private readonly IMongoCollection<ProfileDocument> _profiles;
|
||||
|
||||
@@ -17,36 +23,69 @@ public class ProfileRepository : IProfileRepository
|
||||
_profiles = database.GetCollection<ProfileDocument>("profiles");
|
||||
}
|
||||
|
||||
public async Task<ProfileDocument?> GetByIdAsync(Guid id, CancellationToken ct = default)
|
||||
public async Task<UserProfileDto?> GetAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
return await _profiles.Find(p => p.Id == id).FirstOrDefaultAsync(ct);
|
||||
var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(ct);
|
||||
return profile?.ToDto();
|
||||
}
|
||||
|
||||
public async Task<ProfileDocument?> GetByUsernameAsync(string username, CancellationToken ct = default)
|
||||
public async Task<UserProfileDto?> GetByUsernameAsync(string username, CancellationToken ct = default)
|
||||
{
|
||||
return await _profiles.Find(p => p.Username == username).FirstOrDefaultAsync(ct);
|
||||
var profile = await _profiles.Find(p => p.Username == username).FirstOrDefaultAsync(ct);
|
||||
return profile?.ToDto();
|
||||
}
|
||||
|
||||
public async Task AddAsync(ProfileDocument profile, CancellationToken ct = default)
|
||||
public async Task<List<UserProfileDto>> GetAsync(IEnumerable<Guid> userIds, CancellationToken ct = default)
|
||||
{
|
||||
await _profiles.InsertOneAsync(profile, null, ct);
|
||||
var ids = userIds.ToList();
|
||||
var profiles = await _profiles.Find(p => ids.Contains(p.Id)).ToListAsync(ct);
|
||||
return profiles.Select(p => p.ToDto()).ToList();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default)
|
||||
{
|
||||
await _profiles.ReplaceOneAsync(p => p.Id == profile.Id, profile, new ReplaceOptions { IsUpsert = true }, ct);
|
||||
}
|
||||
|
||||
public async Task<List<ProfileDocument>> SearchAsync(string query, CancellationToken ct = default)
|
||||
public async Task<List<UserProfileDto>> SearchAsync(string query, int limit = 20, CancellationToken ct = default)
|
||||
{
|
||||
List<ProfileDocument> docs;
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return await _profiles.Find(_ => true).Limit(50).ToListAsync(ct);
|
||||
{
|
||||
docs = await _profiles.Find(_ => true).Limit(limit).ToListAsync(ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filter = Builders<ProfileDocument>.Filter.Or(
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.Username, new BsonRegularExpression(query, "i")),
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.DisplayName, new BsonRegularExpression(query, "i"))
|
||||
);
|
||||
docs = await _profiles.Find(filter).Limit(limit).ToListAsync(ct);
|
||||
}
|
||||
return docs.Select(p => p.ToDto()).ToList();
|
||||
}
|
||||
|
||||
var filter = Builders<ProfileDocument>.Filter.Or(
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.Username, new BsonRegularExpression(query, "i")),
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.DisplayName, new BsonRegularExpression(query, "i"))
|
||||
);
|
||||
public async Task<Result<UserProfileDto>> CreateAsync(UserProfileDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var profile = ProfileDocument.Create(dto.UserId, dto.Username ?? string.Empty, dto.DisplayName ?? string.Empty, dto.About);
|
||||
|
||||
return await _profiles.Find(filter).Limit(50).ToListAsync(ct);
|
||||
await _profiles.InsertOneAsync(profile, null, ct);
|
||||
return Result.Success(profile.ToDto());
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> UpdateAsync(UserProfileDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var profile = await _profiles.Find(p => p.Id == dto.UserId).FirstOrDefaultAsync(ct);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
profile.UpdateProfile(
|
||||
dto.DisplayName ?? profile.DisplayName,
|
||||
dto.About ?? profile.Bio,
|
||||
null);
|
||||
|
||||
await _profiles.ReplaceOneAsync(p => p.Id == dto.UserId, profile, new ReplaceOptions { IsUpsert = true }, ct);
|
||||
return Result.Success(profile.ToDto());
|
||||
}
|
||||
|
||||
public async Task<Result> DeleteAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _profiles.DeleteOneAsync(p => p.Id == userId, ct);
|
||||
return result.DeletedCount > 0 ? Result.Success() : Result.Failure(ProfilesErrors.ProfileNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
public class ProfilesUnitOfWork : IProfilesUnitOfWork
|
||||
internal class ProfilesUnitOfWork : IProfilesUnitOfWork
|
||||
{
|
||||
public Task SaveChangesAsync(CancellationToken ct = default) => Task.CompletedTask;
|
||||
public IProfileRepository ProfileRepository { get; }
|
||||
|
||||
public ProfilesUnitOfWork(IProfileRepository profileRepository)
|
||||
{
|
||||
ProfileRepository = profileRepository;
|
||||
}
|
||||
|
||||
public Task<int> SaveChangesAsync(CancellationToken ct = default) => Task.FromResult(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Mappings;
|
||||
|
||||
public static class ProfileMappings
|
||||
{
|
||||
public static UserProfileDto ToDto(this ProfileDocument document)
|
||||
{
|
||||
return new UserProfileDto
|
||||
{
|
||||
UserId = document.Id,
|
||||
DisplayName = document.DisplayName,
|
||||
Username = document.Username,
|
||||
About = document.Bio,
|
||||
Avatar = document.AvatarUrl,
|
||||
IsBot = false,
|
||||
LastSeen = null,
|
||||
IsPremium = false,
|
||||
CreatedAt = document.CreatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Profiles.Contracts\Knot.Modules.Profiles.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Carter;
|
||||
using Carter;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Application.Profiles.Avatar;
|
||||
using Knot.Modules.Profiles.Application.Profiles.GetProfile;
|
||||
|
||||
Reference in New Issue
Block a user