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 _profiles; private readonly IUserStatusRepository _statuses; public ProfileRepository(IMongoDatabase database, IUserStatusRepository statuses) { _profiles = database.GetCollection("profiles"); _statuses = statuses; } public async Task 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 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> GetAsync(IEnumerable 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(); return profiles.Select(p => ProfileMappings.ToDtoWithStatusMap(p, map)).ToList(); } public async Task> SearchAsync(string query, int limit = 20, CancellationToken ct = default) { var baseFilter = Builders.Filter.And( Builders.Filter.Ne(p => p.IsBanned, true), Builders.Filter.Ne(p => p.IsDeleted, true) ); List docs; if (string.IsNullOrWhiteSpace(query)) { docs = await _profiles.Find(baseFilter).Limit(limit).ToListAsync(ct); } else { var searchFilter = Builders.Filter.Or( Builders.Filter.Regex(p => p.Username, new BsonRegularExpression(query, "i")), Builders.Filter.Regex(p => p.DisplayName, new BsonRegularExpression(query, "i")) ); var combinedFilter = Builders.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(); return docs.Select(p => ProfileMappings.ToDtoWithStatusMap(p, map)).ToList(); } public async Task> 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> 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(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(ProfilesErrors.ProfileNotFound); return Result.Success(await fresh.ToDtoAsync(_statuses, ct)); } public async Task 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 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); } }