diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs index 5bc9a1834..f6c2e0c47 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs @@ -13,6 +13,7 @@ using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; @@ -106,7 +107,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent return result; } - public static NamedContentData ToApiModel(this NamedContentData content, Schema schema, LanguagesConfig languagesConfig, bool excludeHidden = true) + public static NamedContentData ToApiModel(this NamedContentData content, Schema schema, LanguagesConfig languagesConfig, bool excludeHidden = true, bool checkTypeCompatibility = false) { Guard.NotNull(schema, nameof(schema)); Guard.NotNull(languagesConfig, nameof(languagesConfig)); @@ -123,6 +124,32 @@ namespace Squidex.Domain.Apps.Core.ConvertContent continue; } + if (checkTypeCompatibility) + { + var isValid = true; + + foreach (var value in fieldValue.Value.Values) + { + try + { + if (!value.IsNull()) + { + JsonValueConverter.ConvertValue(field, value); + } + } + catch + { + isValid = false; + break; + } + } + + if (!isValid) + { + continue; + } + } + var fieldResult = new ContentFieldData(); var fieldValues = fieldValue.Value; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index 9095331b3..54475f433 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent private JsonValueConverter(JToken value) { - this.Value = value; + Value = value; } public static object ConvertValue(Field field, JToken json) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index e93f09281..fc8d77f94 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.OData; using Microsoft.OData.UriParser; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Edm; @@ -27,110 +28,130 @@ namespace Squidex.Domain.Apps.Entities.Contents public sealed class ContentQueryService : IContentQueryService { private readonly IContentRepository contentRepository; + private readonly IContentVersionLoader contentVersionLoader; private readonly IAppProvider appProvider; private readonly IScriptEngine scriptEngine; private readonly EdmModelBuilder modelBuilder; public ContentQueryService( IContentRepository contentRepository, + IContentVersionLoader contentVersionLoader, IAppProvider appProvider, IScriptEngine scriptEngine, EdmModelBuilder modelBuilder) { Guard.NotNull(contentRepository, nameof(contentRepository)); + Guard.NotNull(contentVersionLoader, nameof(contentVersionLoader)); Guard.NotNull(scriptEngine, nameof(scriptEngine)); Guard.NotNull(modelBuilder, nameof(modelBuilder)); Guard.NotNull(appProvider, nameof(appProvider)); this.contentRepository = contentRepository; + this.contentVersionLoader = contentVersionLoader; this.appProvider = appProvider; this.scriptEngine = scriptEngine; this.modelBuilder = modelBuilder; } - public async Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id) + public Task ThrowIfSchemaNotExistsAsync(IAppEntity app, string schemaIdOrName) + { + return GetSchemaAsync(app, schemaIdOrName); + } + + public async Task FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = -1) { Guard.NotNull(app, nameof(app)); Guard.NotNull(user, nameof(user)); + Guard.NotEmpty(id, nameof(id)); Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); - var isFrontendClient = user.IsInClient("squidex-frontend"); + var schema = await GetSchemaAsync(app, schemaIdOrName); - var schema = await FindSchemaAsync(app, schemaIdOrName); + var isFrontendClient = IsFrontendClient(user); + var isVersioned = version > EtagVersion.Empty; - var content = await contentRepository.FindContentAsync(app, schema, id); + var content = + isVersioned ? + await FindContentByVersionAsync(id, version) : + await FindContentAsync(app, id, schema); - if (content == null || (content.Status != Status.Published && !isFrontendClient)) + if (content == null || (content.Status != Status.Published && !isFrontendClient) || content.SchemaId.Id != schema.Id) { throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity)); } - content = TransformContent(user, schema, Enumerable.Repeat(content, 1)).FirstOrDefault(); + content = TransformContent(app, schema, user, Enumerable.Repeat(content, 1), isVersioned, isFrontendClient).FirstOrDefault(); - return (schema, content); + return content; } - public async Task<(ISchemaEntity Schema, IResultList Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query) + public async Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query) { Guard.NotNull(app, nameof(app)); Guard.NotNull(user, nameof(user)); Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); - var schema = await FindSchemaAsync(app, schemaIdOrName); + var schema = await GetSchemaAsync(app, schemaIdOrName); + + var isFrontendClient = IsFrontendClient(user); var parsedQuery = ParseQuery(app, query, schema); - var parsedStatus = ParseStatus(user, archived); + var parsedStatus = ParseStatus(isFrontendClient, archived); var contents = await contentRepository.QueryAsync(app, schema, parsedStatus.ToArray(), parsedQuery); - return TransformContents(user, schema, contents); + return TransformContents(app, schema, user, contents, false, isFrontendClient); } - public async Task<(ISchemaEntity Schema, IResultList Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet ids) + public async Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet ids) { Guard.NotNull(ids, nameof(ids)); Guard.NotNull(app, nameof(app)); Guard.NotNull(user, nameof(user)); Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); - var schema = await FindSchemaAsync(app, schemaIdOrName); + var schema = await GetSchemaAsync(app, schemaIdOrName); + + var isFrontendClient = IsFrontendClient(user); - var parsedStatus = ParseStatus(user, archived); + var parsedStatus = ParseStatus(isFrontendClient, archived); var contents = await contentRepository.QueryAsync(app, schema, parsedStatus.ToArray(), ids); - return TransformContents(user, schema, contents); + return TransformContents(app, schema, user, contents, false, isFrontendClient); } - private (ISchemaEntity Schema, IResultList Contents) TransformContents(ClaimsPrincipal user, ISchemaEntity schema, IResultList contents) + private IResultList TransformContents(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, + IResultList contents, + bool isTypeChecking, + bool isFrontendClient) { - var transformed = TransformContent(user, schema, contents); + var transformed = TransformContent(app, schema, user, contents, isTypeChecking, isFrontendClient); - return (schema, ResultList.Create(transformed, contents.Total)); + return ResultList.Create(transformed, contents.Total); } - private IEnumerable TransformContent(ClaimsPrincipal user, ISchemaEntity schema, IEnumerable contents) + private IEnumerable TransformContent(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, + IEnumerable contents, + bool isTypeChecking, + bool isFrontendClient) { var scriptText = schema.ScriptQuery; - if (!string.IsNullOrWhiteSpace(scriptText)) - { - foreach (var content in contents) - { - var contentData = scriptEngine.Transform(new ScriptContext { User = user, Data = content.Data, ContentId = content.Id }, scriptText); - var contentResult = SimpleMapper.Map(content, new ContentEntity()); - - contentResult.Data = contentData; + var isScripting = !string.IsNullOrWhiteSpace(scriptText); - yield return contentResult; - } - } - else + foreach (var content in contents) { - foreach (var content in contents) + var result = SimpleMapper.Map(content, new ContentEntity()); + + if (!isFrontendClient && isScripting) { - yield return content; + result.Data = scriptEngine.Transform(new ScriptContext { User = user, Data = content.Data, ContentId = content.Id }, scriptText); } + + result.Data = result.Data.ToApiModel(schema.SchemaDef, app.LanguagesConfig, isFrontendClient, isTypeChecking); + + yield return result; } } @@ -148,7 +169,7 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - public async Task FindSchemaAsync(IAppEntity app, string schemaIdOrName) + public async Task GetSchemaAsync(IAppEntity app, string schemaIdOrName) { Guard.NotNull(app, nameof(app)); @@ -172,11 +193,11 @@ namespace Squidex.Domain.Apps.Entities.Contents return schema; } - private static List ParseStatus(ClaimsPrincipal user, bool archived) + private static List ParseStatus(bool isFrontendClient, bool archived) { var status = new List(); - if (user.IsInClient("squidex-frontend")) + if (isFrontendClient) { if (archived) { @@ -195,5 +216,20 @@ namespace Squidex.Domain.Apps.Entities.Contents return status; } + + private Task FindContentByVersionAsync(Guid id, long version) + { + return contentVersionLoader.LoadAsync(id, version); + } + + private Task FindContentAsync(IAppEntity app, Guid id, ISchemaEntity schema) + { + return contentRepository.FindContentAsync(app, schema, id); + } + + private static bool IsFrontendClient(ClaimsPrincipal user) + { + return user.IsInClient("squidex-frontend"); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs index 04f02b1e0..75b4bb248 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs @@ -7,11 +7,8 @@ using System; using System.Threading.Tasks; -using NodaTime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.State; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Schemas.State; using Squidex.Infrastructure; using Squidex.Infrastructure.States; @@ -32,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents this.registry = registry; } - public async Task<(ISchemaEntity Schema, IContentEntity Content)> LoadAsync(Guid id, int version) + public async Task LoadAsync(Guid id, long version) { var content = new ContentState(); @@ -51,45 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Contents throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); } - var (now, then) = await ReadSchema(content.SchemaId.Id, content.LastModified); - - foreach (var key in content.Data.Keys) - { - if (IsFieldRemovedOrChanged(then.SchemaDef, now.SchemaDef, key)) - { - content.Data.Remove(key); - } - } - - return (then, content); - } - - private static bool IsFieldRemovedOrChanged(Schema schemaThen, Schema schemaNow, string key) - { - return - !schemaThen.FieldsByName.TryGetValue(key, out var fieldThen) || - !schemaNow.FieldsByName.TryGetValue(key, out var fieldNow) || - fieldThen.GetType() != fieldNow.GetType(); - } - - private async Task<(ISchemaEntity, ISchemaEntity)> ReadSchema(Guid schemaId, Instant lastUpdate) - { - var state = new SchemaState(); - var stateAtVersion = (SchemaState)null; - - var persistence = store.WithEventSourcing(schemaId, e => - { - state = state.Apply(e, registry); - - if (state.LastModified < lastUpdate) - { - stateAtVersion = state; - } - }); - - await persistence.ReadAsync(); - - return (state, stateAtVersion); + return content; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs index c2581135f..205e3652c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs @@ -10,19 +10,18 @@ using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentQueryService { - Task<(ISchemaEntity Schema, IResultList Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet ids); + Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet ids); - Task<(ISchemaEntity Schema, IResultList Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); + Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); - Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id); + Task FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = EtagVersion.Any); - Task FindSchemaAsync(IAppEntity app, string schemaIdOrName); + Task ThrowIfSchemaNotExistsAsync(IAppEntity app, string schemaIdOrName); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentVersionLoader.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentVersionLoader.cs index 54ae6a28b..e55988d52 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentVersionLoader.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentVersionLoader.cs @@ -7,12 +7,11 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Entities.Schemas; namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentVersionLoader { - Task<(ISchemaEntity Schema, IContentEntity Content)> LoadAsync(Guid id, int version); + Task LoadAsync(Guid id, long version); } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs index bf5dbd7ab..daf4709f0 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (content == null) { - content = (await contentQuery.FindContentAsync(app, schemaId.ToString(), user, id)).Content; + content = await contentQuery.FindContentAsync(app, schemaId.ToString(), user, id); if (content != null) { @@ -96,12 +96,12 @@ namespace Squidex.Domain.Apps.Entities.Contents { var result = await contentQuery.QueryAsync(app, schemaIdOrName, user, false, query); - foreach (var content in result.Contents) + foreach (var content in result) { cachedContents[content.Id] = content; } - return result.Contents; + return result; } public async Task> GetReferencedAssetsAsync(ICollection ids) @@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var result = await contentQuery.QueryAsync(app, schemaId.ToString(), user, false, notLoadedContents); - foreach (var content in result.Contents) + foreach (var content in result) { cachedContents[content.Id] = content; } diff --git a/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs b/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs index 13ce32acc..210617ff6 100644 --- a/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs +++ b/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities public Instant LastModified { get; set; } [JsonProperty] - public long Version { get; set; } = EtagVersion.Empty; + public long Version { get; set; } public T Clone() { diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs index 93580c7f0..433cf5caa 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs @@ -63,7 +63,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains public Task> GetStateAsync() { - return Task.FromResult(state.ToInfo(this.eventConsumer.Name).AsImmutable()); + return Task.FromResult(state.ToInfo(eventConsumer.Name).AsImmutable()); } public Task OnEventAsync(Immutable subscription, Immutable storedEvent) diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs index 673beb849..ff5dd942c 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs @@ -15,7 +15,6 @@ using NodaTime.Text; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Contents.Models; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.GraphQL; @@ -32,17 +31,14 @@ namespace Squidex.Areas.Api.Controllers.Contents public sealed class ContentsController : ApiController { private readonly IContentQueryService contentQuery; - private readonly IContentVersionLoader contentVersionLoader; private readonly IGraphQLService graphQl; public ContentsController(ICommandBus commandBus, IContentQueryService contentQuery, - IContentVersionLoader contentVersionLoader, IGraphQLService graphQl) : base(commandBus) { this.contentQuery = contentQuery; - this.contentVersionLoader = contentVersionLoader; this.graphQl = graphQl; } @@ -122,18 +118,8 @@ namespace Squidex.Areas.Api.Controllers.Contents var response = new ContentsDto { - Total = result.Contents.Total, - Items = result.Contents.Take(200).Select(item => - { - var itemModel = SimpleMapper.Map(item, new ContentDto()); - - if (item.Data != null) - { - itemModel.Data = item.Data.ToApiModel(result.Schema.SchemaDef, App.LanguagesConfig, !isFrontendClient); - } - - return itemModel; - }).ToArray() + Total = result.Total, + Items = result.Take(200).Select(item => SimpleMapper.Map(item, new ContentDto())).ToArray() }; Response.Headers["Surrogate-Key"] = string.Join(" ", response.Items.Select(x => x.Id)); @@ -160,19 +146,12 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContent(string app, string name, Guid id) { - var (schema, entity) = await contentQuery.FindContentAsync(App, name, User, id); + var content = await contentQuery.FindContentAsync(App, name, User, id); - var response = SimpleMapper.Map(entity, new ContentDto()); + var response = SimpleMapper.Map(content, new ContentDto()); - if (entity.Data != null) - { - var isFrontendClient = User.IsFrontendClient(); - - response.Data = entity.Data.ToApiModel(schema.SchemaDef, App.LanguagesConfig, !isFrontendClient); - } - - Response.Headers["ETag"] = entity.Version.ToString(); - Response.Headers["Surrogate-Key"] = entity.Id.ToString(); + Response.Headers["ETag"] = content.Version.ToString(); + Response.Headers["Surrogate-Key"] = content.Id.ToString(); return Ok(response); } @@ -198,18 +177,12 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContentVersion(string app, string name, Guid id, int version) { - var content = await contentVersionLoader.LoadAsync(id, version); - - var response = SimpleMapper.Map(content.Content, new ContentDto()); + var content = await contentQuery.FindContentAsync(App, name, User, id, version); - if (content.Content.Data != null) - { - var isFrontendClient = User.IsFrontendClient(); - - response.Data = content.Content.Data.ToApiModel(content.Schema.SchemaDef, App.LanguagesConfig, !isFrontendClient); - } + var response = SimpleMapper.Map(content, new ContentDto()); - Response.Headers["ETag"] = version.ToString(); + Response.Headers["ETag"] = content.Version.ToString(); + Response.Headers["Surrogate-Key"] = content.Id.ToString(); return Ok(response.Data); } @@ -235,7 +208,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish }; @@ -268,7 +241,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PutContent(string app, string name, Guid id, [FromBody] NamedContentData request) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = new UpdateContent { ContentId = id, Data = request.ToCleaned() }; var context = await CommandBus.PublishAsync(command); @@ -300,7 +273,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PatchContent(string app, string name, Guid id, [FromBody] NamedContentData request) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = new PatchContent { ContentId = id, Data = request.ToCleaned() }; var context = await CommandBus.PublishAsync(command); @@ -332,7 +305,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PublishContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = CreateCommand(id, Status.Published, dueTime); @@ -362,7 +335,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task UnpublishContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = CreateCommand(id, Status.Draft, dueTime); @@ -392,7 +365,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task ArchiveContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = CreateCommand(id, Status.Archived, dueTime); @@ -422,7 +395,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task RestoreContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = CreateCommand(id, Status.Draft, dueTime); @@ -450,7 +423,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task DeleteContent(string app, string name, Guid id) { - await contentQuery.FindSchemaAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); var command = new DeleteContent { ContentId = id }; diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/AddFieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/AddFieldDto.cs index 515c2eaa3..8ce6cd996 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/AddFieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/AddFieldDto.cs @@ -33,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models public AddField ToCommand() { - return SimpleMapper.Map(this, new AddField { Properties = this.Properties.ToProperties() }); + return SimpleMapper.Map(this, new AddField { Properties = Properties.ToProperties() }); } } } \ No newline at end of file diff --git a/src/Squidex/Config/ServiceExtensions.cs b/src/Squidex/Config/ServiceExtensions.cs index c9ed0ab12..0d5838820 100644 --- a/src/Squidex/Config/ServiceExtensions.cs +++ b/src/Squidex/Config/ServiceExtensions.cs @@ -33,7 +33,7 @@ namespace Squidex.Config { if (typeof(TInterface) != typeof(T)) { - this.services.AddSingleton(typeof(TInterface), c => + services.AddSingleton(typeof(TInterface), c => { return c.GetRequiredService(); }); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs index 3d95e6342..6b0ff52ab 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs @@ -315,6 +315,29 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent Assert.Equal(expected, actual); } + [Fact] + public void Should_not_include_invalid_field_types() + { + var input = + new NamedContentData() + .AddField("field1", + new ContentFieldData() + .AddValue("iv", "INVALID")) + .AddField("field2", + new ContentFieldData() + .AddValue("iv", 2)); + + var actual = input.ToApiModel(schema, languagesConfig, checkTypeCompatibility: true); + + var expected = + new NamedContentData() + .AddField("field2", + new ContentFieldData() + .AddValue("iv", 2)); + + Assert.Equal(expected, actual); + } + [Fact] public void Should_return_original_when_no_language_preferences_defined() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index 38a364b07..4cca28da4 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -12,7 +12,9 @@ using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; using Microsoft.OData.UriParser; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Edm; @@ -27,6 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents public class ContentQueryServiceTests { private readonly IContentRepository contentRepository = A.Fake(); + private readonly IContentVersionLoader contentVersionLoader = A.Fake(); private readonly IScriptEngine scriptEngine = A.Fake(); private readonly ISchemaEntity schema = A.Fake(); private readonly IContentEntity content = A.Fake(); @@ -49,12 +52,15 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => app.Id).Returns(appId); A.CallTo(() => app.Name).Returns(appName); + A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English); A.CallTo(() => content.Id).Returns(contentId); A.CallTo(() => content.Data).Returns(contentData); A.CallTo(() => content.Status).Returns(Status.Published); - sut = new ContentQueryService(contentRepository, appProvider, scriptEngine, modelBuilder); + A.CallTo(() => schema.SchemaDef).Returns(new Schema("my-schema")); + + sut = new ContentQueryService(contentRepository, contentVersionLoader, appProvider, scriptEngine, modelBuilder); } [Fact] @@ -63,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); - var result = await sut.FindSchemaAsync(app, schemaId.ToString()); + var result = await sut.GetSchemaAsync(app, schemaId.ToString()); Assert.Equal(schema, result); } @@ -74,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) .Returns(schema); - var result = await sut.FindSchemaAsync(app, "my-schema"); + var result = await sut.GetSchemaAsync(app, "my-schema"); Assert.Equal(schema, result); } @@ -85,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) .Returns((ISchemaEntity)null); - await Assert.ThrowsAsync(() => sut.FindSchemaAsync(app, "my-schema")); + await Assert.ThrowsAsync(() => sut.GetSchemaAsync(app, "my-schema")); } [Fact] @@ -104,10 +110,28 @@ namespace Squidex.Domain.Apps.Entities.Contents var result = await sut.FindContentAsync(app, schemaId.ToString(), user, contentId); - Assert.Equal(schema, result.Schema); + Assert.Equal(contentTransformed, result.Data); + Assert.Equal(content.Id, result.Id); + } - Assert.Equal(contentTransformed, result.Content.Data); - Assert.Equal(content.Id, result.Content.Id); + [Fact] + public async Task Should_return_versioned_content_from_repository_and_transform() + { + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) + .Returns(schema); + A.CallTo(() => contentVersionLoader.LoadAsync(contentId, 10)) + .Returns(content); + + A.CallTo(() => schema.ScriptQuery) + .Returns(""); + + A.CallTo(() => scriptEngine.Transform(A.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "")) + .Returns(contentTransformed); + + var result = await sut.FindContentAsync(app, schemaId.ToString(), user, contentId, 10); + + Assert.Equal(contentTransformed, result.Data); + Assert.Equal(content.Id, result.Id); } [Fact] @@ -161,12 +185,10 @@ namespace Squidex.Domain.Apps.Entities.Contents var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, string.Empty); - Assert.Equal(schema, result.Schema); + Assert.Equal(contentData, result[0].Data); + Assert.Equal(content.Id, result[0].Id); - Assert.Equal(contentData, result.Contents[0].Data); - Assert.Equal(content.Id, result.Contents[0].Id); - - Assert.Equal(123, result.Contents.Total); + Assert.Equal(123, result.Total); } private async Task TestManyIdRequest(bool isFrontend, bool archive, HashSet ids, params Status[] status) @@ -178,12 +200,10 @@ namespace Squidex.Domain.Apps.Entities.Contents var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, ids); - Assert.Equal(schema, result.Schema); - - Assert.Equal(contentData, result.Contents[0].Data); - Assert.Equal(content.Id, result.Contents[0].Id); + Assert.Equal(contentData, result[0].Data); + Assert.Equal(content.Id, result[0].Id); - Assert.Equal(123, result.Contents.Total); + Assert.Equal(123, result.Total); } private void SetupClaims(bool isFrontend) diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index ae5bc90fc..1c17347ed 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -282,7 +282,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var contents = new List { content }; A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, "?$top=30&$skip=5")) - .Returns((schema, ResultList.Create(contents, 0))); + .Returns(ResultList.Create(contents, 0)); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); @@ -400,7 +400,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var contents = new List { content }; A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, "?$top=30&$skip=5")) - .Returns((schema, ResultList.Create(contents, 10))); + .Returns(ResultList.Create(contents, 10)); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); @@ -517,8 +517,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }} }}"; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) - .Returns((schema, content)); + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + .Returns(content); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); @@ -610,11 +610,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var refContents = new List { contentRef }; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) - .Returns((schema, content)); + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + .Returns(content); A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, A>.That.Matches(x => x.Contains(contentRefId)))) - .Returns((schema, ResultList.Create(refContents, 0))); + .Returns(ResultList.Create(refContents, 0)); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); @@ -670,8 +670,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var refAssets = new List { assetRef }; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) - .Returns((schema, content)); + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + .Returns(content); A.CallTo(() => assetRepository.QueryAsync(app.Id, A>.That.Matches(x => x.Contains(assetRefId)))) .Returns(ResultList.Create(refAssets, 0)); @@ -729,8 +729,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }} }}"; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) - .Returns((schema, content)); + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + .Returns(content); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); diff --git a/tests/Squidex.Infrastructure.Tests/Caching/HttpRequestCacheTests.cs b/tests/Squidex.Infrastructure.Tests/Caching/HttpRequestCacheTests.cs index 271c7b76a..c8a64dfa5 100644 --- a/tests/Squidex.Infrastructure.Tests/Caching/HttpRequestCacheTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Caching/HttpRequestCacheTests.cs @@ -13,7 +13,7 @@ using Xunit; namespace Squidex.Infrastructure.Caching { - public sealed class HttpRequestCacheTests + public class HttpRequestCacheTests { private readonly IHttpContextAccessor httpContextAccessor = A.Fake(); private readonly IRequestCache sut; diff --git a/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs index 0a28ef533..95d2976ab 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs @@ -19,7 +19,7 @@ using Xunit; namespace Squidex.Infrastructure.Commands { - public sealed class DomainObjectGrainTests + public class DomainObjectGrainTests { private readonly IStore store = A.Fake>(); private readonly IPersistence persistence = A.Fake>(); diff --git a/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs b/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs index 037aab083..9db48683e 100644 --- a/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs @@ -17,7 +17,7 @@ using Xunit; namespace Squidex.Infrastructure.Orleans { - public sealed class BootstrapTests + public class BootstrapTests { private readonly IBackgroundGrain grain = A.Fake(); private readonly Bootstrap sut; diff --git a/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockPoolTests.cs b/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockPoolTests.cs index 8253783e3..a465fde93 100644 --- a/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockPoolTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockPoolTests.cs @@ -12,7 +12,7 @@ using Xunit; namespace Squidex.Infrastructure.Tasks { - public sealed class AsyncLockPoolTests + public class AsyncLockPoolTests { [Fact] public async Task Should_lock() diff --git a/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockTests.cs b/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockTests.cs index 58765fb66..99863cf13 100644 --- a/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Tasks/AsyncLockTests.cs @@ -12,7 +12,7 @@ using Xunit; namespace Squidex.Infrastructure.Tasks { - public sealed class AsyncLockTests + public class AsyncLockTests { [Fact] public async Task Should_lock() diff --git a/tests/Squidex.Infrastructure.Tests/UsageTracking/ThreadingUsageTrackerTests.cs b/tests/Squidex.Infrastructure.Tests/UsageTracking/ThreadingUsageTrackerTests.cs index 40a22b56a..41216ed30 100644 --- a/tests/Squidex.Infrastructure.Tests/UsageTracking/ThreadingUsageTrackerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/UsageTracking/ThreadingUsageTrackerTests.cs @@ -14,7 +14,7 @@ using Xunit; namespace Squidex.Infrastructure.UsageTracking { - public sealed class ThreadingUsageTrackerTests + public class ThreadingUsageTrackerTests { private readonly MemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly IUsageTracker inner = A.Fake();