mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
308 changed files with 4755 additions and 3177 deletions
@ -0,0 +1,38 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Domain.Apps.Core.Assets; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public sealed class BulkUpdateJob |
|||
{ |
|||
public BulkUpdateAssetType Type { get; set; } |
|||
|
|||
public DomainId Id { get; set; } |
|||
|
|||
public DomainId ParentId { get; set; } |
|||
|
|||
public string? ParentPath { get; set; } |
|||
|
|||
public string? FileName { get; set; } |
|||
|
|||
public string? Slug { get; set; } |
|||
|
|||
public bool? IsProtected { get; set; } |
|||
|
|||
public bool Permanent { get; set; } |
|||
|
|||
public HashSet<string> Tags { get; set; } |
|||
|
|||
public AssetMetadata? Metadata { get; set; } |
|||
|
|||
public long ExpectedVersion { get; set; } = EtagVersion.Any; |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public sealed class UpsertAsset : UploadAssetCommand |
|||
{ |
|||
public DomainId? ParentId { get; set; } |
|||
|
|||
public string? ParentPath { get; set; } |
|||
|
|||
public UpsertAsset() |
|||
{ |
|||
AssetId = DomainId.NewGuid(); |
|||
} |
|||
|
|||
public CreateAsset AsCreate() |
|||
{ |
|||
return SimpleMapper.Map(this, new CreateAsset()); |
|||
} |
|||
|
|||
public UpdateAsset AsUpdate() |
|||
{ |
|||
return SimpleMapper.Map(this, new UpdateAsset()); |
|||
} |
|||
|
|||
public MoveAsset AsMove(DomainId parentId) |
|||
{ |
|||
return SimpleMapper.Map(this, new MoveAsset { ParentId = parentId }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Caching; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject |
|||
{ |
|||
public sealed class AssetFolderResolver : IAssetFolderResolver |
|||
{ |
|||
private static readonly char[] TrimChars = { '/', '\\' }; |
|||
private static readonly char[] SplitChars = { ' ', '/', '\\' }; |
|||
private readonly ILocalCache localCache; |
|||
private readonly IAssetQueryService assetQuery; |
|||
|
|||
public AssetFolderResolver(ILocalCache localCache, IAssetQueryService assetQuery) |
|||
{ |
|||
Guard.NotNull(localCache, nameof(localCache)); |
|||
Guard.NotNull(assetQuery, nameof(assetQuery)); |
|||
|
|||
this.localCache = localCache; |
|||
this.assetQuery = assetQuery; |
|||
} |
|||
|
|||
public async Task<DomainId> ResolveOrCreateAsync(Context context, ICommandBus commandBus, string path) |
|||
{ |
|||
Guard.NotNull(commandBus, nameof(commandBus)); |
|||
Guard.NotNull(path, nameof(path)); |
|||
|
|||
path = path.Trim(TrimChars); |
|||
|
|||
var elements = path.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries); |
|||
|
|||
if (elements.Length == 0) |
|||
{ |
|||
return DomainId.Empty; |
|||
} |
|||
|
|||
var currentId = DomainId.Empty; |
|||
|
|||
var i = elements.Length; |
|||
|
|||
for (; i > 0; i--) |
|||
{ |
|||
var subPath = string.Join('/', elements.Take(i)); |
|||
|
|||
if (localCache.TryGetValue(GetCacheKey(subPath), out var cached) && cached is DomainId id) |
|||
{ |
|||
currentId = id; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
var creating = false; |
|||
|
|||
for (; i < elements.Length; i++) |
|||
{ |
|||
var name = elements[i]; |
|||
|
|||
var isResolved = false; |
|||
|
|||
if (!creating) |
|||
{ |
|||
var children = await assetQuery.QueryAssetFoldersAsync(context, currentId); |
|||
|
|||
foreach (var child in children) |
|||
{ |
|||
var childPath = string.Join('/', elements.Take(i).Union(Enumerable.Repeat(child.FolderName, 1))); |
|||
|
|||
localCache.Add(GetCacheKey(childPath), child.Id); |
|||
} |
|||
|
|||
foreach (var child in children) |
|||
{ |
|||
if (child.FolderName == name) |
|||
{ |
|||
currentId = child.Id; |
|||
|
|||
isResolved = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!isResolved) |
|||
{ |
|||
var command = new CreateAssetFolder { ParentId = currentId, FolderName = name }; |
|||
|
|||
await commandBus.PublishAsync(command); |
|||
|
|||
currentId = command.AssetFolderId; |
|||
creating = true; |
|||
} |
|||
|
|||
var newPath = string.Join('/', elements.Take(i).Union(Enumerable.Repeat(name, 1))); |
|||
|
|||
localCache.Add(GetCacheKey(newPath), currentId); |
|||
} |
|||
|
|||
return currentId; |
|||
} |
|||
|
|||
private static object GetCacheKey(string path) |
|||
{ |
|||
return $"ASSET_FOLDERS_{path}"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,210 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Threading.Tasks; |
|||
using System.Threading.Tasks.Dataflow; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Domain.Apps.Entities.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Shared; |
|||
|
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
#pragma warning disable RECS0082 // Parameter has the same name as a member and hides it
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject |
|||
{ |
|||
public sealed class AssetsBulkUpdateCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IContextProvider contextProvider; |
|||
|
|||
private sealed record BulkTaskCommand(BulkTask Task, DomainId Id, ICommand Command) |
|||
{ |
|||
} |
|||
|
|||
private sealed record BulkTask( |
|||
ICommandBus Bus, |
|||
int JobIndex, |
|||
BulkUpdateJob Job, |
|||
BulkUpdateAssets Command, |
|||
ConcurrentBag<BulkUpdateResultItem> Results |
|||
) |
|||
{ |
|||
} |
|||
|
|||
public AssetsBulkUpdateCommandMiddleware(IContextProvider contextProvider) |
|||
{ |
|||
Guard.NotNull(contextProvider, nameof(contextProvider)); |
|||
|
|||
this.contextProvider = contextProvider; |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, NextDelegate next) |
|||
{ |
|||
if (context.Command is BulkUpdateAssets bulkUpdates) |
|||
{ |
|||
if (bulkUpdates.Jobs?.Length > 0) |
|||
{ |
|||
var executionOptions = new ExecutionDataflowBlockOptions |
|||
{ |
|||
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2) |
|||
}; |
|||
|
|||
var createCommandsBlock = new TransformBlock<BulkTask, BulkTaskCommand?>(task => |
|||
{ |
|||
return CreateCommand(task); |
|||
}, executionOptions); |
|||
|
|||
var executeCommandBlock = new ActionBlock<BulkTaskCommand?>(async command => |
|||
{ |
|||
if (command != null) |
|||
{ |
|||
await ExecuteCommandAsync(command); |
|||
} |
|||
}, executionOptions); |
|||
|
|||
createCommandsBlock.LinkTo(executeCommandBlock, new DataflowLinkOptions |
|||
{ |
|||
PropagateCompletion = true |
|||
}); |
|||
|
|||
contextProvider.Context.Change(b => b |
|||
.WithoutAssetEnrichment() |
|||
.WithoutCleanup() |
|||
.WithUnpublished(true) |
|||
.WithoutTotal()); |
|||
|
|||
var results = new ConcurrentBag<BulkUpdateResultItem>(); |
|||
|
|||
for (var i = 0; i < bulkUpdates.Jobs.Length; i++) |
|||
{ |
|||
var task = new BulkTask( |
|||
context.CommandBus, |
|||
i, |
|||
bulkUpdates.Jobs[i], |
|||
bulkUpdates, |
|||
results); |
|||
|
|||
await createCommandsBlock.SendAsync(task); |
|||
} |
|||
|
|||
createCommandsBlock.Complete(); |
|||
|
|||
await executeCommandBlock.Completion; |
|||
|
|||
context.Complete(new BulkUpdateResult(results)); |
|||
} |
|||
else |
|||
{ |
|||
context.Complete(new BulkUpdateResult()); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await next(context); |
|||
} |
|||
} |
|||
|
|||
private static async Task ExecuteCommandAsync(BulkTaskCommand bulkCommand) |
|||
{ |
|||
var (task, id, command) = bulkCommand; |
|||
|
|||
Exception? exception = null; |
|||
try |
|||
{ |
|||
await task.Bus.PublishAsync(command); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
exception = ex; |
|||
} |
|||
|
|||
task.Results.Add(new BulkUpdateResultItem |
|||
{ |
|||
Id = id, |
|||
JobIndex = task.JobIndex, |
|||
Exception = exception |
|||
}); |
|||
} |
|||
|
|||
private BulkTaskCommand? CreateCommand(BulkTask task) |
|||
{ |
|||
var id = task.Job.Id; |
|||
|
|||
try |
|||
{ |
|||
var command = CreateCommandCore(task); |
|||
|
|||
command.AssetId = id; |
|||
|
|||
return new BulkTaskCommand(task, id, command); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
task.Results.Add(new BulkUpdateResultItem |
|||
{ |
|||
Id = id, |
|||
JobIndex = task.JobIndex, |
|||
Exception = ex |
|||
}); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private AssetCommand CreateCommandCore(BulkTask task) |
|||
{ |
|||
var job = task.Job; |
|||
|
|||
switch (job.Type) |
|||
{ |
|||
case BulkUpdateAssetType.Annotate: |
|||
{ |
|||
var command = new AnnotateAsset(); |
|||
|
|||
Enrich(task, command, Permissions.AppAssetsUpdate); |
|||
return command; |
|||
} |
|||
|
|||
case BulkUpdateAssetType.Move: |
|||
{ |
|||
var command = new MoveAsset(); |
|||
|
|||
Enrich(task, command, Permissions.AppAssetsUpdate); |
|||
return command; |
|||
} |
|||
|
|||
case BulkUpdateAssetType.Delete: |
|||
{ |
|||
var command = new DeleteAsset(); |
|||
|
|||
Enrich(task, command, Permissions.AppAssetsDelete); |
|||
return command; |
|||
} |
|||
|
|||
default: |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
|
|||
private void Enrich<T>(BulkTask task, T command, string permissionId) where T : AssetCommand |
|||
{ |
|||
SimpleMapper.Map(task.Command, command); |
|||
SimpleMapper.Map(task.Job, command); |
|||
|
|||
if (!contextProvider.Context.Allows(permissionId)) |
|||
{ |
|||
throw new DomainForbiddenException("Forbidden"); |
|||
} |
|||
|
|||
command.ExpectedVersion = task.Command.ExpectedVersion; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue