Files
tiku-backend.net/Tiku.Infrastructure/Learning/Access/LearningStrongRevocationService.cs
xiong 3f5957d744
Some checks failed
ci / release-gate (push) Has been cancelled
feat(content): establish v2 learning delivery foundation
2026-08-06 09:52:18 +08:00

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}";
}