forked from xiongyuxing/tiku-backend.net
155 lines
6.4 KiB
C#
155 lines
6.4 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Application.Catalog;
|
|
using Tiku.Application.QuestionBanks;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Domain.Catalog;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.Infrastructure.Catalog;
|
|
|
|
public sealed class TaxonomyService(
|
|
TikuDbContext dbContext,
|
|
IPublicQuestionAccessPolicy accessPolicy,
|
|
ITenantExecutionScope tenantExecutionScope) : ITaxonomyService
|
|
{
|
|
public async Task<IReadOnlyCollection<TaxonomyNodeItem>> ListAsync(
|
|
Guid tenantId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var includePlatform = true;
|
|
try
|
|
{
|
|
await accessPolicy.EnsureCanStartAsync(tenantId, cancellationToken);
|
|
}
|
|
catch (PublicQuestionAccessDeniedException)
|
|
{
|
|
includePlatform = false;
|
|
}
|
|
|
|
return await tenantExecutionScope.ExecuteAsync(
|
|
new SystemScopeRequest(
|
|
tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(TaxonomyService),
|
|
"List platform taxonomy with tenant extensions", Guid.NewGuid().ToString("N")),
|
|
async (provider, token) =>
|
|
{
|
|
var systemDbContext = provider.GetRequiredService<TikuDbContext>();
|
|
var platformTenantId = includePlatform
|
|
? await systemDbContext.Tenants.AsNoTracking()
|
|
.Where(tenant => tenant.Mode == TenantMode.PlatformOwned)
|
|
.Select(tenant => (Guid?)tenant.Id)
|
|
.SingleOrDefaultAsync(token)
|
|
: null;
|
|
return await systemDbContext.TaxonomyNodes.AsNoTracking()
|
|
.Where(node =>
|
|
node.IsActive &&
|
|
(node.TenantId == tenantId ||
|
|
(platformTenantId.HasValue && node.TenantId == platformTenantId.Value)))
|
|
.OrderBy(node => node.Depth)
|
|
.ThenBy(node => node.SortOrder)
|
|
.Select(node => new TaxonomyNodeItem(
|
|
node.Id,
|
|
node.TenantId == tenantId ? QuestionSource.Tenant : QuestionSource.Platform,
|
|
node.ParentId,
|
|
!node.ParentOwnerTenantId.HasValue
|
|
? null
|
|
: node.ParentOwnerTenantId == tenantId
|
|
? QuestionSource.Tenant
|
|
: QuestionSource.Platform,
|
|
node.NodeType,
|
|
node.Code,
|
|
node.Name,
|
|
node.Path,
|
|
node.Depth,
|
|
node.SortOrder,
|
|
node.Metadata))
|
|
.ToArrayAsync(token);
|
|
},
|
|
cancellationToken);
|
|
}
|
|
|
|
public async Task<TaxonomyNodeItem> CreateAsync(
|
|
Guid tenantId,
|
|
CreateTaxonomyNodeCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Guid? parentOwnerTenantId = null;
|
|
TaxonomyParent? parent = null;
|
|
if (command.ParentId.HasValue)
|
|
{
|
|
parentOwnerTenantId = command.ParentSource switch
|
|
{
|
|
QuestionSource.Tenant => tenantId,
|
|
QuestionSource.Platform => await ResolvePlatformTenantIdAsync(tenantId, cancellationToken),
|
|
_ => throw new InvalidOperationException("A parent source is required when parentId is provided.")
|
|
};
|
|
parent = await tenantExecutionScope.ExecuteAsync(
|
|
new SystemScopeRequest(
|
|
tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(TaxonomyService),
|
|
"Validate taxonomy extension parent ownership", Guid.NewGuid().ToString("N")),
|
|
async (provider, token) =>
|
|
{
|
|
var systemDbContext = provider.GetRequiredService<TikuDbContext>();
|
|
return await systemDbContext.TaxonomyNodes.AsNoTracking()
|
|
.Where(node =>
|
|
node.TenantId == parentOwnerTenantId &&
|
|
node.Id == command.ParentId.Value &&
|
|
node.IsActive)
|
|
.Select(node => new TaxonomyParent(node.Path, node.Depth))
|
|
.SingleOrDefaultAsync(token);
|
|
},
|
|
cancellationToken)
|
|
?? throw new InvalidOperationException("Taxonomy parent was not found.");
|
|
}
|
|
|
|
var node = new TaxonomyNode
|
|
{
|
|
TenantId = tenantId,
|
|
ParentOwnerTenantId = parentOwnerTenantId,
|
|
ParentId = command.ParentId,
|
|
NodeType = command.NodeType,
|
|
Code = command.Code.Trim(),
|
|
Name = command.Name.Trim(),
|
|
Depth = parent is null ? 0 : parent.Depth + 1,
|
|
SortOrder = command.SortOrder,
|
|
Metadata = command.Metadata.Clone()
|
|
};
|
|
node.Path = parent is null
|
|
? $"n{node.Id:N}"
|
|
: $"{parent.Path}.n{node.Id:N}";
|
|
dbContext.TaxonomyNodes.Add(node);
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
return new TaxonomyNodeItem(
|
|
node.Id,
|
|
QuestionSource.Tenant,
|
|
node.ParentId,
|
|
node.ParentId.HasValue ? command.ParentSource : null,
|
|
node.NodeType,
|
|
node.Code,
|
|
node.Name,
|
|
node.Path,
|
|
node.Depth,
|
|
node.SortOrder,
|
|
node.Metadata);
|
|
}
|
|
|
|
private async Task<Guid> ResolvePlatformTenantIdAsync(Guid tenantId, CancellationToken cancellationToken)
|
|
{
|
|
await accessPolicy.EnsureCanStartAsync(tenantId, cancellationToken);
|
|
return await tenantExecutionScope.ExecuteAsync(
|
|
new SystemScopeRequest(
|
|
tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(TaxonomyService),
|
|
"Resolve platform taxonomy owner", Guid.NewGuid().ToString("N")),
|
|
async (provider, token) => await provider.GetRequiredService<TikuDbContext>()
|
|
.Tenants.AsNoTracking()
|
|
.Where(tenant => tenant.Mode == TenantMode.PlatformOwned)
|
|
.Select(tenant => tenant.Id)
|
|
.SingleAsync(token),
|
|
cancellationToken);
|
|
}
|
|
|
|
private sealed record TaxonomyParent(string? Path, int Depth);
|
|
}
|