127 lines
5.3 KiB
C#
127 lines
5.3 KiB
C#
using Knot.Contracts.Profiles.Application.DTOs;
|
|
using Knot.Contracts.Profiles.Domain;
|
|
using Knot.Modules.Profiles.Infrastructure.Mappings;
|
|
using Knot.Shared.Kernel;
|
|
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
|
|
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
|
|
|
internal class ProfileRepository : IProfileRepository
|
|
{
|
|
private readonly IMongoCollection<ProfileDocument> _profiles;
|
|
private readonly IUserStatusRepository _statuses;
|
|
|
|
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 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 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);
|
|
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)
|
|
{
|
|
var baseFilter = Builders<ProfileDocument>.Filter.And(
|
|
Builders<ProfileDocument>.Filter.Ne(p => p.IsBanned, true),
|
|
Builders<ProfileDocument>.Filter.Ne(p => p.IsDeleted, true)
|
|
);
|
|
|
|
List<ProfileDocument> docs;
|
|
if (string.IsNullOrWhiteSpace(query))
|
|
{
|
|
docs = await _profiles.Find(baseFilter).Limit(limit).ToListAsync(ct);
|
|
}
|
|
else
|
|
{
|
|
var searchFilter = 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"))
|
|
);
|
|
|
|
var combinedFilter = Builders<ProfileDocument>.Filter.And(baseFilter, searchFilter);
|
|
docs = await _profiles.Find(combinedFilter).Limit(limit).ToListAsync(ct);
|
|
}
|
|
|
|
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)
|
|
{
|
|
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(await profile.ToDtoAsync(_statuses, ct));
|
|
}
|
|
|
|
public async Task<Result<UserProfileDto>> UpdateAsync(UserProfileDto dto, CancellationToken ct = default)
|
|
{
|
|
var document = await _profiles.Find(p => p.Id == dto.UserId).FirstOrDefaultAsync(ct);
|
|
if (document is null)
|
|
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
|
|
|
document.UpdateProfile(
|
|
dto.DisplayName ?? document.DisplayName,
|
|
dto.About ?? document.Bio,
|
|
dto.Birthday);
|
|
|
|
document.UpdateAvatar(dto.Avatar);
|
|
document.UpdateInvisible(dto.IsInvisible);
|
|
|
|
await _profiles.ReplaceOneAsync(p => p.Id == dto.UserId, document, cancellationToken: ct);
|
|
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)
|
|
{
|
|
var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(ct);
|
|
if (profile is null) return Result.Failure(ProfilesErrors.ProfileNotFound);
|
|
|
|
profile.UpdateStatus(isBanned, isDeleted);
|
|
await _profiles.ReplaceOneAsync(p => p.Id == userId, profile, cancellationToken: ct);
|
|
return Result.Success();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|