using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Tiku.Application.QuestionBanks; using Tiku.Application.Security; using Tiku.Domain.Content; using Tiku.Domain.QuestionBanks; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.QuestionBanks; public sealed class QuestionReferenceService( TikuDbContext dbContext, IPublicQuestionAccessPolicy accessPolicy, ITenantExecutionScope tenantExecutionScope) : IQuestionReferenceService { public async Task ResolveAsync( Guid tenantId, Guid? userId, QuestionLocator locator, CancellationToken cancellationToken = default) { var ownerTenantId = locator.Source switch { QuestionSource.Tenant => await ResolveTenantQuestionAsync(tenantId, locator.QuestionId, cancellationToken), QuestionSource.Platform => await ResolvePlatformQuestionAsync(tenantId, locator.QuestionId, cancellationToken), _ => throw new QuestionLocatorException("question_source_invalid", "Question source is invalid.") }; var existing = await dbContext.TenantQuestionReferences.SingleOrDefaultAsync( reference => reference.TenantId == tenantId && reference.QuestionOwnerTenantId == ownerTenantId && reference.QuestionId == locator.QuestionId, cancellationToken); if (existing is not null) { return existing; } var reference = new TenantQuestionReference { TenantId = tenantId, QuestionOwnerTenantId = ownerTenantId, QuestionId = locator.QuestionId, Source = locator.Source, CreatedBy = userId }; dbContext.TenantQuestionReferences.Add(reference); return reference; } private async Task ResolveTenantQuestionAsync( Guid tenantId, Guid questionId, CancellationToken cancellationToken) { var exists = await dbContext.Questions.AsNoTracking().AnyAsync( question => question.TenantId == tenantId && question.Id == questionId && question.Status == QuestionStatus.Published, cancellationToken); return exists ? tenantId : throw new QuestionLocatorException("question_not_found", "Tenant question was not found."); } private async Task ResolvePlatformQuestionAsync( Guid tenantId, Guid questionId, CancellationToken cancellationToken) { await accessPolicy.EnsureCanStartAsync(tenantId, cancellationToken); var ownerTenantId = await tenantExecutionScope.ExecuteAsync( new SystemScopeRequest( tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(QuestionReferenceService), "Resolve a platform question for an entitled tenant", Guid.NewGuid().ToString("N")), async (provider, token) => { var systemDbContext = provider.GetRequiredService(); return await systemDbContext.Questions.AsNoTracking() .Where(question => question.Id == questionId && question.Status == QuestionStatus.Published) .Join( systemDbContext.Tenants.AsNoTracking().Where(tenant => tenant.Mode == TenantMode.PlatformOwned), question => question.TenantId, tenant => tenant.Id, (question, tenant) => (Guid?)tenant.Id) .SingleOrDefaultAsync(token); }, cancellationToken); return ownerTenantId ?? throw new QuestionLocatorException( "question_not_found", "Platform question was not found."); } }