refactor(architecture): harden module boundaries
Some checks failed
ci / release-gate (push) Has been cancelled
Some checks failed
ci / release-gate (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.Content;
|
||||
using Tiku.Domain.QuestionBanks;
|
||||
using Tiku.Infrastructure.Security;
|
||||
|
||||
namespace Tiku.Infrastructure.Content;
|
||||
|
||||
internal sealed class QuestionCollectionManagementService(ContentManagementDependencies dependencies)
|
||||
: ContentManagementServiceBase(dependencies), IQuestionCollectionManagementService
|
||||
{
|
||||
public async Task<CatalogList<QuestionCollectionManagementItem>> GetCollectionsAsync(
|
||||
ContentManagementActor actor,
|
||||
ContentManagementFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
||||
var regionIds = scope.RegionIds.ToArray();
|
||||
var query = questionBankPersistence.QuestionCollections
|
||||
.AsNoTracking()
|
||||
.Where(collection => collection.TenantId == actor.TenantId)
|
||||
.ApplyDataScope(
|
||||
scope,
|
||||
collection => collection.CreatedBy == actor.UserId,
|
||||
collection => collection.RegionId.HasValue && regionIds.Contains(collection.RegionId.Value));
|
||||
|
||||
if (!filter.IncludeInactive) query = query.Where(collection => collection.Status == ContentStatus.Active);
|
||||
|
||||
if (filter.RegionId.HasValue) query = query.Where(collection => collection.RegionId == filter.RegionId.Value);
|
||||
|
||||
if (filter.EntryId.HasValue) query = query.Where(collection => collection.EntryId == filter.EntryId.Value);
|
||||
|
||||
if (filter.NodeId.HasValue) query = query.Where(collection => collection.NodeId == filter.NodeId.Value);
|
||||
|
||||
if (TryParse(filter.CollectionType, out QuestionCollectionType collectionType))
|
||||
query = query.Where(collection => collection.CollectionType == collectionType);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
||||
{
|
||||
var keyword = filter.Keyword.Trim();
|
||||
query = query.Where(collection => collection.Name.Contains(keyword));
|
||||
}
|
||||
|
||||
var items = await query
|
||||
.OrderBy(collection => collection.SortOrder)
|
||||
.ThenBy(collection => collection.CreatedAt)
|
||||
.Take(ResolveLimit(filter.Limit))
|
||||
.Select(collection => ToCollectionItem(collection))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return new CatalogList<QuestionCollectionManagementItem>(items);
|
||||
}
|
||||
|
||||
public async Task<ContentManagementResult<QuestionCollectionManagementItem>> UpsertCollectionAsync(
|
||||
ContentManagementActor actor,
|
||||
UpsertQuestionCollectionCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
|
||||
await AssertRegionAsync(actor.TenantId, command.RegionId, cancellationToken);
|
||||
await AssertEntryAsync(actor, scope, command.EntryId, cancellationToken);
|
||||
await AssertNodeAsync(actor, scope, command.NodeId, cancellationToken);
|
||||
await AssertReferenceAsync<Subject>(actor.TenantId, command.SubjectId, "subject_not_found", cancellationToken);
|
||||
await AssertReferenceAsync<Category>(actor.TenantId, command.CategoryId, "category_not_found",
|
||||
cancellationToken);
|
||||
await AssertReferenceAsync<QuestionBank>(actor.TenantId, command.QuestionBankId, "question_bank_not_found",
|
||||
cancellationToken);
|
||||
|
||||
var collection = await ResolveEntityByIdOrLegacyAsync(
|
||||
questionBankPersistence.QuestionCollections,
|
||||
actor.TenantId,
|
||||
command.Id,
|
||||
command.LegacyId,
|
||||
cancellationToken);
|
||||
|
||||
var isNew = collection is null;
|
||||
if (command.Id.HasValue && (collection is null || collection.Id != command.Id.Value))
|
||||
throw new ContentManagementException("Collection was not found.", "collection_not_found");
|
||||
|
||||
if (collection is not null && !scope.AllowsResource(actor.UserId, collection.CreatedBy, collection.RegionId))
|
||||
throw new ContentManagementException("Collection was not found.", "collection_not_found");
|
||||
|
||||
if (collection is null && !scope.AllowsResource(actor.UserId, actor.UserId, command.RegionId))
|
||||
throw new ContentManagementException("Collection was not found.", "collection_not_found");
|
||||
|
||||
collection ??= new QuestionCollection
|
||||
{
|
||||
Id = command.Id ?? Guid.NewGuid(),
|
||||
TenantId = actor.TenantId,
|
||||
CreatedBy = actor.UserId
|
||||
};
|
||||
|
||||
collection.RegionId = command.RegionId;
|
||||
collection.EntryId = command.EntryId;
|
||||
collection.NodeId = command.NodeId;
|
||||
collection.SubjectId = command.SubjectId;
|
||||
collection.CategoryId = command.CategoryId;
|
||||
collection.QuestionBankId = command.QuestionBankId;
|
||||
collection.LegacyId = Normalize(command.LegacyId);
|
||||
collection.Name = command.Name.Trim();
|
||||
collection.CollectionType =
|
||||
Parse(command.CollectionType, QuestionCollectionType.Dynamic, "collection_type_invalid");
|
||||
collection.SourceType = Parse(command.SourceType, QuestionCollectionSourceType.Filters,
|
||||
"collection_source_type_invalid");
|
||||
collection.Filters = JsonObjectOrDefault(command.Filters);
|
||||
collection.TotalScore = command.TotalScore;
|
||||
collection.DurationMinutes = command.DurationMinutes;
|
||||
collection.Status = Parse(command.Status, ContentStatus.Active, "content_status_invalid");
|
||||
collection.SortOrder = command.Order ?? 0;
|
||||
collection.AccessRules = JsonObjectOrDefault(command.AccessRules);
|
||||
collection.Metadata = JsonObjectOrDefault(command.Metadata);
|
||||
collection.UpdatedBy = actor.UserId;
|
||||
|
||||
if (isNew) questionBankPersistence.QuestionCollections.Add(collection);
|
||||
|
||||
await questionBankPersistence.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<QuestionCollectionManagementItem>(ToCollectionItem(collection));
|
||||
}
|
||||
|
||||
public async Task<CollectionItemsReplaceResult> ReplaceCollectionItemsAsync(
|
||||
ContentManagementActor actor,
|
||||
ReplaceCollectionItemsCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
||||
var regionIds = scope.RegionIds.ToArray();
|
||||
var collection = await questionBankPersistence.QuestionCollections
|
||||
.Where(item => item.TenantId == actor.TenantId && item.Id == command.CollectionId)
|
||||
.ApplyDataScope(
|
||||
scope,
|
||||
item => item.CreatedBy == actor.UserId,
|
||||
item => item.RegionId.HasValue && regionIds.Contains(item.RegionId.Value))
|
||||
.SingleOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (collection is null)
|
||||
throw new ContentManagementException("Collection was not found.", "collection_not_found");
|
||||
|
||||
var resolvedQuestions = new List<(CollectionQuestionCommand Command, TenantQuestionReference Reference)>();
|
||||
foreach (var question in command.Questions)
|
||||
{
|
||||
var reference = await questionReferenceService.ResolveAsync(
|
||||
actor.TenantId,
|
||||
actor.UserId,
|
||||
question.Locator,
|
||||
cancellationToken);
|
||||
resolvedQuestions.Add((question, reference));
|
||||
}
|
||||
|
||||
var oldItems = await questionBankPersistence.QuestionCollectionItems
|
||||
.Where(item => item.TenantId == actor.TenantId && item.CollectionId == command.CollectionId)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
questionBankPersistence.QuestionCollectionItems.RemoveRange(oldItems);
|
||||
|
||||
var items = resolvedQuestions
|
||||
.Select((resolved, index) => new QuestionCollectionItem
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
CollectionId = command.CollectionId,
|
||||
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();
|
||||
|
||||
questionBankPersistence.QuestionCollectionItems.AddRange(items);
|
||||
collection.QuestionCount = items.Length;
|
||||
collection.UpdatedBy = actor.UserId;
|
||||
await questionBankPersistence.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new CollectionItemsReplaceResult(
|
||||
command.CollectionId,
|
||||
collection.QuestionCount,
|
||||
items.Select(ToCollectionItemItem).ToArray());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user