Browse Source

Upsert patch. (#872)

pull/874/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
e0cdd3bfc1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs
  2. 3
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx
  3. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateJob.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpsertContent.cs
  5. 10
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  6. 9
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs
  7. 16
      backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs
  8. 5
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/BulkUpdateContentsJobDto.cs
  9. 15
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/UpsertContentDto.cs
  10. 22
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs
  11. 100
      backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs
  12. 2
      backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj

9
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs

@ -492,6 +492,15 @@ namespace Squidex.Domain.Apps.Core {
}
}
/// <summary>
/// Looks up a localized string similar to Makes the update as patch..
/// </summary>
public static string ContentRequestPatch {
get {
return ResourceManager.GetString("ContentRequestPatch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Set to true to autopublish content on create..
/// </summary>

3
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx

@ -261,6 +261,9 @@
<data name="ContentRequestOptionalStatus" xml:space="preserve">
<value>The initial status.</value>
</data>
<data name="ContentRequestPatch" xml:space="preserve">
<value>Makes the update as patch.</value>
</data>
<data name="ContentRequestPublish" xml:space="preserve">
<value>Set to true to autopublish content on create.</value>
</data>

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateJob.cs

@ -29,6 +29,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public string? Schema { get; set; }
public bool Patch { get; set; }
public bool Permanent { get; set; }
public long ExpectedCount { get; set; } = 1;

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpsertContent.cs

@ -17,6 +17,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public bool CheckReferrers { get; set; }
public bool Patch { get; set; }
public UpsertContent()
{
ContentId = DomainId.NewGuid();

10
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs

@ -72,13 +72,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
if (Version > EtagVersion.Empty && !IsDeleted(Snapshot))
if (Version <= EtagVersion.Empty || IsDeleted(Snapshot))
{
await UpdateCore(c.AsUpdate(), operation);
await CreateCore(c.AsCreate(), operation);
}
else if (c.Patch)
{
await PatchCore(c.AsUpdate(), operation);
}
else
{
await CreateCore(c.AsCreate(), operation);
await UpdateCore(c.AsUpdate(), operation);
}
if (Is.OptionalChange(operation.Snapshot.EditingStatus(), c.Status))

9
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs

@ -277,6 +277,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = FieldDescriptions.ContentRequestPublish,
DefaultValue = false
},
new QueryArgument(AllTypes.Boolean)
{
Name = "patch",
Description = FieldDescriptions.ContentRequestPatch,
DefaultValue = false
},
new QueryArgument(AllTypes.String)
{
Name = "status",
@ -297,8 +303,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
var contentId = c.GetArgument<DomainId>("id");
var contentData = c.GetArgument<ContentData>("data")!;
var contentStatus = c.GetArgument<string?>("status");
var patch = c.GetArgument<bool>("patch");
var command = new UpsertContent { ContentId = contentId, Data = contentData };
var command = new UpsertContent { ContentId = contentId, Data = contentData, Patch = patch };
if (!string.IsNullOrWhiteSpace(contentStatus))
{

16
backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs

@ -9,13 +9,14 @@ using NJsonSchema;
using NSwag;
using NSwag.Generation;
using NSwag.Generation.Processors.Contexts;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Hosting;
using Squidex.Infrastructure.Caching;
using Squidex.Properties;
using Squidex.Shared;
using IRequestUrlGenerator = Squidex.Hosting.IUrlGenerator;
using SchemaDefType = Squidex.Domain.Apps.Core.Schemas.SchemaType;
namespace Squidex.Areas.Api.Controllers.Contents.Generator
@ -23,16 +24,16 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
public sealed class SchemasOpenApiGenerator
{
private readonly IAppProvider appProvider;
private readonly IUrlGenerator urlGenerator;
private readonly OpenApiDocumentGeneratorSettings schemaSettings;
private readonly OpenApiSchemaGenerator schemaGenerator;
private readonly IRequestUrlGenerator urlGenerator;
private readonly IRequestCache requestCache;
public SchemasOpenApiGenerator(
IAppProvider appProvider,
IUrlGenerator urlGenerator,
OpenApiDocumentGeneratorSettings schemaSettings,
OpenApiSchemaGenerator schemaGenerator,
IRequestUrlGenerator urlGenerator,
IRequestCache requestCache)
{
this.appProvider = appProvider;
@ -123,7 +124,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
.RequirePermission(Permissions.AppContentsReadOwn)
.Operation("GetVersioned")
.OperationSummary("Get a [schema] content item by id and version.")
.HasPath("version", JsonObjectType.Number, "The version of the content item.")
.HasPath("version", JsonObjectType.Number, FieldDescriptions.EntityVersion)
.HasId()
.Responds(200, "Content item returned.", builder.ContentSchema);
@ -139,8 +140,8 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
.RequirePermission(Permissions.AppContentsCreate)
.Operation("Create")
.OperationSummary("Create a [schema] content item.")
.HasQuery("publish", JsonObjectType.Boolean, "True to automatically publish the content.")
.HasQuery("id", JsonObjectType.String, "The optional custom content id.")
.HasQuery("publish", JsonObjectType.Boolean, FieldDescriptions.ContentRequestPublish)
.HasQuery("id", JsonObjectType.String, FieldDescriptions.ContentRequestOptionalId)
.HasBody("data", builder.DataSchema, Resources.OpenApiSchemaBody)
.Responds(201, "Content item created", builder.ContentSchema)
.Responds(400, "Content data not valid.");
@ -149,7 +150,8 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
.RequirePermission(Permissions.AppContentsUpsert)
.Operation("Upsert")
.OperationSummary("Upsert a [schema] content item.")
.HasQuery("publish", JsonObjectType.Boolean, "True to automatically publish the content.")
.HasQuery("patch", JsonObjectType.Boolean, FieldDescriptions.ContentRequestPatch)
.HasQuery("publish", JsonObjectType.Boolean, FieldDescriptions.ContentRequestPublish)
.HasId()
.HasBody("data", builder.DataSchema, Resources.OpenApiSchemaBody)
.Responds(200, "Content item created or updated.", builder.ContentSchema)

5
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/BulkUpdateContentsJobDto.cs

@ -50,6 +50,11 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary>
public string? Schema { get; set; }
/// <summary>
/// Makes the update as patch.
/// </summary>
public bool Patch { get; set; }
/// <summary>
/// True to delete the content permanently.
/// </summary>

15
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/UpsertContentDto.cs

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using StatusType = Squidex.Domain.Apps.Core.Contents.Status;
namespace Squidex.Areas.Api.Controllers.Contents.Models
@ -27,6 +28,12 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
[FromQuery]
public StatusType? Status { get; set; }
/// <summary>
/// Makes the update as patch.
/// </summary>
[FromQuery]
public bool Patch { get; set; }
/// <summary>
/// True to automatically publish the content.
/// </summary>
@ -36,14 +43,10 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
public UpsertContent ToCommand(DomainId id)
{
var command = new UpsertContent { Data = Data!, ContentId = id };
var command = SimpleMapper.Map(this, new UpsertContent { ContentId = id });
if (Status != null)
{
command.Status = Status;
}
#pragma warning disable CS0618 // Type or member is obsolete
else if (Publish)
if (command.Status == null && Publish)
{
command.Status = StatusType.Published;
}

22
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs

@ -325,6 +325,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
.MustHaveHappened();
}
[Fact]
public async Task Upsert_should_patch_content_if_found()
{
var command = new UpsertContent { Data = patch, Patch = true };
await ExecuteCreateAsync();
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(patched, sut.Snapshot.CurrentVersion.Data);
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentUpdated { Data = patched })
);
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "<update-script>", ScriptOptions(), default))
.MustHaveHappened();
}
[Fact]
public async Task Upsert_should_not_change_status_on_update_if_status_set_to_initial()
{

100
backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs

@ -465,6 +465,106 @@ namespace TestSuite.ApiTests
}
}
[Fact]
public async Task Should_patch_content_with_upsert()
{
TestEntity content = null;
try
{
// STEP 1: Create a new item.
content = await _.Contents.CreateAsync(new TestEntityData { String = "test" }, ContentCreateOptions.AsPublish);
// STEP 2: Path an item.
await _.Contents.UpsertAsync(content.Id, new TestEntityData { Number = 1 }, ContentUpsertOptions.AsPatch);
// STEP 3: Update the item and ensure that the data has changed.
await _.Contents.UpsertAsync(content.Id, new TestEntityData { Number = 2 }, ContentUpsertOptions.AsPatch);
var updated = await _.Contents.GetAsync(content.Id);
Assert.Equal(2, updated.Data.Number);
// Should not change other value with patch.
Assert.Equal("test", updated.Data.String);
}
finally
{
if (content != null)
{
await _.Contents.DeleteAsync(content.Id);
}
}
}
[Fact]
public async Task Should_patch_content_with_bulk()
{
TestEntity content = null;
try
{
// STEP 1: Create a new item.
content = await _.Contents.CreateAsync(new TestEntityData { String = "test" }, ContentCreateOptions.AsPublish);
// STEP 2: Path an item.
await _.Contents.BulkUpdateAsync(new BulkUpdate
{
Jobs = new List<BulkUpdateJob>
{
new BulkUpdateJob
{
Id = content.Id,
Data = new
{
number = new
{
iv = 1
}
},
Patch = true
}
}
});
// STEP 3: Update the item and ensure that the data has changed.
await _.Contents.BulkUpdateAsync(new BulkUpdate
{
Jobs = new List<BulkUpdateJob>
{
new BulkUpdateJob
{
Id = content.Id,
Data = new
{
number = new
{
iv = 2
}
},
Patch = true
}
}
});
var updated = await _.Contents.GetAsync(content.Id);
Assert.Equal(2, updated.Data.Number);
// Should not change other value with patch.
Assert.Equal("test", updated.Data.String);
}
finally
{
if (content != null)
{
await _.Contents.DeleteAsync(content.Id);
}
}
}
[Fact]
public async Task Should_create_draft_version()
{

2
backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj

@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.ClientLibrary" Version="8.16.0" />
<PackageReference Include="Squidex.ClientLibrary" Version="8.17.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>

Loading…
Cancel
Save