внедрил пресеты статусов и модульную архитектуру
This commit is contained in:
@@ -10,29 +10,39 @@ namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
internal class ProfileRepository : IProfileRepository
|
||||
{
|
||||
private readonly IMongoCollection<ProfileDocument> _profiles;
|
||||
private readonly IUserStatusRepository _statuses;
|
||||
|
||||
public ProfileRepository(IMongoDatabase database)
|
||||
public ProfileRepository(IMongoDatabase database, IUserStatusRepository statuses)
|
||||
{
|
||||
_profiles = database.GetCollection<ProfileDocument>("profiles");
|
||||
_statuses = statuses;
|
||||
}
|
||||
|
||||
public async Task<UserProfileDto?> GetAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(ct);
|
||||
return profile?.ToDto();
|
||||
return profile is null ? null : await profile.ToDtoAsync(_statuses, ct);
|
||||
}
|
||||
|
||||
public async Task<UserProfileDto?> GetByUsernameAsync(string username, CancellationToken ct = default)
|
||||
{
|
||||
var profile = await _profiles.Find(p => p.Username == username).FirstOrDefaultAsync(ct);
|
||||
return profile?.ToDto();
|
||||
return profile is null ? null : await profile.ToDtoAsync(_statuses, ct);
|
||||
}
|
||||
|
||||
public async Task<List<UserProfileDto>> GetAsync(IEnumerable<Guid> userIds, CancellationToken ct = default)
|
||||
{
|
||||
var ids = userIds.ToList();
|
||||
var profiles = await _profiles.Find(p => ids.Contains(p.Id)).ToListAsync(ct);
|
||||
return profiles.Select(p => p.ToDto()).ToList();
|
||||
var statusIds = profiles
|
||||
.Where(p => p.CurrentStatusId.HasValue)
|
||||
.Select(p => p.CurrentStatusId!.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var map = statusIds.Count > 0
|
||||
? await _statuses.GetByIdsAsync(statusIds, ct)
|
||||
: new Dictionary<Guid, UserStatusDto>();
|
||||
return profiles.Select(p => ProfileMappings.ToDtoWithStatusMap(p, map)).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<UserProfileDto>> SearchAsync(string query, int limit = 20, CancellationToken ct = default)
|
||||
@@ -57,7 +67,16 @@ internal class ProfileRepository : IProfileRepository
|
||||
var combinedFilter = Builders<ProfileDocument>.Filter.And(baseFilter, searchFilter);
|
||||
docs = await _profiles.Find(combinedFilter).Limit(limit).ToListAsync(ct);
|
||||
}
|
||||
return docs.Select(p => p.ToDto()).ToList();
|
||||
|
||||
var statusIds = docs
|
||||
.Where(p => p.CurrentStatusId.HasValue)
|
||||
.Select(p => p.CurrentStatusId!.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var map = statusIds.Count > 0
|
||||
? await _statuses.GetByIdsAsync(statusIds, ct)
|
||||
: new Dictionary<Guid, UserStatusDto>();
|
||||
return docs.Select(p => ProfileMappings.ToDtoWithStatusMap(p, map)).ToList();
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> CreateAsync(UserProfileDto dto, CancellationToken ct = default)
|
||||
@@ -65,7 +84,7 @@ internal class ProfileRepository : IProfileRepository
|
||||
var profile = ProfileDocument.Create(dto.UserId, dto.Username ?? string.Empty, dto.DisplayName ?? string.Empty, dto.About);
|
||||
|
||||
await _profiles.InsertOneAsync(profile, null, ct);
|
||||
return Result.Success(profile.ToDto());
|
||||
return Result.Success(await profile.ToDtoAsync(_statuses, ct));
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> UpdateAsync(UserProfileDto dto, CancellationToken ct = default)
|
||||
@@ -80,11 +99,13 @@ internal class ProfileRepository : IProfileRepository
|
||||
dto.Birthday);
|
||||
|
||||
document.UpdateAvatar(dto.Avatar);
|
||||
document.UpdateStatus(dto.StatusText, dto.StatusEmoji, dto.StatusExpiresAt);
|
||||
document.UpdateInvisible(dto.IsInvisible);
|
||||
|
||||
await _profiles.ReplaceOneAsync(p => p.Id == dto.UserId, document, cancellationToken: ct);
|
||||
return Result.Success(document.ToDto());
|
||||
var fresh = await _profiles.Find(p => p.Id == dto.UserId).FirstOrDefaultAsync(ct);
|
||||
if (fresh is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
return Result.Success(await fresh.ToDtoAsync(_statuses, ct));
|
||||
}
|
||||
|
||||
public async Task<Result> UpdateStatusAsync(Guid userId, bool isBanned, bool isDeleted, CancellationToken ct = default)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using Knot.Contracts.Profiles.Application.DTOs;
|
||||
using Knot.Contracts.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Infrastructure.Mappings;
|
||||
using Knot.Shared.Kernel;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
internal sealed class ProfileStatusWriter : IProfileStatusWriter
|
||||
{
|
||||
private readonly IMongoCollection<ProfileDocument> _profiles;
|
||||
private readonly IUserStatusRepository _statuses;
|
||||
|
||||
public ProfileStatusWriter(IMongoDatabase database, IUserStatusRepository statuses)
|
||||
{
|
||||
_profiles = database.GetCollection<ProfileDocument>("profiles");
|
||||
_statuses = statuses;
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> SetCustomAsync(Guid userId, string emoji, string text, DateTime? expiresAt, string? presetKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
if (profile.CurrentStatusId is Guid oldId)
|
||||
await _statuses.DeleteAsync(oldId, cancellationToken);
|
||||
|
||||
var newId = Guid.NewGuid();
|
||||
var type = string.IsNullOrWhiteSpace(presetKey) ? "Custom" : "Preset";
|
||||
var dto = new UserStatusDto
|
||||
{
|
||||
Id = newId,
|
||||
Type = type,
|
||||
Emoji = emoji,
|
||||
Text = text,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ExpiresAt = expiresAt
|
||||
};
|
||||
await _statuses.InsertAsync(dto, userId, cancellationToken);
|
||||
|
||||
profile.SetCurrentStatusId(newId);
|
||||
profile.ClearMoodStatusFields();
|
||||
await _profiles.ReplaceOneAsync(p => p.Id == userId, profile, cancellationToken: cancellationToken);
|
||||
|
||||
var fresh = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(cancellationToken);
|
||||
if (fresh is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
return Result.Success(await fresh.ToDtoAsync(_statuses, cancellationToken));
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> ClearAsync(Guid userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
if (profile.CurrentStatusId is Guid oldId)
|
||||
await _statuses.DeleteAsync(oldId, cancellationToken);
|
||||
|
||||
profile.SetCurrentStatusId(null);
|
||||
profile.ClearMoodStatusFields();
|
||||
await _profiles.ReplaceOneAsync(p => p.Id == userId, profile, cancellationToken: cancellationToken);
|
||||
|
||||
var fresh = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(cancellationToken);
|
||||
if (fresh is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
return Result.Success(await fresh.ToDtoAsync(_statuses, cancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Knot.Contracts.Profiles.Application.DTOs;
|
||||
using Knot.Contracts.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
internal sealed class UserStatusMongoRepository : IUserStatusRepository
|
||||
{
|
||||
private readonly IMongoCollection<UserStatusDocument> _collection;
|
||||
|
||||
public UserStatusMongoRepository(IMongoDatabase database)
|
||||
{
|
||||
_collection = database.GetCollection<UserStatusDocument>("user_statuses");
|
||||
}
|
||||
|
||||
public async Task<UserStatusDto?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var doc = await _collection.Find(x => x.Id == id).FirstOrDefaultAsync(cancellationToken);
|
||||
return doc is null ? null : ToDto(doc);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyDictionary<Guid, UserStatusDto>> GetByIdsAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var idList = ids.Distinct().ToList();
|
||||
if (idList.Count == 0)
|
||||
return new Dictionary<Guid, UserStatusDto>();
|
||||
|
||||
var docs = await _collection.Find(x => idList.Contains(x.Id)).ToListAsync(cancellationToken);
|
||||
return docs.ToDictionary(d => d.Id, ToDto);
|
||||
}
|
||||
|
||||
public async Task InsertAsync(UserStatusDto dto, Guid userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var doc = new UserStatusDocument
|
||||
{
|
||||
Id = dto.Id,
|
||||
UserId = userId,
|
||||
Type = dto.Type,
|
||||
Emoji = dto.Emoji,
|
||||
Text = dto.Text,
|
||||
CreatedAt = dto.CreatedAt,
|
||||
ExpiresAt = dto.ExpiresAt,
|
||||
Metadata = new BsonDocument()
|
||||
};
|
||||
await _collection.InsertOneAsync(doc, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
=> _collection.DeleteOneAsync(x => x.Id == id, cancellationToken);
|
||||
|
||||
private static UserStatusDto ToDto(UserStatusDocument d) => new()
|
||||
{
|
||||
Id = d.Id,
|
||||
Type = d.Type,
|
||||
Emoji = d.Emoji,
|
||||
Text = d.Text,
|
||||
CreatedAt = d.CreatedAt,
|
||||
ExpiresAt = d.ExpiresAt
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user