feat: enforce tenant isolation and shared question bank
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.QuestionBanks;
|
||||
using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.Common;
|
||||
using Tiku.Domain.Content;
|
||||
@@ -11,7 +12,9 @@ using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.Infrastructure.Content;
|
||||
|
||||
public sealed class ContentManagementService(TikuDbContext dbContext) : IContentManagementService
|
||||
public sealed class ContentManagementService(
|
||||
TikuDbContext dbContext,
|
||||
IQuestionReferenceService questionReferenceService) : IContentManagementService
|
||||
{
|
||||
private const int DefaultLimit = 100;
|
||||
private const int MaxLimit = 1000;
|
||||
@@ -358,15 +361,15 @@ public sealed class ContentManagementService(TikuDbContext dbContext) : IContent
|
||||
throw new ContentManagementException("Collection was not found.", "collection_not_found");
|
||||
}
|
||||
|
||||
var questionIds = command.Questions.Select(item => item.QuestionId).Distinct().ToArray();
|
||||
var existingQuestions = await dbContext.Questions
|
||||
.Where(question => question.TenantId == actor.TenantId && questionIds.Contains(question.Id))
|
||||
.Select(question => question.Id)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
if (existingQuestions.Length != questionIds.Length)
|
||||
var resolvedQuestions = new List<(CollectionQuestionCommand Command, TenantQuestionReference Reference)>();
|
||||
foreach (var question in command.Questions)
|
||||
{
|
||||
throw new ContentManagementException("One or more questions are not in this tenant.", "question_not_found");
|
||||
var reference = await questionReferenceService.ResolveAsync(
|
||||
actor.TenantId,
|
||||
actor.UserId,
|
||||
question.Locator,
|
||||
cancellationToken);
|
||||
resolvedQuestions.Add((question, reference));
|
||||
}
|
||||
|
||||
var oldItems = await dbContext.QuestionCollectionItems
|
||||
@@ -374,17 +377,19 @@ public sealed class ContentManagementService(TikuDbContext dbContext) : IContent
|
||||
.ToArrayAsync(cancellationToken);
|
||||
dbContext.QuestionCollectionItems.RemoveRange(oldItems);
|
||||
|
||||
var items = command.Questions
|
||||
.Select((question, index) => new QuestionCollectionItem
|
||||
var items = resolvedQuestions
|
||||
.Select((resolved, index) => new QuestionCollectionItem
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
CollectionId = command.CollectionId,
|
||||
QuestionId = question.QuestionId,
|
||||
SectionKey = Normalize(question.SectionKey),
|
||||
SortOrder = question.Order ?? index,
|
||||
Score = question.Score,
|
||||
Required = question.Required ?? true,
|
||||
Metadata = JsonObjectOrDefault(question.Metadata)
|
||||
QuestionReferenceId = resolved.Reference.Id,
|
||||
QuestionOwnerTenantId = resolved.Reference.QuestionOwnerTenantId,
|
||||
QuestionId = resolved.Reference.QuestionId,
|
||||
SectionKey = Normalize(resolved.Command.SectionKey),
|
||||
SortOrder = resolved.Command.Order ?? index,
|
||||
Score = resolved.Command.Score,
|
||||
Required = resolved.Command.Required ?? true,
|
||||
Metadata = JsonObjectOrDefault(resolved.Command.Metadata)
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
@@ -743,6 +748,9 @@ public sealed class ContentManagementService(TikuDbContext dbContext) : IContent
|
||||
item.Id,
|
||||
item.CollectionId,
|
||||
item.QuestionId,
|
||||
new QuestionLocator(
|
||||
item.QuestionOwnerTenantId == item.TenantId ? QuestionSource.Tenant : QuestionSource.Platform,
|
||||
item.QuestionId),
|
||||
item.SectionKey,
|
||||
item.SortOrder,
|
||||
item.Score,
|
||||
|
||||
Reference in New Issue
Block a user