137 lines
6.0 KiB
C#
137 lines
6.0 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Tiku.Application.Catalog;
|
|
using Tiku.Application.Content;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Infrastructure.Security;
|
|
|
|
namespace Tiku.Infrastructure.Content;
|
|
|
|
public sealed partial class ContentManagementService
|
|
{
|
|
public async Task<CatalogList<ContentNodeManagementItem>> GetNodesAsync(
|
|
ContentManagementActor actor,
|
|
ContentManagementFilter filter,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
|
if (!filter.EntryId.HasValue) throw new ContentManagementException("entryId is required.", "entry_id_required");
|
|
|
|
await AssertEntryAsync(actor, scope, filter.EntryId, cancellationToken);
|
|
var regionIds = scope.RegionIds.ToArray();
|
|
var query = dbContext.ContentNodes
|
|
.AsNoTracking()
|
|
.Where(node => node.TenantId == actor.TenantId && node.EntryId == filter.EntryId.Value)
|
|
.ApplyDataScope(
|
|
scope,
|
|
node => node.CreatedBy == actor.UserId,
|
|
node => node.RegionId.HasValue && regionIds.Contains(node.RegionId.Value));
|
|
|
|
if (!filter.IncludeInactive) query = query.Where(node => node.IsActive);
|
|
|
|
if (filter.RegionId.HasValue) query = query.Where(node => node.RegionId == filter.RegionId.Value);
|
|
|
|
if (filter.ParentId is not null)
|
|
{
|
|
if (string.Equals(filter.ParentId, "root", StringComparison.OrdinalIgnoreCase))
|
|
query = query.Where(node => node.ParentId == null);
|
|
else if (Guid.TryParse(filter.ParentId, out var parentId))
|
|
query = query.Where(node => node.ParentId == parentId);
|
|
}
|
|
|
|
if (TryParse(filter.MarkerType, out ContentMarkerType markerType))
|
|
query = query.Where(node => node.MarkerType == markerType);
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
|
{
|
|
var keyword = filter.Keyword.Trim();
|
|
query = query.Where(node =>
|
|
node.Name.Contains(keyword) ||
|
|
(node.NodeKey != null && node.NodeKey.Contains(keyword)));
|
|
}
|
|
|
|
query = string.Equals(filter.Mode, "flat", StringComparison.OrdinalIgnoreCase)
|
|
? query.OrderBy(node => node.Path).ThenBy(node => node.SortOrder)
|
|
: query.OrderBy(node => node.SortOrder).ThenBy(node => node.CreatedAt);
|
|
|
|
var items = await query
|
|
.Take(ResolveLimit(filter.Limit))
|
|
.Select(node => ToNodeItem(node))
|
|
.ToArrayAsync(cancellationToken);
|
|
|
|
return new CatalogList<ContentNodeManagementItem>(items);
|
|
}
|
|
|
|
public async Task<ContentManagementResult<ContentNodeManagementItem>> UpsertNodeAsync(
|
|
ContentManagementActor actor,
|
|
UpsertContentNodeCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var scope = await RequireDataScopeAsync(actor, cancellationToken);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
|
|
await AssertEntryAsync(actor, scope, command.EntryId, cancellationToken);
|
|
await AssertRegionAsync(actor.TenantId, command.RegionId, cancellationToken);
|
|
await AssertNodeAsync(actor, scope, command.ParentId, cancellationToken);
|
|
|
|
var nodeKey = Normalize(command.NodeKey) ??
|
|
Normalize(command.Id?.ToString("N")) ??
|
|
Guid.NewGuid().ToString("N");
|
|
var node = await ResolveEntityAsync(
|
|
dbContext.ContentNodes,
|
|
actor.TenantId,
|
|
command.Id,
|
|
item => item.EntryId == command.EntryId && item.NodeKey == nodeKey,
|
|
cancellationToken);
|
|
|
|
var isNew = node is null;
|
|
if (command.Id.HasValue && (node is null || node.Id != command.Id.Value))
|
|
throw new ContentManagementException("Content node was not found.", "node_not_found");
|
|
|
|
if (node is not null && !scope.AllowsResource(actor.UserId, node.CreatedBy, node.RegionId))
|
|
throw new ContentManagementException("Content node was not found.", "node_not_found");
|
|
|
|
if (node is null && !scope.AllowsResource(actor.UserId, actor.UserId, command.RegionId))
|
|
throw new ContentManagementException("Content node was not found.", "node_not_found");
|
|
|
|
node ??= new ContentNode
|
|
{
|
|
Id = command.Id ?? Guid.NewGuid(),
|
|
TenantId = actor.TenantId,
|
|
EntryId = command.EntryId,
|
|
NodeKey = nodeKey,
|
|
CreatedBy = actor.UserId
|
|
};
|
|
|
|
var path = await BuildNodePathAsync(actor.TenantId, command.EntryId, node.Id, command.ParentId,
|
|
cancellationToken);
|
|
node.EntryId = command.EntryId;
|
|
node.RegionId = command.RegionId;
|
|
node.ParentId = command.ParentId;
|
|
node.LegacyId = Normalize(command.LegacyId);
|
|
node.Name = command.Name.Trim();
|
|
node.NodeType = Parse(command.NodeType, ContentNodeType.Category, "node_type_invalid");
|
|
node.MarkerType = ParseNullable<ContentMarkerType>(command.MarkerType, "marker_type_invalid");
|
|
node.MarkerConfig = JsonObjectOrDefault(command.MarkerConfig);
|
|
node.Path = path.Path;
|
|
node.Depth = path.Depth;
|
|
node.SortOrder = command.Order ?? 0;
|
|
node.IsActive = command.IsActive ?? true;
|
|
node.IsSelectable = command.IsSelectable ?? true;
|
|
node.IsLeaf = command.IsLeaf ?? false;
|
|
node.AccessRules = JsonObjectOrDefault(command.AccessRules);
|
|
node.Metadata = JsonObjectOrDefault(command.Metadata);
|
|
node.UpdatedBy = actor.UserId;
|
|
|
|
if (isNew) dbContext.ContentNodes.Add(node);
|
|
|
|
if (command.ParentId.HasValue)
|
|
{
|
|
var parent = await dbContext.ContentNodes.SingleOrDefaultAsync(
|
|
item => item.TenantId == actor.TenantId && item.Id == command.ParentId.Value,
|
|
cancellationToken);
|
|
if (parent is not null) parent.IsLeaf = false;
|
|
}
|
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
return new ContentManagementResult<ContentNodeManagementItem>(ToNodeItem(node));
|
|
}
|
|
} |