mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
81 changed files with 1564 additions and 604 deletions
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ValidateContent |
||||
|
{ |
||||
|
public enum ValidationMode |
||||
|
{ |
||||
|
Default, |
||||
|
Optimized |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.State; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public sealed class AssetDomainObjectGrain : DomainObjectGrain<AssetDomainObject, AssetState>, IAssetGrain |
||||
|
{ |
||||
|
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); |
||||
|
|
||||
|
public AssetDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
limit?.SetLimit(5000, Lifetime); |
||||
|
} |
||||
|
|
||||
|
protected override Task OnActivateAsync(Guid key) |
||||
|
{ |
||||
|
TryDelayDeactivation(Lifetime); |
||||
|
|
||||
|
return base.OnActivateAsync(key); |
||||
|
} |
||||
|
|
||||
|
public async Task<J<IAssetEntity>> GetStateAsync(long version = EtagVersion.Any) |
||||
|
{ |
||||
|
await DomainObject.EnsureLoadedAsync(); |
||||
|
|
||||
|
return DomainObject.GetSnapshot(version); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.State; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public sealed class AssetFolderDomainObjectGrain : DomainObjectGrain<AssetFolderDomainObject, AssetFolderState>, IAssetFolderGrain |
||||
|
{ |
||||
|
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); |
||||
|
|
||||
|
public AssetFolderDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
limit?.SetLimit(5000, Lifetime); |
||||
|
} |
||||
|
|
||||
|
protected override Task OnActivateAsync(Guid key) |
||||
|
{ |
||||
|
TryDelayDeactivation(Lifetime); |
||||
|
|
||||
|
return base.OnActivateAsync(key); |
||||
|
} |
||||
|
|
||||
|
public async Task<J<IAssetFolderEntity>> GetStateAsync() |
||||
|
{ |
||||
|
await DomainObject.EnsureLoadedAsync(); |
||||
|
|
||||
|
return Snapshot; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Commands |
||||
|
{ |
||||
|
public sealed class CreateContents : SquidexCommand, ISchemaCommand, IAppCommand |
||||
|
{ |
||||
|
public NamedId<Guid> AppId { get; set; } |
||||
|
|
||||
|
public NamedId<Guid> SchemaId { get; set; } |
||||
|
|
||||
|
public bool Publish { get; set; } |
||||
|
|
||||
|
public bool DoNotValidate { get; set; } |
||||
|
|
||||
|
public bool DoNotScript { get; set; } |
||||
|
|
||||
|
public bool OptimizeValidation { get; set; } |
||||
|
|
||||
|
public List<NamedContentData> Datas { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.State; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public sealed class ContentDomainObjectGrain : DomainObjectGrain<ContentDomainObject, ContentState>, IContentGrain |
||||
|
{ |
||||
|
private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); |
||||
|
|
||||
|
public ContentDomainObjectGrain(IServiceProvider serviceProvider, IActivationLimit limit) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
limit?.SetLimit(5000, Lifetime); |
||||
|
} |
||||
|
|
||||
|
public async Task<J<IContentEntity>> GetStateAsync(long version = -2) |
||||
|
{ |
||||
|
await DomainObject.EnsureLoadedAsync(); |
||||
|
|
||||
|
return DomainObject.GetSnapshot(version); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public sealed class ContentImporterCommandMiddleware : ICommandMiddleware |
||||
|
{ |
||||
|
private readonly IServiceProvider serviceProvider; |
||||
|
|
||||
|
public ContentImporterCommandMiddleware(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
Guard.NotNull(serviceProvider); |
||||
|
|
||||
|
this.serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, NextDelegate next) |
||||
|
{ |
||||
|
if (context.Command is CreateContents createContents) |
||||
|
{ |
||||
|
var result = new ImportResult(); |
||||
|
|
||||
|
if (createContents.Datas != null && createContents.Datas.Count > 0) |
||||
|
{ |
||||
|
var command = SimpleMapper.Map(createContents, new CreateContent()); |
||||
|
|
||||
|
foreach (var data in createContents.Datas) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
command.ContentId = Guid.NewGuid(); |
||||
|
command.Data = data; |
||||
|
|
||||
|
var content = serviceProvider.GetRequiredService<ContentDomainObject>(); |
||||
|
|
||||
|
content.Setup(command.ContentId); |
||||
|
|
||||
|
await content.ExecuteAsync(command); |
||||
|
|
||||
|
result.Add(new ImportResultItem { ContentId = command.ContentId }); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
result.Add(new ImportResultItem { Exception = ex }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
context.Complete(result); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await next(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public sealed class ImportResult : List<ImportResultItem> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public sealed class ImportResultItem |
||||
|
{ |
||||
|
public Guid? ContentId { get; set; } |
||||
|
|
||||
|
public Exception? Exception { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.State; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public sealed class RuleDomainObjectGrain : DomainObjectGrain<RuleDomainObject, RuleState>, IRuleGrain |
||||
|
{ |
||||
|
public RuleDomainObjectGrain(IServiceProvider serviceProvider) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<J<IRuleEntity>> GetStateAsync() |
||||
|
{ |
||||
|
await DomainObject.EnsureLoadedAsync(); |
||||
|
|
||||
|
return Snapshot; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas.State; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas |
||||
|
{ |
||||
|
public sealed class SchemaDomainObjectGrain : DomainObjectGrain<SchemaDomainObject, SchemaState>, ISchemaGrain |
||||
|
{ |
||||
|
public SchemaDomainObjectGrain(IServiceProvider serviceProvider) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<J<ISchemaEntity>> GetStateAsync() |
||||
|
{ |
||||
|
await DomainObject.EnsureLoadedAsync(); |
||||
|
|
||||
|
return Snapshot; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.EventSourcing; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Commands |
||||
|
{ |
||||
|
public abstract class DomainObject<T> : DomainObjectBase<T> where T : class, IDomainState<T>, new() |
||||
|
{ |
||||
|
private readonly IStore<Guid> store; |
||||
|
private T snapshot = new T { Version = EtagVersion.Empty }; |
||||
|
private IPersistence<T>? persistence; |
||||
|
|
||||
|
public override T Snapshot |
||||
|
{ |
||||
|
get { return snapshot; } |
||||
|
} |
||||
|
|
||||
|
protected DomainObject(IStore<Guid> store, ISemanticLog log) |
||||
|
: base(log) |
||||
|
{ |
||||
|
Guard.NotNull(store); |
||||
|
|
||||
|
this.store = store; |
||||
|
} |
||||
|
|
||||
|
protected override void OnSetup() |
||||
|
{ |
||||
|
persistence = store.WithSnapshotsAndEventSourcing(GetType(), Id, new HandleSnapshot<T>(ApplySnapshot), x => ApplyEvent(x, true)); |
||||
|
} |
||||
|
|
||||
|
protected sealed override bool ApplyEvent(Envelope<IEvent> @event, bool isLoading) |
||||
|
{ |
||||
|
var newVersion = Version + 1; |
||||
|
|
||||
|
var newSnapshot = OnEvent(@event); |
||||
|
|
||||
|
if (!ReferenceEquals(Snapshot, newSnapshot) || isLoading) |
||||
|
{ |
||||
|
snapshot = newSnapshot; |
||||
|
snapshot.Version = newVersion; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
protected sealed override void RestorePreviousSnapshot(T previousSnapshot, long previousVersion) |
||||
|
{ |
||||
|
snapshot = previousSnapshot; |
||||
|
} |
||||
|
|
||||
|
private void ApplySnapshot(T state) |
||||
|
{ |
||||
|
snapshot = state; |
||||
|
} |
||||
|
|
||||
|
protected sealed override async Task WriteAsync(Envelope<IEvent>[] newEvents, long previousVersion) |
||||
|
{ |
||||
|
if (newEvents.Length > 0 && persistence != null) |
||||
|
{ |
||||
|
await persistence.WriteEventsAsync(newEvents); |
||||
|
await persistence.WriteSnapshotAsync(Snapshot); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected async sealed override Task ReadAsync() |
||||
|
{ |
||||
|
if (persistence != null) |
||||
|
{ |
||||
|
await persistence.ReadAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async sealed override Task RebuildStateAsync() |
||||
|
{ |
||||
|
if (persistence != null) |
||||
|
{ |
||||
|
await persistence.WriteSnapshotAsync(Snapshot); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected T OnEvent(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
return Snapshot.Apply(@event); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,129 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Diagnostics; |
||||
|
using System.Linq; |
||||
|
using System.Security; |
||||
|
using System.Text; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Validation; |
||||
|
|
||||
|
namespace Squidex.Web |
||||
|
{ |
||||
|
public static class ApiExceptionConverter |
||||
|
{ |
||||
|
private static readonly List<Func<Exception, ErrorDto?>> Handlers = new List<Func<Exception, ErrorDto?>>(); |
||||
|
private static readonly Dictionary<int, string> Links = new Dictionary<int, string> |
||||
|
{ |
||||
|
[400] = "https://tools.ietf.org/html/rfc7231#section-6.5.1", |
||||
|
[401] = "https://tools.ietf.org/html/rfc7235#section-3.1", |
||||
|
[403] = "https://tools.ietf.org/html/rfc7231#section-6.5.3", |
||||
|
[404] = "https://tools.ietf.org/html/rfc7231#section-6.5.4", |
||||
|
[406] = "https://tools.ietf.org/html/rfc7231#section-6.5.6", |
||||
|
[409] = "https://tools.ietf.org/html/rfc7231#section-6.5.8", |
||||
|
[412] = "https://tools.ietf.org/html/rfc7231#section-6.5.10", |
||||
|
[415] = "https://tools.ietf.org/html/rfc7231#section-6.5.13", |
||||
|
[422] = "https://tools.ietf.org/html/rfc4918#section-11.2", |
||||
|
[500] = "https://tools.ietf.org/html/rfc7231#section-6.6.1", |
||||
|
}; |
||||
|
|
||||
|
private static void AddHandler<T>(Func<T, ErrorDto> handler) where T : Exception |
||||
|
{ |
||||
|
Handlers.Add(ex => ex is T typed ? handler(typed) : null); |
||||
|
} |
||||
|
|
||||
|
static ApiExceptionConverter() |
||||
|
{ |
||||
|
AddHandler<ValidationException>(OnValidationException); |
||||
|
AddHandler<DecoderFallbackException>(OnDecoderException); |
||||
|
AddHandler<DomainObjectNotFoundException>(OnDomainObjectNotFoundException); |
||||
|
AddHandler<DomainObjectVersionException>(OnDomainObjectVersionException); |
||||
|
AddHandler<DomainForbiddenException>(OnDomainForbiddenException); |
||||
|
AddHandler<DomainException>(OnDomainException); |
||||
|
AddHandler<SecurityException>(OnSecurityException); |
||||
|
} |
||||
|
|
||||
|
public static ErrorDto ToErrorDto(this Exception exception, HttpContext? httpContext) |
||||
|
{ |
||||
|
Guard.NotNull(exception); |
||||
|
|
||||
|
ErrorDto? result = null; |
||||
|
|
||||
|
foreach (var handler in Handlers) |
||||
|
{ |
||||
|
result = handler(exception); |
||||
|
|
||||
|
if (result != null) |
||||
|
{ |
||||
|
result.TraceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier; |
||||
|
|
||||
|
if (result.StatusCode.HasValue) |
||||
|
{ |
||||
|
result.Type = Links.GetOrDefault(result.StatusCode.Value); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new ErrorDto { StatusCode = 500 }; |
||||
|
} |
||||
|
|
||||
|
private static ErrorDto OnDecoderException(DecoderFallbackException ex) |
||||
|
{ |
||||
|
return new ErrorDto { StatusCode = 400, Message = ex.Message }; |
||||
|
} |
||||
|
|
||||
|
private static ErrorDto OnDomainObjectNotFoundException(DomainObjectNotFoundException ex) |
||||
|
{ |
||||
|
return new ErrorDto { StatusCode = 404 }; |
||||
|
} |
||||
|
|
||||
|
private static ErrorDto OnDomainObjectVersionException(DomainObjectVersionException ex) |
||||
|
{ |
||||
|
return new ErrorDto { StatusCode = 412, Message = ex.Message }; |
||||
|
} |
||||
|
|
||||
|
private static ErrorDto OnDomainException(DomainException ex) |
||||
|
{ |
||||
|
return new ErrorDto { StatusCode = 400, Message = ex.Message }; |
||||
|
} |
||||
|
|
||||
|
private static ErrorDto OnDomainForbiddenException(DomainForbiddenException ex) |
||||
|
{ |
||||
|
return new ErrorDto { StatusCode = 403, Message = ex.Message }; |
||||
|
} |
||||
|
|
||||
|
private static ErrorDto OnSecurityException(SecurityException ex) |
||||
|
{ |
||||
|
return new ErrorDto { StatusCode = 403, Message = ex.Message }; |
||||
|
} |
||||
|
|
||||
|
private static ErrorDto OnValidationException(ValidationException ex) |
||||
|
{ |
||||
|
return new ErrorDto { StatusCode = 400, Message = ex.Summary, Details = ToDetails(ex) }; |
||||
|
} |
||||
|
|
||||
|
private static string[] ToDetails(ValidationException ex) |
||||
|
{ |
||||
|
return ex.Errors?.Select(e => |
||||
|
{ |
||||
|
if (e.PropertyNames?.Any() == true) |
||||
|
{ |
||||
|
return $"{string.Join(", ", e.PropertyNames)}: {e.Message}"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return e.Message; |
||||
|
} |
||||
|
}).ToArray() ?? new string[0]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Contents.Models |
||||
|
{ |
||||
|
public sealed class ImportContentsDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The data to import.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public List<NamedContentData> Datas { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// True to automatically publish the content.
|
||||
|
/// </summary>
|
||||
|
public bool Publish { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// True to turn off scripting for faster inserts. Default: true.
|
||||
|
/// </summary>
|
||||
|
public bool DoNotScript { get; set; } = true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// True to turn off costly validation: Unique checks, asset checks and reference checks. Default: true.
|
||||
|
/// </summary>
|
||||
|
public bool OptimizeValidation { get; set; } = true; |
||||
|
|
||||
|
public CreateContents ToCommand() |
||||
|
{ |
||||
|
return SimpleMapper.Map(this, new CreateContents()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Squidex.Domain.Apps.Entities.Contents; |
||||
|
using Squidex.Web; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Contents.Models |
||||
|
{ |
||||
|
public sealed class ImportResultDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The error when the import failed.
|
||||
|
/// </summary>
|
||||
|
public ErrorDto? Error { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The id of the content when the import succeeds.
|
||||
|
/// </summary>
|
||||
|
public Guid? ContentId { get; set; } |
||||
|
|
||||
|
public static ImportResultDto FromImportResult(ImportResultItem result, HttpContext httpContext) |
||||
|
{ |
||||
|
return new ImportResultDto { ContentId = result.ContentId, Error = result.Exception?.ToErrorDto(httpContext) }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable RECS0026 // Possible unassigned object created by 'new'
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class AssetDomainObjectGrainTests |
||||
|
{ |
||||
|
private readonly IActivationLimit limit = A.Fake<IActivationLimit>(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_set_limit() |
||||
|
{ |
||||
|
var serviceProvider = A.Fake<IServiceProvider>(); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(typeof(AssetDomainObject))) |
||||
|
.Returns(A.Dummy<AssetDomainObject>()); |
||||
|
|
||||
|
new AssetDomainObjectGrain(serviceProvider, limit); |
||||
|
|
||||
|
A.CallTo(() => limit.SetLimit(5000, TimeSpan.FromMinutes(5))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable RECS0026 // Possible unassigned object created by 'new'
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class AssetFolderDomainObjectGrainTests |
||||
|
{ |
||||
|
private readonly IActivationLimit limit = A.Fake<IActivationLimit>(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_set_limit() |
||||
|
{ |
||||
|
var serviceProvider = A.Fake<IServiceProvider>(); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(typeof(AssetFolderDomainObject))) |
||||
|
.Returns(A.Dummy<AssetFolderDomainObject>()); |
||||
|
|
||||
|
new AssetFolderDomainObjectGrain(serviceProvider, limit); |
||||
|
|
||||
|
A.CallTo(() => limit.SetLimit(5000, TimeSpan.FromMinutes(5))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable RECS0026 // Possible unassigned object created by 'new'
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public class ContentDomainObjectGrainTests |
||||
|
{ |
||||
|
private readonly IActivationLimit limit = A.Fake<IActivationLimit>(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_set_limit() |
||||
|
{ |
||||
|
var serviceProvider = A.Fake<IServiceProvider>(); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) |
||||
|
.Returns(A.Dummy<ContentDomainObject>()); |
||||
|
|
||||
|
new ContentDomainObjectGrain(serviceProvider, limit); |
||||
|
|
||||
|
A.CallTo(() => limit.SetLimit(5000, TimeSpan.FromMinutes(5))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,142 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public class ContentImporterCommandMiddlewareTests |
||||
|
{ |
||||
|
private readonly IServiceProvider serviceProvider = A.Fake<IServiceProvider>(); |
||||
|
private readonly ICommandBus commandBus = A.Dummy<ICommandBus>(); |
||||
|
private readonly ContentImporterCommandMiddleware sut; |
||||
|
|
||||
|
public ContentImporterCommandMiddlewareTests() |
||||
|
{ |
||||
|
sut = new ContentImporterCommandMiddleware(serviceProvider); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_do_nothing_if_datas_is_null() |
||||
|
{ |
||||
|
var command = new CreateContents(); |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
Assert.True(context.PlainResult is ImportResult); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(A<Type>.Ignored)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_do_nothing_if_datas_is_empty() |
||||
|
{ |
||||
|
var command = new CreateContents { Datas = new List<NamedContentData>() }; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
Assert.True(context.PlainResult is ImportResult); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(A<Type>.Ignored)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_import_data() |
||||
|
{ |
||||
|
var data1 = CreateData(1); |
||||
|
var data2 = CreateData(2); |
||||
|
|
||||
|
var domainObject = A.Fake<ContentDomainObject>(); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) |
||||
|
.Returns(domainObject); |
||||
|
|
||||
|
var command = new CreateContents |
||||
|
{ |
||||
|
Datas = new List<NamedContentData> |
||||
|
{ |
||||
|
data1, |
||||
|
data2 |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<ImportResult>(); |
||||
|
|
||||
|
Assert.Equal(2, result.Count); |
||||
|
Assert.Equal(2, result.Count(x => x.ContentId.HasValue && x.Exception == null)); |
||||
|
|
||||
|
A.CallTo(() => domainObject.Setup(A<Guid>.Ignored)) |
||||
|
.MustHaveHappenedTwiceExactly(); |
||||
|
|
||||
|
A.CallTo(() => domainObject.ExecuteAsync(A<CreateContent>.Ignored)) |
||||
|
.MustHaveHappenedTwiceExactly(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_skip_exception() |
||||
|
{ |
||||
|
var data1 = CreateData(1); |
||||
|
var data2 = CreateData(2); |
||||
|
|
||||
|
var domainObject = A.Fake<ContentDomainObject>(); |
||||
|
|
||||
|
var exception = new InvalidOperationException(); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) |
||||
|
.Returns(domainObject); |
||||
|
|
||||
|
A.CallTo(() => domainObject.ExecuteAsync(A<CreateContent>.That.Matches(x => x.Data == data1))) |
||||
|
.Throws(exception); |
||||
|
|
||||
|
var command = new CreateContents |
||||
|
{ |
||||
|
Datas = new List<NamedContentData> |
||||
|
{ |
||||
|
data1, |
||||
|
data2 |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<ImportResult>(); |
||||
|
|
||||
|
Assert.Equal(2, result.Count); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId.HasValue && x.Exception == null)); |
||||
|
Assert.Equal(1, result.Count(x => !x.ContentId.HasValue && x.Exception == exception)); |
||||
|
} |
||||
|
|
||||
|
private static NamedContentData CreateData(int value) |
||||
|
{ |
||||
|
return new NamedContentData() |
||||
|
.AddField("value", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue("iv", JsonValue.Create(value))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue