refinement phase 3

This commit is contained in:
hamid
2026-07-13 11:26:39 +03:30
parent 1ce36f9414
commit 314763f764
194 changed files with 24211 additions and 274 deletions
@@ -54,13 +54,14 @@ internal sealed class SearchIndexMaintainer(ApplicationDbContext db, IDateTimePr
public async Task ReindexNurseAsync(NurseProfile profile, VerificationStatus? verificationStatus, CancellationToken cancellationToken)
{
var status = verificationStatus ?? await LoadStatusAsync(profile.Id, cancellationToken);
var gender = await LoadGenderAsync(profile.Id, cancellationToken);
var identity = await LoadIdentityAsync(profile.Id, cancellationToken);
// Bookability + aggregates come from the tracked profile (its just-changed values); gender is stable
// for these triggers so it is read from the database.
// Bookability + aggregates come from the tracked profile (its just-changed values); gender + name are
// stable for these triggers so they are read from the database. The avatar is on the tracked profile.
var ctx = new NurseContext(
profile.IsVerified, profile.IsAcceptingBookings, gender,
profile.AverageRating, profile.TotalReviews, profile.TotalCompletedBookings);
profile.IsVerified, profile.IsAcceptingBookings, identity.Gender,
profile.AverageRating, profile.TotalReviews, profile.TotalCompletedBookings,
identity.Name, profile.AvatarUrl);
var bookable = NurseBookable(profile.IsVerified, profile.IsAcceptingBookings, status);
var areas = await LoadActiveAreasAsync(profile.Id, cancellationToken);
@@ -197,6 +198,8 @@ internal sealed class SearchIndexMaintainer(ApplicationDbContext db, IDateTimePr
existing.Price = variant.Price;
existing.PriceUnit = variant.PriceUnit;
existing.NurseGender = ctx.Gender;
existing.NurseName = ctx.NurseName;
existing.AvatarUrl = ctx.AvatarUrl;
existing.AverageRating = ctx.AverageRating;
existing.TotalReviews = ctx.TotalReviews;
existing.TotalCompletedBookings = ctx.TotalCompletedBookings;
@@ -218,6 +221,8 @@ internal sealed class SearchIndexMaintainer(ApplicationDbContext db, IDateTimePr
CityId = area.CityId,
DistrictId = area.DistrictId,
NurseGender = ctx.Gender,
NurseName = ctx.NurseName,
AvatarUrl = ctx.AvatarUrl,
AverageRating = ctx.AverageRating,
TotalReviews = ctx.TotalReviews,
TotalCompletedBookings = ctx.TotalCompletedBookings,
@@ -248,14 +253,21 @@ internal sealed class SearchIndexMaintainer(ApplicationDbContext db, IDateTimePr
.Where(p => p.Id == nurseId)
.Select(p => new NurseContext(
p.IsVerified, p.IsAcceptingBookings, p.User.Gender,
p.AverageRating, p.TotalReviews, p.TotalCompletedBookings))
p.AverageRating, p.TotalReviews, p.TotalCompletedBookings,
((p.User.Name ?? "") + " " + (p.User.FamilyName ?? "")).Trim(), p.AvatarUrl))
.FirstOrDefaultAsync(cancellationToken);
private async Task<string> LoadGenderAsync(long nurseId, CancellationToken cancellationToken)
=> await db.Set<NurseProfile>()
private async Task<NurseIdentity> LoadIdentityAsync(long nurseId, CancellationToken cancellationToken)
{
var identity = await db.Set<NurseProfile>()
.Where(p => p.Id == nurseId)
.Select(p => p.User.Gender)
.FirstOrDefaultAsync(cancellationToken) ?? string.Empty;
.Select(p => new NurseIdentity(
p.User.Gender ?? string.Empty,
((p.User.Name ?? "") + " " + (p.User.FamilyName ?? "")).Trim()))
.FirstOrDefaultAsync(cancellationToken);
// NurseIdentity is a struct — FirstOrDefaultAsync yields default (null fields) when no row exists.
return new NurseIdentity(identity.Gender ?? string.Empty, identity.Name ?? string.Empty);
}
private Task<VerificationStatus?> LoadStatusAsync(long nurseId, CancellationToken cancellationToken)
=> db.Set<NurseVerification>()
@@ -281,5 +293,8 @@ internal sealed class SearchIndexMaintainer(ApplicationDbContext db, IDateTimePr
private sealed record NurseContext(
bool IsVerified, bool IsAcceptingBookings, string Gender,
decimal AverageRating, int TotalReviews, int TotalCompletedBookings);
decimal AverageRating, int TotalReviews, int TotalCompletedBookings,
string NurseName, string AvatarUrl);
private readonly record struct NurseIdentity(string Gender, string Name);
}
@@ -54,19 +54,23 @@ internal sealed class SqlNurseSearch(ApplicationDbContext db) : INurseSearch
.Take(criteria.PageSize)
.Select(r => new Row(
r.VariantId, r.NurseId, r.ServiceCategoryId, r.Price, r.PriceUnit, r.NurseGender,
r.AverageRating, r.TotalReviews, r.TotalCompletedBookings, r.CityId, r.DistrictId))
r.AverageRating, r.TotalReviews, r.TotalCompletedBookings, r.CityId, r.DistrictId,
r.NurseName, r.AvatarUrl))
.ToListAsync(cancellationToken);
// Format price to a digit string in memory (no long.ToString translation required in SQL).
// DistanceKm is null: the covering index carries no coordinate, so distance is not derivable here.
var items = rows.Select(r => new NurseSearchResultDto(
r.VariantId, r.NurseId, r.ServiceCategoryId,
r.Price.ToString(CultureInfo.InvariantCulture), r.PriceUnit, r.NurseGender,
r.AverageRating, r.TotalReviews, r.TotalCompletedBookings, r.CityId, r.DistrictId)).ToList();
r.AverageRating, r.TotalReviews, r.TotalCompletedBookings, r.CityId, r.DistrictId,
r.NurseName, r.AvatarUrl, null)).ToList();
return new PagedResult<NurseSearchResultDto>(items, total, criteria.Page, criteria.PageSize);
}
private sealed record Row(
long VariantId, long NurseId, long ServiceCategoryId, long Price, string PriceUnit, string NurseGender,
decimal AverageRating, int TotalReviews, int TotalCompletedBookings, long CityId, long? DistrictId);
decimal AverageRating, int TotalReviews, int TotalCompletedBookings, long CityId, long? DistrictId,
string NurseName, string AvatarUrl);
}