@ -0,0 +1,65 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Entities.Assets; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets |
|||
{ |
|||
public sealed class MongoAssetFolderEntity : IAssetFolderEntity |
|||
{ |
|||
[BsonId] |
|||
[BsonElement("_id")] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("_ai")] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public Guid IndexedAppId { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("pi")] |
|||
public Guid ParentId { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("ct")] |
|||
public Instant Created { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("mt")] |
|||
public Instant LastModified { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("ai")] |
|||
public NamedId<Guid> AppId { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("fn")] |
|||
public string FolderName { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("vs")] |
|||
public long Version { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("cb")] |
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("mb")] |
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement("dl")] |
|||
public bool IsDeleted { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Entities.Assets; |
|||
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets |
|||
{ |
|||
public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase<MongoAssetFolderEntity>, IAssetFolderRepository |
|||
{ |
|||
public MongoAssetFolderRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "States_AssetFolders"; |
|||
} |
|||
|
|||
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetFolderEntity> collection, CancellationToken ct = default) |
|||
{ |
|||
return collection.Indexes.CreateManyAsync(new[] |
|||
{ |
|||
new CreateIndexModel<MongoAssetFolderEntity>( |
|||
Index |
|||
.Ascending(x => x.IndexedAppId) |
|||
.Ascending(x => x.IsDeleted) |
|||
.Ascending(x => x.ParentId)) |
|||
}, ct); |
|||
} |
|||
|
|||
public async Task<IResultList<IAssetFolderEntity>> QueryAsync(Guid appId, Guid parentId) |
|||
{ |
|||
using (Profiler.TraceMethod<MongoAssetFolderRepository>("QueryAsyncByQuery")) |
|||
{ |
|||
var assetFolders = |
|||
await Collection |
|||
.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).SortBy(x => x.FolderName) |
|||
.ToListAsync(); |
|||
|
|||
return ResultList.Create<IAssetFolderEntity>(assetFolders.Count, assetFolders); |
|||
} |
|||
} |
|||
|
|||
public async Task<IAssetFolderEntity?> FindAssetFolderAsync(Guid id) |
|||
{ |
|||
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) |
|||
{ |
|||
var assetFolderEntity = |
|||
await Collection.Find(x => x.Id == id) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (assetFolderEntity?.IsDeleted == true) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return assetFolderEntity; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Entities.Assets.State; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets |
|||
{ |
|||
public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFolderState, Guid> |
|||
{ |
|||
async Task<(AssetFolderState Value, long Version)> ISnapshotStore<AssetFolderState, Guid>.ReadAsync(Guid key) |
|||
{ |
|||
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) |
|||
{ |
|||
var existing = |
|||
await Collection.Find(x => x.Id == key) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existing != null) |
|||
{ |
|||
return (Map(existing), existing.Version); |
|||
} |
|||
|
|||
return (null!, EtagVersion.NotFound); |
|||
} |
|||
} |
|||
|
|||
async Task ISnapshotStore<AssetFolderState, Guid>.WriteAsync(Guid key, AssetFolderState value, long oldVersion, long newVersion) |
|||
{ |
|||
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) |
|||
{ |
|||
var entity = SimpleMapper.Map(value, new MongoAssetFolderEntity()); |
|||
|
|||
entity.Version = newVersion; |
|||
entity.IndexedAppId = value.AppId.Id; |
|||
|
|||
await Collection.ReplaceOneAsync(x => x.Id == key && x.Version == oldVersion, entity, Upsert); |
|||
} |
|||
} |
|||
|
|||
async Task ISnapshotStore<AssetFolderState, Guid>.ReadAllAsync(Func<AssetFolderState, long, Task> callback, CancellationToken ct) |
|||
{ |
|||
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) |
|||
{ |
|||
await Collection.Find(new BsonDocument(), options: Batching.Options).ForEachPipelineAsync(x => callback(Map(x), x.Version), ct); |
|||
} |
|||
} |
|||
|
|||
async Task ISnapshotStore<AssetFolderState, Guid>.RemoveAsync(Guid key) |
|||
{ |
|||
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) |
|||
{ |
|||
await Collection.DeleteOneAsync(x => x.Id == key); |
|||
} |
|||
} |
|||
|
|||
private static AssetFolderState Map(MongoAssetFolderEntity existing) |
|||
{ |
|||
return SimpleMapper.Map(existing, new AssetFolderState()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Domain.Apps.Entities.Assets.Guards; |
|||
using Squidex.Domain.Apps.Entities.Assets.State; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public sealed class AssetFolderGrain : DomainObjectGrain<AssetFolderState>, IAssetFolderGrain |
|||
{ |
|||
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); |
|||
private readonly IAssetQueryService assetQuery; |
|||
|
|||
public AssetFolderGrain(IStore<Guid> store, IAssetQueryService assetQuery, IActivationLimit limit, ISemanticLog log) |
|||
: base(store, log) |
|||
{ |
|||
Guard.NotNull(assetQuery); |
|||
|
|||
this.assetQuery = assetQuery; |
|||
|
|||
limit?.SetLimit(5000, Lifetime); |
|||
} |
|||
|
|||
protected override Task OnActivateAsync(Guid key) |
|||
{ |
|||
TryDelayDeactivation(Lifetime); |
|||
|
|||
return base.OnActivateAsync(key); |
|||
} |
|||
|
|||
protected override Task<object?> ExecuteAsync(IAggregateCommand command) |
|||
{ |
|||
VerifyNotDeleted(); |
|||
|
|||
switch (command) |
|||
{ |
|||
case CreateAssetFolder createAssetFolder: |
|||
return CreateReturnAsync(createAssetFolder, async c => |
|||
{ |
|||
await GuardAssetFolder.CanCreate(c, assetQuery); |
|||
|
|||
Create(c); |
|||
|
|||
return Snapshot; |
|||
}); |
|||
case MoveAssetFolder moveAssetFolder: |
|||
return UpdateReturnAsync(moveAssetFolder, async c => |
|||
{ |
|||
await GuardAssetFolder.CanMove(c, assetQuery, Snapshot.Id, Snapshot.ParentId); |
|||
|
|||
Move(c); |
|||
|
|||
return Snapshot; |
|||
}); |
|||
case RenameAssetFolder renameAssetFolder: |
|||
return UpdateReturn(renameAssetFolder, c => |
|||
{ |
|||
GuardAssetFolder.CanRename(c, Snapshot.FolderName); |
|||
|
|||
Rename(c); |
|||
|
|||
return Snapshot; |
|||
}); |
|||
case DeleteAssetFolder deleteAssetFolder: |
|||
return Update(deleteAssetFolder, c => |
|||
{ |
|||
GuardAssetFolder.CanDelete(c); |
|||
|
|||
Delete(c); |
|||
}); |
|||
default: |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
|
|||
public void Create(CreateAssetFolder command) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new AssetFolderCreated())); |
|||
} |
|||
|
|||
public void Move(MoveAssetFolder command) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new AssetFolderMoved())); |
|||
} |
|||
|
|||
public void Rename(RenameAssetFolder command) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new AssetFolderRenamed())); |
|||
} |
|||
|
|||
public void Delete(DeleteAssetFolder command) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new AssetFolderDeleted())); |
|||
} |
|||
|
|||
private void RaiseEvent(AppEvent @event) |
|||
{ |
|||
if (@event.AppId == null) |
|||
{ |
|||
@event.AppId = Snapshot.AppId; |
|||
} |
|||
|
|||
RaiseEvent(Envelope.Create(@event)); |
|||
} |
|||
|
|||
private void VerifyNotDeleted() |
|||
{ |
|||
if (Snapshot.IsDeleted) |
|||
{ |
|||
throw new DomainException("Asset folder has already been deleted"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public abstract class AssetFolderCommand : SquidexCommand, IAggregateCommand |
|||
{ |
|||
public Guid AssetFolderId { get; set; } |
|||
|
|||
Guid IAggregateCommand.AggregateId |
|||
{ |
|||
get { return AssetFolderId; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public sealed class CreateAssetFolder : AssetFolderCommand, IAppCommand |
|||
{ |
|||
public NamedId<Guid> AppId { get; set; } |
|||
|
|||
public string FolderName { get; set; } |
|||
|
|||
public Guid ParentId { get; set; } |
|||
|
|||
public CreateAssetFolder() |
|||
{ |
|||
AssetFolderId = Guid.NewGuid(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public sealed class DeleteAssetFolder : AssetFolderCommand |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public sealed class MoveAsset : AssetCommand |
|||
{ |
|||
public Guid ParentId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public sealed class MoveAssetFolder : AssetFolderCommand |
|||
{ |
|||
public Guid ParentId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public sealed class RenameAssetFolder : AssetFolderCommand |
|||
{ |
|||
public string FolderName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Guards |
|||
{ |
|||
public static class GuardAssetFolder |
|||
{ |
|||
public static Task CanCreate(CreateAssetFolder command, IAssetQueryService assetQuery) |
|||
{ |
|||
Guard.NotNull(command); |
|||
|
|||
return Validate.It(() => "Cannot upload asset.", async e => |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(command.FolderName)) |
|||
{ |
|||
e(Not.Defined("Folder name"), nameof(command.FolderName)); |
|||
} |
|||
|
|||
await CheckPathAsync(command.ParentId, assetQuery, Guid.Empty, e); |
|||
}); |
|||
} |
|||
|
|||
public static void CanRename(RenameAssetFolder command, string olderFolderName) |
|||
{ |
|||
Guard.NotNull(command); |
|||
|
|||
Validate.It(() => "Cannot rename asset.", e => |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(command.FolderName)) |
|||
{ |
|||
e(Not.Defined("Folder name"), nameof(command.FolderName)); |
|||
} |
|||
else if (string.Equals(command.FolderName, olderFolderName)) |
|||
{ |
|||
e(Not.New("Asset folder", "name"), nameof(command.FolderName)); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static Task CanMove(MoveAssetFolder command, IAssetQueryService assetQuery, Guid id, Guid oldParentId) |
|||
{ |
|||
Guard.NotNull(command); |
|||
|
|||
return Validate.It(() => "Cannot move asset.", async e => |
|||
{ |
|||
if (command.ParentId == oldParentId) |
|||
{ |
|||
e("Asset folder is already part of this folder.", nameof(command.ParentId)); |
|||
} |
|||
else |
|||
{ |
|||
await CheckPathAsync(command.ParentId, assetQuery, id, e); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanDelete(DeleteAssetFolder command) |
|||
{ |
|||
Guard.NotNull(command); |
|||
} |
|||
|
|||
private static async Task CheckPathAsync(Guid parentId, IAssetQueryService assetQuery, Guid id, AddValidation e) |
|||
{ |
|||
if (parentId != default) |
|||
{ |
|||
var path = await assetQuery.FindAssetFolderAsync(parentId); |
|||
|
|||
if (path.Count == 0) |
|||
{ |
|||
e("Asset folder does not exist.", nameof(MoveAssetFolder.ParentId)); |
|||
} |
|||
else if (id != default) |
|||
{ |
|||
var indexOfThis = path.IndexOf(x => x.Id == id); |
|||
var indexOfParent = path.IndexOf(x => x.Id == parentId); |
|||
|
|||
if (indexOfThis >= 0 && indexOfParent > indexOfThis) |
|||
{ |
|||
e("Cannot add folder to its own child.", nameof(MoveAssetFolder.ParentId)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public interface IAssetFolderEntity : IEntity, IEntityWithVersion |
|||
{ |
|||
NamedId<Guid> AppId { get; set; } |
|||
|
|||
string FolderName { get; set; } |
|||
|
|||
Guid ParentId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public interface IAssetFolderGrain : IDomainObjectGrain |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Repositories |
|||
{ |
|||
public interface IAssetFolderRepository |
|||
{ |
|||
Task<IResultList<IAssetFolderEntity>> QueryAsync(Guid appId, Guid parentId); |
|||
|
|||
Task<IAssetFolderEntity?> FindAssetFolderAsync(Guid id); |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Runtime.Serialization; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
#pragma warning disable IDE0060 // Remove unused parameter
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.State |
|||
{ |
|||
public class AssetFolderState : DomainObjectState<AssetFolderState>, IAssetFolderEntity |
|||
{ |
|||
[DataMember] |
|||
public NamedId<Guid> AppId { get; set; } |
|||
|
|||
[DataMember] |
|||
public string FolderName { get; set; } |
|||
|
|||
[DataMember] |
|||
public bool IsDeleted { get; set; } |
|||
|
|||
[DataMember] |
|||
public Guid ParentId { get; set; } |
|||
|
|||
public void ApplyEvent(IEvent @event) |
|||
{ |
|||
switch (@event) |
|||
{ |
|||
case AssetFolderCreated e: |
|||
{ |
|||
SimpleMapper.Map(e, this); |
|||
|
|||
break; |
|||
} |
|||
|
|||
case AssetFolderRenamed e: |
|||
{ |
|||
SimpleMapper.Map(e, this); |
|||
|
|||
break; |
|||
} |
|||
|
|||
case AssetFolderMoved e: |
|||
{ |
|||
ParentId = e.ParentId; |
|||
|
|||
break; |
|||
} |
|||
|
|||
case AssetFolderDeleted _: |
|||
{ |
|||
IsDeleted = true; |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override AssetFolderState Apply(Envelope<IEvent> @event) |
|||
{ |
|||
return Clone().Update(@event, (e, s) => s.ApplyEvent(e)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Assets |
|||
{ |
|||
[EventType(nameof(AssetFolderCreated))] |
|||
public sealed class AssetFolderCreated : AssetFolderEvent |
|||
{ |
|||
public Guid ParentId { get; set; } |
|||
|
|||
public string FolderName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Assets |
|||
{ |
|||
[EventType(nameof(AssetFolderDeleted))] |
|||
public sealed class AssetFolderDeleted : AssetFolderEvent |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Assets |
|||
{ |
|||
public abstract class AssetFolderEvent : AppEvent |
|||
{ |
|||
public Guid AssetFolderId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Assets |
|||
{ |
|||
[EventType(nameof(AssetFolderMoved))] |
|||
public sealed class AssetFolderMoved : AssetFolderEvent |
|||
{ |
|||
public Guid ParentId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Assets |
|||
{ |
|||
[EventType(nameof(AssetFolderRenamed))] |
|||
public sealed class AssetFolderRenamed : AssetFolderEvent |
|||
{ |
|||
public string FolderName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Assets |
|||
{ |
|||
[EventType(nameof(AssetMoved))] |
|||
public sealed class AssetMoved : AssetEvent |
|||
{ |
|||
public Guid ParentId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Net.Http.Headers; |
|||
using Squidex.Areas.Api.Controllers.Assets.Models; |
|||
using Squidex.Domain.Apps.Entities.Assets; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Shared; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Assets |
|||
{ |
|||
/// <summary>
|
|||
/// Uploads and retrieves assets.
|
|||
/// </summary>
|
|||
[ApiExplorerSettings(GroupName = nameof(Assets))] |
|||
public sealed class AssetFoldersController : ApiController |
|||
{ |
|||
private readonly IAssetQueryService assetQuery; |
|||
|
|||
public AssetFoldersController(ICommandBus commandBus, IAssetQueryService assetQuery) |
|||
: base(commandBus) |
|||
{ |
|||
this.assetQuery = assetQuery; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get asset folders.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="parentId">The optional parent folder id.</param>
|
|||
/// <returns>
|
|||
/// 200 => Asset folders returned.
|
|||
/// 404 => App not found.
|
|||
/// </returns>
|
|||
/// <remarks>
|
|||
/// Get all asset folders for the app.
|
|||
/// </remarks>
|
|||
[HttpGet] |
|||
[Route("apps/{app}/assets/folders", Order = -1)] |
|||
[ProducesResponseType(typeof(AssetsDto), 200)] |
|||
[ApiPermission(Permissions.AppAssetsRead)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> GetAssetFolders(string app, [FromQuery] Guid parentId) |
|||
{ |
|||
var assetFolders = await assetQuery.QueryAssetFoldersAsync(Context, parentId); |
|||
|
|||
var response = Deferred.Response(() => |
|||
{ |
|||
return AssetFoldersDto.FromAssets(assetFolders, this, app); |
|||
}); |
|||
|
|||
Response.Headers[HeaderNames.ETag] = assetFolders.ToEtag(); |
|||
|
|||
return Ok(response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Upload a new asset.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="request">The asset folder object that needs to be added to the App.</param>
|
|||
/// <returns>
|
|||
/// 201 => Asset folder created.
|
|||
/// 404 => App not found.
|
|||
/// </returns>
|
|||
[HttpPost] |
|||
[Route("apps/{app}/assets/folders", Order = -1)] |
|||
[ProducesResponseType(typeof(AssetDto), 201)] |
|||
[AssetRequestSizeLimit] |
|||
[ApiPermission(Permissions.AppAssetsUpdate)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> PostAssetFolder(string app, [FromBody] CreateAssetFolderDto request) |
|||
{ |
|||
var command = request.ToCommand(); |
|||
|
|||
var response = await InvokeCommandAsync(app, command); |
|||
|
|||
return Ok(response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the asset folder.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="id">The id of the asset folder.</param>
|
|||
/// <param name="request">The asset folder object that needs to updated.</param>
|
|||
/// <returns>
|
|||
/// 200 => Asset folder updated.
|
|||
/// 400 => Asset folder name not valid.
|
|||
/// 404 => Asset or app not found.
|
|||
/// </returns>
|
|||
[HttpPut] |
|||
[Route("apps/{app}/assets/folders/{id}/", Order = -1)] |
|||
[ProducesResponseType(typeof(AssetDto), 200)] |
|||
[AssetRequestSizeLimit] |
|||
[ApiPermission(Permissions.AppAssetsUpdate)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> PutAssetFolder(string app, Guid id, [FromBody] RenameAssetFolderDto request) |
|||
{ |
|||
var command = request.ToCommand(id); |
|||
|
|||
var response = await InvokeCommandAsync(app, command); |
|||
|
|||
return Ok(response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Moves the asset folder.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="id">The id of the asset folder.</param>
|
|||
/// <param name="request">The asset folder object that needs to updated.</param>
|
|||
/// <returns>
|
|||
/// 200 => Asset folder moved.
|
|||
/// 404 => Asset or app not found.
|
|||
/// </returns>
|
|||
[HttpPut] |
|||
[Route("apps/{app}/assets/folders/{id}/parent", Order = -1)] |
|||
[ProducesResponseType(typeof(AssetDto), 200)] |
|||
[AssetRequestSizeLimit] |
|||
[ApiPermission(Permissions.AppAssetsUpdate)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> PutAssetFolderParent(string app, Guid id, [FromBody] MoveAssetItemDto request) |
|||
{ |
|||
var command = request.ToFolderCommand(id); |
|||
|
|||
var response = await InvokeCommandAsync(app, command); |
|||
|
|||
return Ok(response); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Delete an asset folder.
|
|||
/// </summary>
|
|||
/// <param name="app">The name of the app.</param>
|
|||
/// <param name="id">The id of the asset folder to delete.</param>
|
|||
/// <returns>
|
|||
/// 204 => Asset folder deleted.
|
|||
/// 404 => Asset or app not found.
|
|||
/// </returns>
|
|||
[HttpDelete] |
|||
[Route("apps/{app}/assets/folders/{id}/", Order = -1)] |
|||
[ApiPermission(Permissions.AppAssetsUpdate)] |
|||
[ApiCosts(1)] |
|||
public async Task<IActionResult> DeleteAssetFolder(string app, Guid id) |
|||
{ |
|||
await CommandBus.PublishAsync(new DeleteAssetFolder { AssetFolderId = id }); |
|||
|
|||
return NoContent(); |
|||
} |
|||
|
|||
private async Task<AssetFolderDto> InvokeCommandAsync(string app, ICommand command) |
|||
{ |
|||
var context = await CommandBus.PublishAsync(command); |
|||
|
|||
return AssetFolderDto.FromAssetFolder(context.Result<IAssetFolderEntity>(), this, app); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Entities.Assets; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Shared; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Assets.Models |
|||
{ |
|||
public sealed class AssetFolderDto : Resource |
|||
{ |
|||
/// <summary>
|
|||
/// The id of the asset.
|
|||
/// </summary>
|
|||
public Guid Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The id of the parent folder. Empty for files without parent.
|
|||
/// </summary>
|
|||
public Guid ParentId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The folder name.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string FolderName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The version of the asset folder.
|
|||
/// </summary>
|
|||
public long Version { get; set; } |
|||
|
|||
public static AssetFolderDto FromAssetFolder(IAssetFolderEntity asset, ApiController controller, string app) |
|||
{ |
|||
var response = SimpleMapper.Map(asset, new AssetFolderDto()); |
|||
|
|||
return CreateLinks(response, controller, app); |
|||
} |
|||
|
|||
private static AssetFolderDto CreateLinks(AssetFolderDto response, ApiController controller, string app) |
|||
{ |
|||
var values = new { app, id = response.Id }; |
|||
|
|||
response.AddSelfLink(controller.Url<AssetsController>(x => nameof(x.GetAsset), values)); |
|||
|
|||
if (controller.HasPermission(Permissions.AppAssetsUpdate)) |
|||
{ |
|||
response.AddPutLink("update", controller.Url<AssetFoldersController>(x => nameof(x.PutAssetFolder), values)); |
|||
|
|||
response.AddPutLink("move", controller.Url<AssetFoldersController>(x => nameof(x.PutAssetFolderParent), values)); |
|||
} |
|||
|
|||
if (controller.HasPermission(Permissions.AppAssetsUpdate)) |
|||
{ |
|||
response.AddDeleteLink("delete", controller.Url<AssetFoldersController>(x => nameof(x.DeleteAssetFolder), values)); |
|||
} |
|||
|
|||
return response; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using Squidex.Domain.Apps.Entities.Assets; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Shared; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Assets.Models |
|||
{ |
|||
public sealed class AssetFoldersDto : Resource |
|||
{ |
|||
/// <summary>
|
|||
/// The total number of assets.
|
|||
/// </summary>
|
|||
public long Total { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The assets folders.
|
|||
/// </summary>
|
|||
[Required] |
|||
public AssetFolderDto[] Items { get; set; } |
|||
|
|||
public static AssetFoldersDto FromAssets(IResultList<IAssetFolderEntity> assetFolders, ApiController controller, string app) |
|||
{ |
|||
var response = new AssetFoldersDto |
|||
{ |
|||
Total = assetFolders.Total, |
|||
Items = assetFolders.Select(x => AssetFolderDto.FromAssetFolder(x, controller, app)).ToArray() |
|||
}; |
|||
|
|||
return CreateLinks(response, controller, app); |
|||
} |
|||
|
|||
private static AssetFoldersDto CreateLinks(AssetFoldersDto response, ApiController controller, string app) |
|||
{ |
|||
var values = new { app }; |
|||
|
|||
response.AddSelfLink(controller.Url<AssetFoldersController>(x => nameof(x.GetAssetFolders), values)); |
|||
|
|||
if (controller.HasPermission(Permissions.AppAssetsUpdate)) |
|||
{ |
|||
response.AddPostLink("create", controller.Url<AssetFoldersController>(x => nameof(x.PostAssetFolder), values)); |
|||
} |
|||
|
|||
return response; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Assets.Models |
|||
{ |
|||
public sealed class CreateAssetFolderDto |
|||
{ |
|||
/// <summary>
|
|||
/// The name of the folder.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string FolderName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The id of the parent folder.
|
|||
/// </summary>
|
|||
public Guid ParentId { get; set; } |
|||
|
|||
public CreateAssetFolder ToCommand() |
|||
{ |
|||
return SimpleMapper.Map(this, new CreateAssetFolder()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Assets.Models |
|||
{ |
|||
public sealed class MoveAssetItemDto |
|||
{ |
|||
/// <summary>
|
|||
/// The parent folder id.
|
|||
/// </summary>
|
|||
public Guid ParentId { get; set; } |
|||
|
|||
public MoveAsset ToCommand(Guid id) |
|||
{ |
|||
return new MoveAsset { AssetId = id, ParentId = ParentId }; |
|||
} |
|||
|
|||
public MoveAssetFolder ToFolderCommand(Guid id) |
|||
{ |
|||
return new MoveAssetFolder { AssetFolderId = id, ParentId = ParentId }; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Assets.Models |
|||
{ |
|||
public sealed class RenameAssetFolderDto |
|||
{ |
|||
/// <summary>
|
|||
/// The name of the folder.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string FolderName { get; set; } |
|||
|
|||
public RenameAssetFolder ToCommand(Guid id) |
|||
{ |
|||
return SimpleMapper.Map(this, new RenameAssetFolder { AssetFolderId = id }); |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 958 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 514 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 723 B |
|
After Width: | Height: | Size: 913 B |
|
Before Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 913 B |
|
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,171 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Domain.Apps.Entities.Assets.State; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public class AssetFolderGrainTests : HandlerTestBase<AssetFolderState> |
|||
{ |
|||
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); |
|||
private readonly IActivationLimit limit = A.Fake<IActivationLimit>(); |
|||
private readonly Guid parentId = Guid.NewGuid(); |
|||
private readonly Guid assetFolderId = Guid.NewGuid(); |
|||
private readonly AssetFolderGrain sut; |
|||
|
|||
protected override Guid Id |
|||
{ |
|||
get { return assetFolderId; } |
|||
} |
|||
|
|||
public AssetFolderGrainTests() |
|||
{ |
|||
A.CallTo(() => assetQuery.FindAssetFolderAsync(parentId)) |
|||
.Returns(new List<IAssetFolderEntity> { A.Fake<IAssetFolderEntity>() }); |
|||
|
|||
sut = new AssetFolderGrain(Store, assetQuery, limit, A.Dummy<ISemanticLog>()); |
|||
sut.ActivateAsync(Id).Wait(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_set_limit() |
|||
{ |
|||
A.CallTo(() => limit.SetLimit(5000, TimeSpan.FromMinutes(5))) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Command_should_throw_exception_if_rule_is_deleted() |
|||
{ |
|||
await ExecuteCreateAsync(); |
|||
await ExecuteDeleteAsync(); |
|||
|
|||
await Assert.ThrowsAsync<DomainException>(ExecuteUpdateAsync); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Create_should_create_events_and_update_state() |
|||
{ |
|||
var command = new CreateAssetFolder { FolderName = "New Name" }; |
|||
|
|||
var result = await sut.ExecuteAsync(CreateAssetFolderCommand(command)); |
|||
|
|||
result.ShouldBeEquivalent(sut.Snapshot); |
|||
|
|||
Assert.Equal(command.FolderName, sut.Snapshot.FolderName); |
|||
|
|||
LastEvents |
|||
.ShouldHaveSameEvents( |
|||
CreateAssetFolderEvent(new AssetFolderCreated |
|||
{ |
|||
FolderName = command.FolderName |
|||
}) |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Update_should_create_events_and_update_state() |
|||
{ |
|||
var command = new RenameAssetFolder { FolderName = "New Name" }; |
|||
|
|||
await ExecuteCreateAsync(); |
|||
|
|||
var result = await sut.ExecuteAsync(CreateAssetFolderCommand(command)); |
|||
|
|||
result.ShouldBeEquivalent(sut.Snapshot); |
|||
|
|||
Assert.Equal(command.FolderName, sut.Snapshot.FolderName); |
|||
|
|||
LastEvents |
|||
.ShouldHaveSameEvents( |
|||
CreateAssetFolderEvent(new AssetFolderRenamed |
|||
{ |
|||
FolderName = command.FolderName |
|||
}) |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Move_should_create_events_and_update_state() |
|||
{ |
|||
var command = new MoveAssetFolder { ParentId = parentId }; |
|||
|
|||
await ExecuteCreateAsync(); |
|||
|
|||
var result = await sut.ExecuteAsync(CreateAssetFolderCommand(command)); |
|||
|
|||
result.ShouldBeEquivalent(sut.Snapshot); |
|||
|
|||
Assert.Equal(parentId, sut.Snapshot.ParentId); |
|||
|
|||
LastEvents |
|||
.ShouldHaveSameEvents( |
|||
CreateAssetFolderEvent(new AssetFolderMoved { ParentId = parentId }) |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Delete_should_create_events_with_total_file_size() |
|||
{ |
|||
var command = new DeleteAssetFolder(); |
|||
|
|||
await ExecuteCreateAsync(); |
|||
|
|||
var result = await sut.ExecuteAsync(CreateAssetFolderCommand(command)); |
|||
|
|||
result.ShouldBeEquivalent(new EntitySavedResult(1)); |
|||
|
|||
Assert.True(sut.Snapshot.IsDeleted); |
|||
|
|||
LastEvents |
|||
.ShouldHaveSameEvents( |
|||
CreateAssetFolderEvent(new AssetFolderDeleted()) |
|||
); |
|||
} |
|||
|
|||
private Task ExecuteCreateAsync() |
|||
{ |
|||
return sut.ExecuteAsync(CreateAssetFolderCommand(new CreateAssetFolder { FolderName = "My Folder" })); |
|||
} |
|||
|
|||
private Task ExecuteUpdateAsync() |
|||
{ |
|||
return sut.ExecuteAsync(CreateAssetFolderCommand(new RenameAssetFolder { FolderName = "My Folder" })); |
|||
} |
|||
|
|||
private Task ExecuteDeleteAsync() |
|||
{ |
|||
return sut.ExecuteAsync(CreateAssetFolderCommand(new DeleteAssetFolder())); |
|||
} |
|||
|
|||
protected T CreateAssetFolderEvent<T>(T @event) where T : AssetFolderEvent |
|||
{ |
|||
@event.AssetFolderId = assetFolderId; |
|||
|
|||
return CreateEvent(@event); |
|||
} |
|||
|
|||
protected T CreateAssetFolderCommand<T>(T command) where T : AssetFolderCommand |
|||
{ |
|||
command.AssetFolderId = assetFolderId; |
|||
|
|||
return CreateCommand(command); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Guards |
|||
{ |
|||
public class GuardAssetFolderTests |
|||
{ |
|||
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); |
|||
|
|||
[Fact] |
|||
public async Task CanCreate_should_throw_exception_when_folder_name_not_defined() |
|||
{ |
|||
var command = new CreateAssetFolder(); |
|||
|
|||
A.CallTo(() => assetQuery.FindAssetFolderAsync(command.ParentId)) |
|||
.Returns(new List<IAssetFolderEntity>()); |
|||
|
|||
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanCreate(command, assetQuery), |
|||
new ValidationError("Folder name is required.", "FolderName")); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanCreate_should_throw_exception_when_folder_not_found() |
|||
{ |
|||
var command = new CreateAssetFolder { FolderName = "My Folder", ParentId = Guid.NewGuid() }; |
|||
|
|||
A.CallTo(() => assetQuery.FindAssetFolderAsync(command.ParentId)) |
|||
.Returns(new List<IAssetFolderEntity>()); |
|||
|
|||
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanCreate(command, assetQuery), |
|||
new ValidationError("Asset folder does not exist.", "ParentId")); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanCreate_should_not_throw_exception_when_folder_found() |
|||
{ |
|||
var command = new CreateAssetFolder { FolderName = "My Folder", ParentId = Guid.NewGuid() }; |
|||
|
|||
A.CallTo(() => assetQuery.FindAssetFolderAsync(command.ParentId)) |
|||
.Returns(new List<IAssetFolderEntity> { CreateFolder() }); |
|||
|
|||
await GuardAssetFolder.CanCreate(command, assetQuery); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanCreate_should_not_throw_exception_when_added_to_root() |
|||
{ |
|||
var command = new CreateAssetFolder { FolderName = "My Folder" }; |
|||
|
|||
await GuardAssetFolder.CanCreate(command, assetQuery); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanMove_should_throw_exception_when_adding_to_its_own_child() |
|||
{ |
|||
var id = Guid.NewGuid(); |
|||
|
|||
var command = new MoveAssetFolder { ParentId = Guid.NewGuid() }; |
|||
|
|||
A.CallTo(() => assetQuery.FindAssetFolderAsync(command.ParentId)) |
|||
.Returns(new List<IAssetFolderEntity> |
|||
{ |
|||
CreateFolder(id), |
|||
CreateFolder(command.ParentId) |
|||
}); |
|||
|
|||
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, assetQuery, id, Guid.NewGuid()), |
|||
new ValidationError("Cannot add folder to its own child.", "ParentId")); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanMove_should_throw_exception_when_folder_not_found() |
|||
{ |
|||
var command = new MoveAssetFolder { ParentId = Guid.NewGuid() }; |
|||
|
|||
A.CallTo(() => assetQuery.FindAssetFolderAsync(command.ParentId)) |
|||
.Returns(new List<IAssetFolderEntity>()); |
|||
|
|||
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, assetQuery, Guid.NewGuid(), Guid.NewGuid()), |
|||
new ValidationError("Asset folder does not exist.", "ParentId")); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanMove_should_throw_exception_when_folder_has_not_changed() |
|||
{ |
|||
var command = new MoveAssetFolder { ParentId = Guid.NewGuid() }; |
|||
|
|||
await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, assetQuery, Guid.NewGuid(), command.ParentId), |
|||
new ValidationError("Asset folder is already part of this folder.", "ParentId")); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanMove_should_not_throw_exception_when_folder_found() |
|||
{ |
|||
var command = new MoveAssetFolder { ParentId = Guid.NewGuid() }; |
|||
|
|||
A.CallTo(() => assetQuery.FindAssetFolderAsync(command.ParentId)) |
|||
.Returns(new List<IAssetFolderEntity> { CreateFolder() }); |
|||
|
|||
await GuardAssetFolder.CanMove(command, assetQuery, Guid.NewGuid(), Guid.NewGuid()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CanMove_should_not_throw_exception_when_added_to_root() |
|||
{ |
|||
var command = new MoveAssetFolder(); |
|||
|
|||
await GuardAssetFolder.CanMove(command, assetQuery, Guid.NewGuid(), Guid.NewGuid()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanRename_should_throw_exception_if_folder_name_is_empty() |
|||
{ |
|||
var command = new RenameAssetFolder(); |
|||
|
|||
ValidationAssert.Throws(() => GuardAssetFolder.CanRename(command, "My Folder"), |
|||
new ValidationError("Folder name is required.", "FolderName")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanRename_should_throw_exception_if_names_are_the_same() |
|||
{ |
|||
var command = new RenameAssetFolder { FolderName = "My Folder" }; |
|||
|
|||
ValidationAssert.Throws(() => GuardAssetFolder.CanRename(command, "My Folder"), |
|||
new ValidationError("Asset folder has already this name.", "FolderName")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanRename_should_not_throw_exception_if_names_are_different() |
|||
{ |
|||
var command = new RenameAssetFolder { FolderName = "New Folder Name" }; |
|||
|
|||
GuardAssetFolder.CanRename(command, "My Folder"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanDelete_should_not_throw_exception() |
|||
{ |
|||
var command = new DeleteAssetFolder(); |
|||
|
|||
GuardAssetFolder.CanDelete(command); |
|||
} |
|||
|
|||
private IAssetFolderEntity CreateFolder(Guid id = default) |
|||
{ |
|||
var assetFolder = A.Fake<IAssetFolderEntity>(); |
|||
|
|||
A.CallTo(() => assetFolder.Id).Returns(id); |
|||
|
|||
return assetFolder; |
|||
} |
|||
} |
|||
} |
|||