3 Commits
7 changed files with 417 additions and 90 deletions
@@ -153,5 +153,5 @@ public interface IProjectService
Task<Result<SiteVisit[]>> GetSiteVisitsAsync(string projectIdHash, CancellationToken cancellationToken = default); Task<Result<SiteVisit[]>> GetSiteVisitsAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Log] [Log]
Task<Result<SiteVisit>> GetSiteVisitAsync(string SiteVisitIdHash, CancellationToken cancellationToken = default); Task<Result<SiteVisit>> GetSiteVisitAsync(string siteVisitIdHash, CancellationToken cancellationToken = default);
} }
@@ -215,25 +215,101 @@ public sealed class GovernanceCacheService(GovernanceService service, IDistribut
return Result.Ok(cached.FromJson<Programme[]>()); return Result.Ok(cached.FromJson<Programme[]>());
})); }));
public Task<Result<Tenant>> GetTenantAsync(string tenantIdHash, CancellationToken cancellationToken = default) public async Task<Result<Tenant>> GetTenantAsync(string tenantIdHash, CancellationToken cancellationToken = default) =>
{ await Result.Try((Func<Task<Result<Tenant>>>)(async () =>
throw new NotImplementedException(); {
} if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetTenantAsync(tenantIdHash, cancellationToken);
public Task<Result<TenantOrganisation>> GetTenantOrganisationAsync(string tenantOrganisationIdHash, CancellationToken cancellationToken = default) var cacheKey = HashService.ToMd5Hash($"gettenant-{tenantIdHash}").Value;
{
throw new NotImplementedException();
}
public Task<Result<TenantOrganisation[]>> GetTenantOrganisationsAsync(CancellationToken cancellationToken = default) var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
{
throw new NotImplementedException();
}
public Task<Result<Tenant[]>> GetTenantsAsync(CancellationToken cancellationToken = default) if (string.IsNullOrWhiteSpace(cached))
{ {
throw new NotImplementedException(); var result = await service.GetTenantAsync(tenantIdHash, cancellationToken);
}
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<Tenant>());
}));
public async Task<Result<TenantOrganisation>> GetTenantOrganisationAsync(string tenantOrganisationIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<TenantOrganisation>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetTenantOrganisationAsync(tenantOrganisationIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"gettenantorganisation-{tenantOrganisationIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetTenantOrganisationAsync(tenantOrganisationIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<TenantOrganisation>());
}));
public async Task<Result<TenantOrganisation[]>> GetTenantOrganisationsAsync(CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<TenantOrganisation[]>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetTenantOrganisationsAsync(cancellationToken);
var cacheKey = HashService.ToMd5Hash($"gettenantorganisations").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetTenantOrganisationsAsync(cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<TenantOrganisation[]>());
}));
public async Task<Result<Tenant[]>> GetTenantsAsync(CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<Tenant[]>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetTenantsAsync(cancellationToken);
var cacheKey = HashService.ToMd5Hash($"gettenants").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetTenantsAsync(cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<Tenant[]>());
}));
public async Task<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default) => public async Task<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default) =>
await service.UpdateOrganisationAsync(request, cancellationToken); await service.UpdateOrganisationAsync(request, cancellationToken);
@@ -56,13 +56,14 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var organisations = await context.Organisations.AsNoTracking() var organisations = await context.TenantOrganisations.AsNoTracking()
.OrderBy(o => o.Name) .Include(i => i.Organisation)
.OrderBy(o => o.Organisation!.Name)
.Skip((pagination.Page - 1) * pagination.PageSize) .Skip((pagination.Page - 1) * pagination.PageSize)
.Take(pagination.PageSize) .Take(pagination.PageSize)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
return Result.Ok(organisations.Select(o => o.Map()).ToArray()); return Result.Ok(organisations.Select(o => o.Organisation!.Map()).ToArray());
})); }));
public async Task<Result<Organisation>> GetOrganisationAsync(string organisationIdHash, CancellationToken cancellationToken = default) => public async Task<Result<Organisation>> GetOrganisationAsync(string organisationIdHash, CancellationToken cancellationToken = default) =>
@@ -76,11 +77,12 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var organisation = await context.Organisations.AsNoTracking() var tenantOrganisation = await context.TenantOrganisations.AsNoTracking()
.FirstOrDefaultAsync(o => o.Id == decodeResult.Value, cancellationToken); .Include(i => i.OrganisationId)
.FirstOrDefaultAsync(o => o.OrganisationId == decodeResult.Value, cancellationToken);
return Result.FailIf(organisation is null, NotFoundError.New) return Result.FailIf(tenantOrganisation is null, NotFoundError.New)
.ToResult(organisation!.Map()); .ToResult(tenantOrganisation!.Organisation!.Map());
})); }));
public async Task<Result<Instrument[]>> GetInstrumentsAsync(string programmeIdHash, CancellationToken cancellationToken = default) => public async Task<Result<Instrument[]>> GetInstrumentsAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
@@ -86,45 +86,197 @@ public sealed class ProjectCacheService(ProjectService service, IDistributedCach
return Result.Ok(cached.FromJson<Domain.Models.Activity>()); return Result.Ok(cached.FromJson<Domain.Models.Activity>());
})); }));
public Task<Result<Beneficiary[]>> GetBeneficiariesAsync(string projectIdHash, CancellationToken cancellationToken = default) public async Task<Result<Beneficiary[]>> GetBeneficiariesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
{ await Result.Try((Func<Task<Result<Beneficiary[]>>>)(async () =>
throw new NotImplementedException(); {
} if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetBeneficiariesAsync(projectIdHash, cancellationToken);
public Task<Result<Beneficiary>> GetBeneficiaryAsync(string beneficiaryIdHash, CancellationToken cancellationToken = default) var cacheKey = HashService.ToMd5Hash($"getbeneficiaries-{projectIdHash}").Value;
{
throw new NotImplementedException();
}
public Task<Result<ChangeRequest>> GetChangeRequestAsync(string changeRequestIdHash, CancellationToken cancellationToken = default) var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
{
throw new NotImplementedException();
}
public Task<Result<ChangeRequest[]>> GetChangeRequestsAsync(string projectIdHash, CancellationToken cancellationToken = default) if (string.IsNullOrWhiteSpace(cached))
{ {
throw new NotImplementedException(); var result = await service.GetBeneficiariesAsync(projectIdHash, cancellationToken);
}
public Task<Result<ComplianceItem>> GetComplianceItemAsync(string complianceItemIdHash, CancellationToken cancellationToken = default) if (result.IsFailed) return result;
{
throw new NotImplementedException();
}
public Task<Result<ComplianceItem[]>> GetComplianceItemsAsync(string projectIdHash, CancellationToken cancellationToken = default) await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
{
throw new NotImplementedException();
}
public Task<Result<Issue>> GetIssueAsync(string issueIdHash, CancellationToken cancellationToken = default) return result.Value;
{ }
throw new NotImplementedException();
}
public Task<Result<Issue[]>> GetIssuesAsync(string projectIdHash, CancellationToken cancellationToken = default) return Result.Ok(cached.FromJson<Beneficiary[]>());
{ }));
throw new NotImplementedException();
} public async Task<Result<Beneficiary>> GetBeneficiaryAsync(string beneficiaryIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<Beneficiary>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetBeneficiaryAsync(beneficiaryIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getbeneficiary-{beneficiaryIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetBeneficiaryAsync(beneficiaryIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<Beneficiary>());
}));
public async Task<Result<ChangeRequest>> GetChangeRequestAsync(string changeRequestIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<ChangeRequest>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetChangeRequestAsync(changeRequestIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getchangerequest-{changeRequestIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetChangeRequestAsync(changeRequestIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<ChangeRequest>());
}));
public async Task<Result<ChangeRequest[]>> GetChangeRequestsAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<ChangeRequest[]>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetChangeRequestsAsync(projectIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getchangerequests-{projectIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetChangeRequestsAsync(projectIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<ChangeRequest[]>());
}));
public async Task<Result<ComplianceItem>> GetComplianceItemAsync(string complianceItemIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<ComplianceItem>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetComplianceItemAsync(complianceItemIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getcomplianceitem-{complianceItemIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetComplianceItemAsync(complianceItemIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<ComplianceItem>());
}));
public async Task<Result<ComplianceItem[]>> GetComplianceItemsAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<ComplianceItem[]>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetComplianceItemsAsync(projectIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getcomplianceitems-{projectIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetComplianceItemsAsync(projectIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<ComplianceItem[]>());
}));
public async Task<Result<Issue>> GetIssueAsync(string issueIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<Issue>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetIssueAsync(issueIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getissue-{issueIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetIssueAsync(issueIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<Issue>());
}));
public async Task<Result<Issue[]>> GetIssuesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<Issue[]>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetIssuesAsync(projectIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getissues-{projectIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetIssuesAsync(projectIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<Issue[]>());
}));
public async Task<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default) => public async Task<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<Milestone>>>)(async () => await Result.Try((Func<Task<Result<Milestone>>>)(async () =>
@@ -174,15 +326,53 @@ public sealed class ProjectCacheService(ProjectService service, IDistributedCach
return Result.Ok(cached.FromJson<Milestone[]>()); return Result.Ok(cached.FromJson<Milestone[]>());
})); }));
public Task<Result<Outcome>> GetOutcomeAsync(string outcomeIdHash, CancellationToken cancellationToken = default) public async Task<Result<Outcome>> GetOutcomeAsync(string outcomeIdHash, CancellationToken cancellationToken = default) =>
{ await Result.Try((Func<Task<Result<Outcome>>>)(async () =>
throw new NotImplementedException(); {
} if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetOutcomeAsync(outcomeIdHash, cancellationToken);
public Task<Result<Outcome[]>> GetOutcomesAsync(string beneficiaryIdHash, CancellationToken cancellationToken = default) var cacheKey = HashService.ToMd5Hash($"getoutcome-{outcomeIdHash}").Value;
{
throw new NotImplementedException(); var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
}
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetOutcomeAsync(outcomeIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<Outcome>());
}));
public async Task<Result<Outcome[]>> GetOutcomesAsync(string beneficiaryIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<Outcome[]>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetOutcomesAsync(beneficiaryIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getoutcomes-{beneficiaryIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetOutcomesAsync(beneficiaryIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<Outcome[]>());
}));
public async Task<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default) => public async Task<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<Programme>>>)(async () => await Result.Try((Func<Task<Result<Programme>>>)(async () =>
@@ -376,15 +566,53 @@ public sealed class ProjectCacheService(ProjectService service, IDistributedCach
return Result.Ok(cached.FromJson<Project[]>()); return Result.Ok(cached.FromJson<Project[]>());
})); }));
public Task<Result<SiteVisit>> GetSiteVisitAsync(string SiteVisitIdHash, CancellationToken cancellationToken = default) public async Task<Result<SiteVisit>> GetSiteVisitAsync(string siteVisitIdHash, CancellationToken cancellationToken = default) =>
{ await Result.Try((Func<Task<Result<SiteVisit>>>)(async () =>
throw new NotImplementedException(); {
} if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetSiteVisitAsync(siteVisitIdHash, cancellationToken);
public Task<Result<SiteVisit[]>> GetSiteVisitsAsync(string projectIdHash, CancellationToken cancellationToken = default) var cacheKey = HashService.ToMd5Hash($"getsitevisit-{siteVisitIdHash}").Value;
{
throw new NotImplementedException(); var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
}
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetSiteVisitAsync(siteVisitIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<SiteVisit>());
}));
public async Task<Result<SiteVisit[]>> GetSiteVisitsAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<SiteVisit[]>>>)(async () =>
{
if (!await featureManager.IsEnabledAsync(ServiceCachingFeature))
return await service.GetSiteVisitsAsync(projectIdHash, cancellationToken);
var cacheKey = HashService.ToMd5Hash($"getsitevisits-{projectIdHash}").Value;
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
if (string.IsNullOrWhiteSpace(cached))
{
var result = await service.GetSiteVisitsAsync(projectIdHash, cancellationToken);
if (result.IsFailed) return result;
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
return result.Value;
}
return Result.Ok(cached.FromJson<SiteVisit[]>());
}));
public async Task<Result> UpdateBeneficiaryItemAsync(UpdateBeneficiaryRequest request, Guid userId, CancellationToken cancellationToken = default) => public async Task<Result> UpdateBeneficiaryItemAsync(UpdateBeneficiaryRequest request, Guid userId, CancellationToken cancellationToken = default) =>
await service.UpdateBeneficiaryItemAsync(request, userId, cancellationToken); await service.UpdateBeneficiaryItemAsync(request, userId, cancellationToken);
@@ -498,7 +498,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<Result<SiteVisit>> GetSiteVisitAsync(string SiteVisitIdHash, CancellationToken cancellationToken = default) public Task<Result<SiteVisit>> GetSiteVisitAsync(string siteVisitIdHash, CancellationToken cancellationToken = default)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -12,6 +12,7 @@ public sealed class IdentityService(IDbContextFactory<ApplicationDbContext> cont
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
// TODO: Ensure TenantUser status is checked
var userExistsAndActive = await context.Users var userExistsAndActive = await context.Users
.AnyAsync(u => u.Id == userId && u.Status == ActivityStatus.Active, cancellationToken); .AnyAsync(u => u.Id == userId && u.Status == ActivityStatus.Active, cancellationToken);
@@ -1,5 +1,6 @@
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
@@ -7,16 +8,19 @@ using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Application.Services.Users; namespace PostFundManagement.Application.Services.Users;
public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextFactory) : IUserService public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextFactory, ITenantContextService tenantContext) : IUserService
{ {
public async Task<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default) => public async Task<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result>>)(async () => await Result.Try((Func<Task<Result>>)(async () =>
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); var tenantUser = await context.TenantUsers
.Include(i => i.User)
.FirstOrDefaultAsync(u => u.UserId == userId, cancellationToken);
if (user is null) if (tenantUser is null)
{
context.Users.Add(new Domain.Entities.User context.Users.Add(new Domain.Entities.User
{ {
Id = userId, Id = userId,
@@ -24,12 +28,22 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
Status = ActivityStatus.Active, Status = ActivityStatus.Active,
LastLoginAt = DateTime.UtcNow LastLoginAt = DateTime.UtcNow
}); });
context.TenantUsers.Add(new Domain.Entities.TenantUser
{
CreatedAt = DateTime.UtcNow,
CreatedBy = userId,
Status = ActivityStatus.Active,
TenantId = tenantContext.TenantId,
UserId = userId
});
}
else else
{ {
user!.Email = email; tenantUser.User!.Email = email;
user.LastLoginAt = DateTime.UtcNow; tenantUser.User.LastLoginAt = DateTime.UtcNow;
context.Users.Update(user); context.Users.Update(tenantUser.User);
} }
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New); return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
@@ -40,19 +54,19 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var query = context.Users.AsNoTracking(); var query = context.TenantUsers.Include(i => i.User).AsNoTracking();
if (request.Status.HasValue) if (request.Status.HasValue)
query = query.Where(u => u.Status == request.Status.Value); query = query.Where(u => u.Status == request.Status.Value);
if (!string.IsNullOrWhiteSpace(request.Email)) if (!string.IsNullOrWhiteSpace(request.Email))
query = query.Where(u => u.Email != null && u.Email.Contains(request.Email)); query = query.Where(u => u.User!.Email != null && u.User!.Email.Contains(request.Email));
if (!string.IsNullOrWhiteSpace(request.SortBy)) if (!string.IsNullOrWhiteSpace(request.SortBy))
query = request.SortBy.ToLowerInvariant() switch query = request.SortBy.ToLowerInvariant() switch
{ {
"email" => request.SortDescending ? query.OrderByDescending(u => u.Email) : query.OrderBy(u => u.Email), "email" => request.SortDescending ? query.OrderByDescending(u => u.User!.Email) : query.OrderBy(u => u.User!.Email),
"lastloginat" => request.SortDescending ? query.OrderByDescending(u => u.LastLoginAt) : query.OrderBy(u => u.LastLoginAt), "lastloginat" => request.SortDescending ? query.OrderByDescending(u => u.User!.LastLoginAt) : query.OrderBy(u => u.User!.LastLoginAt),
"status" => request.SortDescending ? query.OrderByDescending(u => u.Status) : query.OrderBy(u => u.Status), "status" => request.SortDescending ? query.OrderByDescending(u => u.Status) : query.OrderBy(u => u.Status),
_ => request.SortDescending ? query.OrderByDescending(u => u.Id) : query.OrderBy(u => u.Id) _ => request.SortDescending ? query.OrderByDescending(u => u.Id) : query.OrderBy(u => u.Id)
}; };
@@ -62,7 +76,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
var users = await query.Skip((pagination.Page - 1) * pagination.PageSize) var users = await query.Skip((pagination.Page - 1) * pagination.PageSize)
.Take(pagination.PageSize).ToListAsync(cancellationToken); .Take(pagination.PageSize).ToListAsync(cancellationToken);
return Result.Ok(users.Select(u => u.Map()).ToArray()); return Result.Ok(users.Select(u => u.User!.Map()).ToArray());
})); }));
public async Task<Result> UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default) => public async Task<Result> UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default) =>
@@ -70,8 +84,13 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var changedTotal = await context.Users var changedTotal = await context.TenantUsers
.Where(f => f.Id == userId) .Include(i => i.User)
.Where(f => f.UserId == userId)
.ExecuteUpdateAsync(f => f
.SetProperty(u => u.Status, status), cancellationToken);
changedTotal += await context.Users.Where(f => f.Id == userId)
.ExecuteUpdateAsync(f => f.SetProperty(u => u.Status, status), cancellationToken); .ExecuteUpdateAsync(f => f.SetProperty(u => u.Status, status), cancellationToken);
return Result.OkIf(changedTotal > 0, UpdateError.New); return Result.OkIf(changedTotal > 0, UpdateError.New);
@@ -82,10 +101,11 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var user = await context.Users.AsNoTracking() var tenantUser = await context.TenantUsers.AsNoTracking()
.FirstOrDefaultAsync(f => f.Id == userId, cancellationToken); .Include(i => i.User)
.FirstOrDefaultAsync(f => f.UserId == userId, cancellationToken);
return Result.FailIf(user == null, NotFoundError.New) return Result.FailIf(tenantUser == null, NotFoundError.New)
.ToResult(user?.Map()!); .ToResult(tenantUser!.User?.Map()!);
})); }));
} }