66 lines
2.6 KiB
C#
66 lines
2.6 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Application.Learning;
|
|
using Tiku.Domain.Identity;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Caching;
|
|
using Tiku.Infrastructure.Persistence;
|
|
using ZiggyCreatures.Caching.Fusion;
|
|
|
|
namespace Tiku.Infrastructure.Learning;
|
|
|
|
internal sealed class LearningStrongRevocationService(
|
|
ILearningAccessPersistence persistence,
|
|
[FromKeyedServices(BusinessCachingServiceCollectionExtensions.CacheName)]
|
|
IFusionCache cache) : ILearningStrongRevocationService
|
|
{
|
|
private static readonly TimeSpan Lifetime = TimeSpan.FromSeconds(1);
|
|
|
|
public async Task EnsureVersionAsync(
|
|
LearningActor actor,
|
|
long expectedVersion,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var current = await cache.GetOrSetAsync<long>(
|
|
CacheKey(actor.TenantId, actor.UserId),
|
|
(_, token) => LoadAsync(actor, token),
|
|
options => options.SetDuration(Lifetime).SetFailSafe(false),
|
|
token: cancellationToken);
|
|
if (current != expectedVersion)
|
|
throw new LearningAccessException(
|
|
"practice_access_revoked",
|
|
"The practice session was strongly revoked and can no longer be used.");
|
|
}
|
|
|
|
public Task InvalidateAsync(
|
|
Guid tenantId,
|
|
Guid userId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return cache.RemoveAsync(CacheKey(tenantId, userId), token: cancellationToken).AsTask();
|
|
}
|
|
|
|
private async Task<long> LoadAsync(LearningActor actor, CancellationToken cancellationToken)
|
|
{
|
|
var active = await (
|
|
from tenant in persistence.Tenants.AsNoTracking()
|
|
join user in persistence.Users.AsNoTracking() on actor.UserId equals user.Id
|
|
where tenant.Id == actor.TenantId &&
|
|
tenant.Status == TenantStatus.Active &&
|
|
user.Status == UserStatus.Active
|
|
select tenant.Id)
|
|
.AnyAsync(cancellationToken);
|
|
if (!active)
|
|
throw new LearningAccessException(
|
|
"practice_access_revoked",
|
|
"The tenant or student account is inactive.");
|
|
return await persistence.LearningAccessVersions.AsNoTracking()
|
|
.Where(item => item.TenantId == actor.TenantId && item.UserId == actor.UserId)
|
|
.Select(item => (long?)item.StrongRevocationVersion)
|
|
.SingleOrDefaultAsync(cancellationToken) ?? 1;
|
|
}
|
|
|
|
internal static string CacheKey(Guid tenantId, Guid userId) =>
|
|
$"learning-strong-revocation:v1:{tenantId:N}:{userId:N}";
|
|
}
|