using Microsoft.EntityFrameworkCore; using Tiku.Application.Content; using Tiku.Application.Security; using Tiku.Domain.QuestionBanks; namespace Tiku.Infrastructure.Content; public sealed partial class DirectContentService { public async Task> CreateQuestionAsync( DirectContentActor actor, QuestionWriteCommand command, CancellationToken cancellationToken = default) { ValidateQuestionForPublication(command); await AssertQuestionReferencesAsync(actor.TenantId, command, cancellationToken); await using var transaction = dbContext.Database.CurrentTransaction is null ? await dbContext.Database.BeginTransactionAsync(cancellationToken) : null; var question = new Question { Id = command.QuestionId ?? Guid.NewGuid(), TenantId = actor.TenantId }; ApplyQuestion(question, command); if (question.Status != QuestionStatus.Archived) await featureAccessService.ConsumeQuotaIfConfiguredAsync( actor.TenantId, SaasQuotaMetricCatalog.PrivateQuestionCount, cancellationToken: cancellationToken); dbContext.Questions.Add(question); await dbContext.SaveChangesAsync(cancellationToken); var version = BuildQuestionVersion(actor, question.Id, 1, command); dbContext.QuestionVersions.Add(version); question.CurrentVersionId = version.Id; await SyncPrimaryCollectionItemAsync(actor, question, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); if (transaction is not null) await transaction.CommitAsync(cancellationToken); return new ContentManagementResult(ToQuestionItem(question, version)); } public async Task> UpdateQuestionAsync( DirectContentActor actor, QuestionWriteCommand command, CancellationToken cancellationToken = default) { if (!command.QuestionId.HasValue) throw new ContentManagementException("questionId is required.", "question_id_required"); ValidateQuestionForPublication(command); var question = await dbContext.Questions.SingleOrDefaultAsync( item => item.TenantId == actor.TenantId && item.Id == command.QuestionId.Value, cancellationToken); if (question is null) throw new ContentManagementException("Question was not found.", "question_not_found"); await AssertQuestionReferencesAsync(actor.TenantId, command, cancellationToken); await using var transaction = dbContext.Database.CurrentTransaction is null ? await dbContext.Database.BeginTransactionAsync(cancellationToken) : null; var wasCounted = question.Status != QuestionStatus.Archived; ApplyQuestion(question, command); var isCounted = question.Status != QuestionStatus.Archived; if (!wasCounted && isCounted) await featureAccessService.ConsumeQuotaIfConfiguredAsync( actor.TenantId, SaasQuotaMetricCatalog.PrivateQuestionCount, cancellationToken: cancellationToken); QuestionVersion? version; if (command.CreateVersion || !question.CurrentVersionId.HasValue) { var nextVersionNo = await dbContext.QuestionVersions .Where(item => item.TenantId == actor.TenantId && item.QuestionId == question.Id) .Select(item => (int?)item.VersionNo) .MaxAsync(cancellationToken) ?? 0; version = BuildQuestionVersion(actor, question.Id, nextVersionNo + 1, command); dbContext.QuestionVersions.Add(version); question.CurrentVersionId = version.Id; } else { version = await dbContext.QuestionVersions.SingleOrDefaultAsync( item => item.TenantId == actor.TenantId && item.QuestionId == question.Id && item.Id == question.CurrentVersionId.Value, cancellationToken); if (version is null) { version = BuildQuestionVersion(actor, question.Id, 1, command); dbContext.QuestionVersions.Add(version); question.CurrentVersionId = version.Id; } else { ApplyQuestionVersion(version, command); } } await SyncPrimaryCollectionItemAsync(actor, question, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); if (wasCounted && !isCounted) await featureAccessService.ReleaseQuotaAsync( actor.TenantId, SaasQuotaMetricCatalog.PrivateQuestionCount, 1, cancellationToken); if (transaction is not null) await transaction.CommitAsync(cancellationToken); return new ContentManagementResult(ToQuestionItem(question, version)); } }