Browse Source

A lot of improvements.

pull/343/head
Sebastian Stehle 7 years ago
parent
commit
e9433db0b3
  1. 6
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs
  2. 30
      src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  3. 55
      src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  4. 22
      src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs
  5. 47
      src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs
  6. 29
      src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs
  7. 35
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs
  8. 23
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs
  9. 10
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs
  10. 13
      src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs
  11. 11
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  12. 10
      src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  13. 3
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  14. 1
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs
  15. 2
      src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs
  16. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs
  17. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs
  18. 8
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs
  19. 44
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs
  20. 12
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs
  21. 4
      src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs
  22. 22
      src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs
  23. 2
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs
  24. 8
      src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigurePreviewUrlsDto.cs
  25. 2
      src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs
  26. 15
      src/Squidex/Areas/Api/Controllers/Schemas/Models/PreviewUrlsDto.cs
  27. 38
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs
  28. 48
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs
  29. 2
      src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs
  30. 2
      src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs
  31. 16
      src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  32. 3
      src/Squidex/Config/Domain/EntitiesServices.cs
  33. 2
      src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html
  34. 4
      src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts
  35. 8
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  36. 2
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  37. 30
      src/Squidex/app/shared/services/schemas.service.spec.ts
  38. 41
      src/Squidex/app/shared/services/schemas.service.ts
  39. 2
      src/Squidex/app/shared/state/contents.forms.spec.ts
  40. 12
      src/Squidex/app/shared/state/schemas.forms.ts
  41. 32
      src/Squidex/app/shared/state/schemas.state.spec.ts
  42. 13
      src/Squidex/app/shared/state/schemas.state.ts
  43. 42
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs
  44. 27
      tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs
  45. 11
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs
  46. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  47. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  48. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/SingletonCommandMiddlewareTests.cs
  49. 12
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs
  50. 8
      tools/Migrate_01/MigrationPath.cs
  51. 30
      tools/Migrate_01/Migrations/ClearSchemas.cs
  52. 12
      tools/Migrate_01/OldEvents/ScriptsConfigured.cs

6
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs

@ -32,10 +32,10 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
public SchemaProperties Properties { get; set; } public SchemaProperties Properties { get; set; }
[JsonProperty] [JsonProperty]
public JsonFieldModel[] Fields { get; set; } public SchemaScripts Scripts { get; set; }
[JsonProperty] [JsonProperty]
public IReadOnlyDictionary<string, string> Scripts { get; set; } public JsonFieldModel[] Fields { get; set; }
[JsonProperty] [JsonProperty]
public IReadOnlyDictionary<string, string> PreviewUrls { get; set; } public IReadOnlyDictionary<string, string> PreviewUrls { get; set; }
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
schema = schema.ChangeCategory(Category); schema = schema.ChangeCategory(Category);
} }
if (Scripts?.Count > 0) if (Scripts != null)
{ {
schema = schema.ConfigureScripts(Scripts); schema = schema.ConfigureScripts(Scripts);
} }

30
src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs

@ -14,14 +14,13 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public sealed class Schema : Cloneable<Schema> public sealed class Schema : Cloneable<Schema>
{ {
private static readonly Dictionary<string, string> EmptyScripts = new Dictionary<string, string>();
private static readonly Dictionary<string, string> EmptyPreviewUrls = new Dictionary<string, string>(); private static readonly Dictionary<string, string> EmptyPreviewUrls = new Dictionary<string, string>();
private readonly string name; private readonly string name;
private readonly bool isSingleton; private readonly bool isSingleton;
private string category; private string category;
private FieldCollection<RootField> fields = FieldCollection<RootField>.Empty; private FieldCollection<RootField> fields = FieldCollection<RootField>.Empty;
private IReadOnlyDictionary<string, string> scripts = EmptyScripts;
private IReadOnlyDictionary<string, string> previewUrls = EmptyPreviewUrls; private IReadOnlyDictionary<string, string> previewUrls = EmptyPreviewUrls;
private SchemaScripts scripts = new SchemaScripts();
private SchemaProperties properties; private SchemaProperties properties;
private bool isPublished; private bool isPublished;
@ -60,11 +59,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return fields.ByName; } get { return fields.ByName; }
} }
public IReadOnlyDictionary<string, string> Scripts
{
get { return scripts; }
}
public IReadOnlyDictionary<string, string> PreviewUrls public IReadOnlyDictionary<string, string> PreviewUrls
{ {
get { return previewUrls; } get { return previewUrls; }
@ -75,6 +69,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return fields; } get { return fields; }
} }
public SchemaScripts Scripts
{
get { return scripts; }
}
public SchemaProperties Properties public SchemaProperties Properties
{ {
get { return properties; } get { return properties; }
@ -115,38 +114,39 @@ namespace Squidex.Domain.Apps.Core.Schemas
} }
[Pure] [Pure]
public Schema Publish() public Schema ConfigureScripts(SchemaScripts newScripts)
{ {
return Clone(clone => return Clone(clone =>
{ {
clone.isPublished = true; clone.scripts = newScripts ?? new SchemaScripts();
clone.scripts.Freeze();
}); });
} }
[Pure] [Pure]
public Schema Unpublish() public Schema Publish()
{ {
return Clone(clone => return Clone(clone =>
{ {
clone.isPublished = false; clone.isPublished = true;
}); });
} }
[Pure] [Pure]
public Schema ChangeCategory(string category) public Schema Unpublish()
{ {
return Clone(clone => return Clone(clone =>
{ {
clone.category = category; clone.isPublished = false;
}); });
} }
[Pure] [Pure]
public Schema ConfigureScripts(IReadOnlyDictionary<string, string> scripts) public Schema ChangeCategory(string category)
{ {
return Clone(clone => return Clone(clone =>
{ {
clone.scripts = scripts ?? EmptyScripts; clone.category = category;
}); });
} }

55
src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs

@ -0,0 +1,55 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using System;
namespace Squidex.Domain.Apps.Core.Schemas
{
public static class SchemaExtensions
{
public static long MaxId(this Schema schema)
{
var id = 0L;
foreach (var field in schema.Fields)
{
if (field is IArrayField arrayField)
{
foreach (var nestedField in arrayField.Fields)
{
id = Math.Max(id, nestedField.Id);
}
}
id = Math.Max(id, field.Id);
}
return id;
}
public static string TypeName(this IField field)
{
return field.Name.ToPascalCase();
}
public static string DisplayName(this IField field)
{
return field.RawProperties.Label.WithFallback(field.TypeName());
}
public static string TypeName(this Schema schema)
{
return schema.Name.ToPascalCase();
}
public static string DisplayName(this Schema schema)
{
return schema.Properties.Label.WithFallback(schema.TypeName());
}
}
}

22
src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs

@ -0,0 +1,22 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class SchemaScripts : Freezable
{
public string Change { get; set; }
public string Create { get; set; }
public string Update { get; set; }
public string Delete { get; set; }
public string Query { get; set; }
}
}

47
src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs

@ -1,47 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core.Schemas
{
public static class Scripts
{
public const string Change = "Change";
public const string Create = "Create";
public const string Delete = "Delete";
public const string Update = "Update";
public const string Query = "Query";
public static string GetChange(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Change);
}
public static string GetCreate(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Create);
}
public static string GetQuery(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Query);
}
public static string GetUpdate(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Update);
}
public static string GetDelete(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Delete);
}
}
}

29
src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs

@ -48,9 +48,9 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
yield return E(new SchemaCategoryChanged { Name = target.Category }); yield return E(new SchemaCategoryChanged { Name = target.Category });
} }
if (!source.Scripts.EqualsDictionary(target.Scripts)) if (!source.Scripts.EqualsJson(target.Scripts, serializer))
{ {
yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts.ToDictionary(x => x.Key, x => x.Value) }); yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts });
} }
if (!source.PreviewUrls.EqualsDictionary(target.PreviewUrls)) if (!source.PreviewUrls.EqualsDictionary(target.PreviewUrls))
@ -115,6 +115,8 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
if (source.ByName.TryGetValue(targetField.Name, out var sourceField)) if (source.ByName.TryGetValue(targetField.Name, out var sourceField))
{ {
canCreateField = false;
id = sourceField.NamedId(); id = sourceField.NamedId();
if (CanUpdate(sourceField, targetField)) if (CanUpdate(sourceField, targetField))
@ -126,14 +128,16 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
} }
else if (!sourceField.IsLocked && !options.NoFieldRecreation) else if (!sourceField.IsLocked && !options.NoFieldRecreation)
{ {
canCreateField = true;
sourceIds.Remove(id);
sourceNames.Remove(id.Name);
yield return E(new FieldDeleted { FieldId = id }); yield return E(new FieldDeleted { FieldId = id });
} }
else
{
canCreateField = false;
}
} }
else if (canCreateField)
if (canCreateField)
{ {
var partitioning = (string)null; var partitioning = (string)null;
@ -192,11 +196,14 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
} }
} }
var targetNames = target.Ordered.Select(x => x.Name); if (sourceNames.Count > 1)
if (sourceNames.Intersect(targetNames).Count() == target.Ordered.Count && !sourceNames.SequenceEqual(targetNames))
{ {
yield return new SchemaFieldsReordered { FieldIds = sourceIds.Select(x => x.Id).ToList(), ParentFieldId = parentId }; var targetNames = target.Ordered.Select(x => x.Name);
if (sourceNames.Intersect(targetNames).Count() == target.Ordered.Count && !sourceNames.SequenceEqual(targetNames))
{
yield return new SchemaFieldsReordered { FieldIds = sourceIds.Select(x => x.Id).ToList(), ParentFieldId = parentId };
}
} }
} }

35
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs

@ -24,20 +24,39 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
public static SchemaBuilder Create(string name) public static SchemaBuilder Create(string name)
{ {
var schemaName = name.ToKebabCase();
return new SchemaBuilder(new CreateSchema return new SchemaBuilder(new CreateSchema
{ {
Name = name.ToKebabCase(), Name = schemaName
Publish = true, }).Published().WithLabel(name);
Properties = new SchemaProperties }
{
Label = name public SchemaBuilder WithLabel(string label)
} {
}); command.Properties = command.Properties ?? new SchemaProperties();
command.Properties.Label = label;
return this;
}
public SchemaBuilder WithScripts(SchemaScripts scripts)
{
command.Scripts = scripts;
return this;
}
public SchemaBuilder Published()
{
command.IsPublished = true;
return this;
} }
public SchemaBuilder Singleton() public SchemaBuilder Singleton()
{ {
command.Singleton = true; command.IsSingleton = true;
return this; return this;
} }

23
src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs

@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Templates.Builders; using Squidex.Domain.Apps.Entities.Apps.Templates.Builders;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -106,19 +105,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
.Disabled() .Disabled()
.Label("Slug (Autogenerated)") .Label("Slug (Autogenerated)")
.Hints("Autogenerated slug that can be used to identity the post.")) .Hints("Autogenerated slug that can be used to identity the post."))
.WithScripts(DefaultScripts.GenerateSlug)
.Build(); .Build();
await publish(schema); await publish(schema);
var schemaId = NamedId.Of(schema.SchemaId, schema.Name); return NamedId.Of(schema.SchemaId, schema.Name);
await publish(new ConfigureScripts
{
SchemaId = schemaId.Id,
Scripts = DefaultScripts.GenerateSlug
});
return schemaId;
} }
private static async Task<NamedId<Guid>> CreatePagesSchemaAsync(Func<ICommand, Task> publish) private static async Task<NamedId<Guid>> CreatePagesSchemaAsync(Func<ICommand, Task> publish)
@ -139,19 +131,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
.Disabled() .Disabled()
.Label("Slug (Autogenerated)") .Label("Slug (Autogenerated)")
.Hints("Autogenerated slug that can be used to identity the page.")) .Hints("Autogenerated slug that can be used to identity the page."))
.WithScripts(DefaultScripts.GenerateSlug)
.Build(); .Build();
await publish(schema); await publish(schema);
var schemaId = NamedId.Of(schema.SchemaId, schema.Name); return NamedId.Of(schema.SchemaId, schema.Name);
await publish(new ConfigureScripts
{
SchemaId = schemaId.Id,
Scripts = DefaultScripts.GenerateSlug
});
return schemaId;
} }
} }
} }

10
src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs

@ -9,7 +9,6 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Templates.Builders; using Squidex.Domain.Apps.Entities.Apps.Templates.Builders;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -229,17 +228,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
.AddString("Security Stamp", f => f .AddString("Security Stamp", f => f
.Disabled() .Disabled()
.Hints("Internal security stamp")) .Hints("Internal security stamp"))
.WithScripts(DefaultScripts.GenerateUsername)
.Build(); .Build();
await publish(schema); await publish(schema);
var schemaId = NamedId.Of(schema.SchemaId, schema.Name);
await publish(new ConfigureScripts
{
SchemaId = schemaId.Id,
Scripts = DefaultScripts.GenerateUsername
});
} }
private static Task CreateApiResourcesSchemaAsync(Func<ICommand, Task> publish) private static Task CreateApiResourcesSchemaAsync(Func<ICommand, Task> publish)

13
src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Apps.Templates namespace Squidex.Domain.Apps.Entities.Apps.Templates
@ -34,16 +33,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
replace(data);"; replace(data);";
public static readonly Dictionary<string, string> GenerateSlug = new Dictionary<string, string> public static readonly SchemaScripts GenerateSlug = new SchemaScripts
{ {
[Scripts.Create] = ScriptToGenerateSlug, Create = ScriptToGenerateSlug,
[Scripts.Update] = ScriptToGenerateSlug Update = ScriptToGenerateSlug
}; };
public static readonly Dictionary<string, string> GenerateUsername = new Dictionary<string, string> public static readonly SchemaScripts GenerateUsername = new SchemaScripts
{ {
[Scripts.Create] = ScriptToGenerateUsername, Create = ScriptToGenerateUsername,
[Scripts.Update] = ScriptToGenerateUsername Update = ScriptToGenerateUsername
}; };
} }
} }

11
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
@ -67,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
GuardContent.CanCreate(ctx.Schema, c); GuardContent.CanCreate(ctx.Schema, c);
await ctx.ExecuteScriptAndTransformAsync(Scripts.Create, "Create", c, c.Data); await ctx.ExecuteScriptAndTransformAsync(s => s.Create, "Create", c, c.Data);
await ctx.EnrichAsync(c.Data); await ctx.EnrichAsync(c.Data);
if (!c.DoNotValidate) if (!c.DoNotValidate)
@ -77,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (c.Publish) if (c.Publish)
{ {
await ctx.ExecuteScriptAsync(Scripts.Change, "Published", c, c.Data); await ctx.ExecuteScriptAsync(s => s.Change, "Published", c, c.Data);
} }
Create(c); Create(c);
@ -141,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
reason = StatusChange.Restored; reason = StatusChange.Restored;
} }
await ctx.ExecuteScriptAsync(Scripts.Change, reason, c, Snapshot.Data); await ctx.ExecuteScriptAsync(s => s.Change, reason, c, Snapshot.Data);
ChangeStatus(c, reason); ChangeStatus(c, reason);
} }
@ -167,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
GuardContent.CanDelete(ctx.Schema, c); GuardContent.CanDelete(ctx.Schema, c);
await ctx.ExecuteScriptAsync(Scripts.Delete, "Delete", c, Snapshot.Data); await ctx.ExecuteScriptAsync(s => s.Delete, "Delete", c, Snapshot.Data);
Delete(c); Delete(c);
}); });
@ -209,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await ctx.ValidateAsync(c.Data); await ctx.ValidateAsync(c.Data);
} }
newData = await ctx.ExecuteScriptAndTransformAsync(Scripts.Update, "Update", c, newData, Snapshot.Data); newData = await ctx.ExecuteScriptAndTransformAsync(s => s.Update, "Update", c, newData, Snapshot.Data);
if (isProposal) if (isProposal)
{ {

10
src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.EnrichContent; using Squidex.Domain.Apps.Core.EnrichContent;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
@ -17,7 +18,6 @@ using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), message); return data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), message);
} }
public Task<NamedContentData> ExecuteScriptAndTransformAsync(string script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) public Task<NamedContentData> ExecuteScriptAndTransformAsync(Func<SchemaScripts, string> script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null)
{ {
var ctx = CreateScriptContext(operation, command, data, oldData); var ctx = CreateScriptContext(operation, command, data, oldData);
@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return Task.FromResult(result); return Task.FromResult(result);
} }
public Task ExecuteScriptAsync(string script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) public Task ExecuteScriptAsync(Func<SchemaScripts, string> script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null)
{ {
var ctx = CreateScriptContext(operation, command, data, oldData); var ctx = CreateScriptContext(operation, command, data, oldData);
@ -125,9 +125,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode); return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode);
} }
private string GetScript(string script) private string GetScript(Func<SchemaScripts, string> script)
{ {
return schemaEntity.SchemaDef.Scripts.GetOrDefault(script); return script(schemaEntity.SchemaDef.Scripts);
} }
} }
} }

3
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -14,7 +14,6 @@ using Microsoft.Extensions.Options;
using Microsoft.OData; using Microsoft.OData;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.Edm;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
@ -159,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var converters = GenerateConverters(context, checkType).ToArray(); var converters = GenerateConverters(context, checkType).ToArray();
var scriptText = schema.SchemaDef.Scripts.GetQuery(); var scriptText = schema.SchemaDef.Scripts.Query;
var isScripting = !string.IsNullOrWhiteSpace(scriptText); var isScripting = !string.IsNullOrWhiteSpace(scriptText);

1
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs

@ -10,6 +10,7 @@ using System.Linq;
using GraphQL.Resolvers; using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;

2
src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs

@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (context.IsCompleted && if (context.IsCompleted &&
context.Command is CreateSchema createSchema && context.Command is CreateSchema createSchema &&
createSchema.Singleton) createSchema.IsSingleton)
{ {
var schemaId = NamedId.Of(createSchema.SchemaId, createSchema.Name); var schemaId = NamedId.Of(createSchema.SchemaId, createSchema.Name);

4
src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs

@ -5,12 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class ConfigureScripts : SchemaCommand public sealed class ConfigureScripts : SchemaCommand
{ {
public Dictionary<string, string> Scripts { get; set; } public SchemaScripts Scripts { get; set; }
} }
} }

4
src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs

@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
public string Name { get; set; } public string Name { get; set; }
public bool Singleton { get; set; } public bool IsSingleton { get; set; }
public CreateSchema() public CreateSchema()
{ {
@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
public Schema ToSchema() public Schema ToSchema()
{ {
return ToSchema(Name, Singleton); return ToSchema(Name, IsSingleton);
} }
} }
} }

8
src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs

@ -14,15 +14,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public abstract class UpsertCommand : SchemaCommand public abstract class UpsertCommand : SchemaCommand
{ {
public bool Publish { get; set; } public bool IsPublished { get; set; }
public string Category { get; set; } public string Category { get; set; }
public SchemaFields Fields { get; set; } public SchemaFields Fields { get; set; }
public SchemaProperties Properties { get; set; } public SchemaScripts Scripts { get; set; }
public Dictionary<string, string> Scripts { get; set; } public SchemaProperties Properties { get; set; }
public Dictionary<string, string> PreviewUrls { get; set; } public Dictionary<string, string> PreviewUrls { get; set; }
@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
var schema = new Schema(name, Properties, isSingleton); var schema = new Schema(name, Properties, isSingleton);
if (Publish) if (IsPublished)
{ {
schema = schema.Publish(); schema = schema.Publish();
} }

44
src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs

@ -19,59 +19,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas
return StaticNamedId.Of(schema.Id, schema.SchemaDef.Name); return StaticNamedId.Of(schema.Id, schema.SchemaDef.Name);
} }
public static long MaxId(this Schema schema)
{
var id = 0L;
foreach (var field in schema.Fields)
{
if (field is IArrayField arrayField)
{
foreach (var nestedField in arrayField.Fields)
{
id = Math.Max(id, nestedField.Id);
}
}
id = Math.Max(id, field.Id);
}
return id;
}
public static string EscapePartition(this string value) public static string EscapePartition(this string value)
{ {
return value.Replace('-', '_'); return value.Replace('-', '_');
} }
public static string TypeName(this IField field)
{
return field.Name.ToPascalCase();
}
public static string TypeName(this ISchemaEntity schema) public static string TypeName(this ISchemaEntity schema)
{ {
return schema.SchemaDef.Name.ToPascalCase(); return schema.SchemaDef.TypeName();
}
public static string DisplayName(this IField field)
{
return field.RawProperties.Label.WithFallback(field.TypeName());
} }
public static string DisplayName(this ISchemaEntity schema) public static string DisplayName(this ISchemaEntity schema)
{ {
return schema.SchemaDef.Properties.Label.WithFallback(schema.TypeName()); return schema.SchemaDef.DisplayName();
}
public static string TypeName(this Schema schema)
{
return schema.Name.ToPascalCase();
}
public static string DisplayName(this Schema schema)
{
return schema.Properties.Label.WithFallback(schema.TypeName());
} }
} }
} }

12
src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs

@ -19,6 +19,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public SchemaHistoryEventsCreator(TypeNameRegistry typeNameRegistry) public SchemaHistoryEventsCreator(TypeNameRegistry typeNameRegistry)
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage("SchemaCreated",
"created schema {[Name]}.");
AddEventMessage("ScriptsConfigured",
"configured script of schema {[Name]}.");
AddEventMessage<SchemaFieldsReordered>(
"reordered fields of schema {[Name]}.");
AddEventMessage<SchemaCreated>( AddEventMessage<SchemaCreated>(
"created schema {[Name]}."); "created schema {[Name]}.");
@ -37,6 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas
AddEventMessage<SchemaFieldsReordered>( AddEventMessage<SchemaFieldsReordered>(
"reordered fields of schema {[Name]}."); "reordered fields of schema {[Name]}.");
AddEventMessage<SchemaScriptsConfigured>(
"configured script of schema {[Name]}.");
AddEventMessage<FieldAdded>( AddEventMessage<FieldAdded>(
"added field {[Field]} to schema {[Name]}."); "added field {[Field]} to schema {[Name]}.");

4
src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Schemas namespace Squidex.Domain.Apps.Events.Schemas
@ -13,6 +13,6 @@ namespace Squidex.Domain.Apps.Events.Schemas
[EventType(nameof(SchemaScriptsConfigured))] [EventType(nameof(SchemaScriptsConfigured))]
public sealed class SchemaScriptsConfigured : SchemaEvent public sealed class SchemaScriptsConfigured : SchemaEvent
{ {
public Dictionary<string, string> Scripts { get; set; } public SchemaScripts Scripts { get; set; }
} }
} }

22
src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using NJsonSchema.Infrastructure; using NJsonSchema.Infrastructure;
@ -23,8 +24,6 @@ namespace Squidex.Areas.Api.Config.Swagger
public async Task<bool> ProcessAsync(OperationProcessorContext context) public async Task<bool> ProcessAsync(OperationProcessorContext context)
{ {
var hasOkResponse = false;
var operation = context.OperationDescription.Operation; var operation = context.OperationDescription.Operation;
var returnsDescription = await context.MethodInfo.GetXmlDocumentationTagAsync("returns") ?? string.Empty; var returnsDescription = await context.MethodInfo.GetXmlDocumentationTagAsync("returns") ?? string.Empty;
@ -41,19 +40,11 @@ namespace Squidex.Areas.Api.Config.Swagger
} }
response.Description = match.Groups["Description"].Value; response.Description = match.Groups["Description"].Value;
if (statusCode == "200" || statusCode == "204")
{
hasOkResponse = true;
}
} }
await AddInternalErrorResponseAsync(context, operation); await AddInternalErrorResponseAsync(context, operation);
if (!hasOkResponse) CleanupResponses(operation);
{
RemoveOkResponse(operation);
}
return true; return true;
} }
@ -66,11 +57,14 @@ namespace Squidex.Areas.Api.Config.Swagger
} }
} }
private static void RemoveOkResponse(SwaggerOperation operation) private static void CleanupResponses(SwaggerOperation operation)
{ {
if (operation.Responses.TryGetValue("200", out var response) && response.Description?.Contains("=>") == true) foreach (var (code, response) in operation.Responses.ToList())
{ {
operation.Responses.Remove("200"); if (string.IsNullOrWhiteSpace(response.Description) || response.Description?.Contains("=>") == true)
{
operation.Responses.Remove(code);
}
} }
} }
} }

2
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs

@ -230,7 +230,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
}); });
} }
private SwaggerPathItem AddOperation(SwaggerOperationMethod method, string entityName, string path, Action<SwaggerOperation> updater) private SwaggerPathItem AddOperation(string method, string entityName, string path, Action<SwaggerOperation> updater)
{ {
var operations = document.Paths.GetOrAddNew(path); var operations = document.Paths.GetOrAddNew(path);
var operation = new SwaggerOperation(); var operation = new SwaggerOperation();

8
src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs → src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigurePreviewUrlsDto.cs

@ -1,7 +1,7 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
@ -10,11 +10,11 @@ using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Areas.Api.Controllers.Schemas.Models namespace Squidex.Areas.Api.Controllers.Schemas.Models
{ {
public sealed class ConfigureScriptsDto : Dictionary<string, string> public sealed class ConfigurePreviewUrlsDto : Dictionary<string, string>
{ {
public ConfigureScripts ToCommand() public ConfigurePreviewUrls ToCommand()
{ {
return new ConfigureScripts { Scripts = this }; return new ConfigurePreviewUrls { PreviewUrls = this };
} }
} }
} }

2
src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs

@ -22,7 +22,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// <summary> /// <summary>
/// Set to true to allow a single content item only. /// Set to true to allow a single content item only.
/// </summary> /// </summary>
public bool Singleton { get; set; } public bool IsSingleton { get; set; }
public CreateSchema ToCommand() public CreateSchema ToCommand()
{ {

15
src/Squidex/Areas/Api/Controllers/Schemas/Models/PreviewUrlsDto.cs

@ -1,15 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Areas.Api.Controllers.Schemas.Models
{
public sealed class PreviewUrlsDto : Dictionary<string, string>
{
}
}

38
src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs

@ -19,6 +19,8 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
{ {
public sealed class SchemaDetailsDto public sealed class SchemaDetailsDto
{ {
private static readonly Dictionary<string, string> EmptyPreviewUrls = new Dictionary<string, string>();
/// <summary> /// <summary>
/// The id of the schema. /// The id of the schema.
/// </summary> /// </summary>
@ -47,34 +49,14 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
public bool IsPublished { get; set; } public bool IsPublished { get; set; }
/// <summary> /// <summary>
/// The script that is executed for each query when querying contents. /// The scripts.
/// </summary>
public string ScriptQuery { get; set; }
/// <summary>
/// The script that is executed when creating a content.
/// </summary>
public string ScriptCreate { get; set; }
/// <summary>
/// The script that is executed when updating a content.
/// </summary>
public string ScriptUpdate { get; set; }
/// <summary>
/// The script that is executed when deleting a content.
/// </summary>
public string ScriptDelete { get; set; }
/// <summary>
/// The script that is executed when changing a content status.
/// </summary> /// </summary>
public string ScriptChange { get; set; } public SchemaScriptsDto Scripts { get; set; } = new SchemaScriptsDto();
/// <summary> /// <summary>
/// The preview Urls. /// The preview Urls.
/// </summary> /// </summary>
public Dictionary<string, string> PreviewUrls { get; set; } public Dictionary<string, string> PreviewUrls { get; set; } = EmptyPreviewUrls;
/// <summary> /// <summary>
/// The list of fields. /// The list of fields.
@ -86,7 +68,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// The schema properties. /// The schema properties.
/// </summary> /// </summary>
[Required] [Required]
public SchemaPropertiesDto Properties { get; set; } public SchemaPropertiesDto Properties { get; set; } = new SchemaPropertiesDto();
/// <summary> /// <summary>
/// The user that has created the schema. /// The user that has created the schema.
@ -117,12 +99,18 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
public static SchemaDetailsDto FromSchema(ISchemaEntity schema) public static SchemaDetailsDto FromSchema(ISchemaEntity schema)
{ {
var response = new SchemaDetailsDto { Properties = new SchemaPropertiesDto() }; var response = new SchemaDetailsDto();
SimpleMapper.Map(schema, response); SimpleMapper.Map(schema, response);
SimpleMapper.Map(schema.SchemaDef, response); SimpleMapper.Map(schema.SchemaDef, response);
SimpleMapper.Map(schema.SchemaDef.Scripts, response.Scripts);
SimpleMapper.Map(schema.SchemaDef.Properties, response.Properties); SimpleMapper.Map(schema.SchemaDef.Properties, response.Properties);
if (schema.SchemaDef.PreviewUrls.Count > 0)
{
response.PreviewUrls = new Dictionary<string, string>(schema.SchemaDef.PreviewUrls);
}
response.Fields = new List<FieldDto>(); response.Fields = new List<FieldDto>();
foreach (var field in schema.SchemaDef.Fields) foreach (var field in schema.SchemaDef.Fields)

48
src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs

@ -0,0 +1,48 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Schemas.Models
{
public sealed class SchemaScriptsDto
{
/// <summary>
/// The script that is executed for each query when querying contents.
/// </summary>
public string Query { get; set; }
/// <summary>
/// The script that is executed when creating a content.
/// </summary>
public string Create { get; set; }
/// <summary>
/// The script that is executed when updating a content.
/// </summary>
public string Update { get; set; }
/// <summary>
/// The script that is executed when deleting a content.
/// </summary>
public string Delete { get; set; }
/// <summary>
/// The script that is executed when change a content status.
/// </summary>
public string Change { get; set; }
public ConfigureScripts ToCommand()
{
var scripts = SimpleMapper.Map(this, new SchemaScripts());
return new ConfigureScripts { Scripts = scripts };
}
}
}

2
src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs

@ -42,7 +42,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// <summary> /// <summary>
/// Set it to true to autopublish the schema. /// Set it to true to autopublish the schema.
/// </summary> /// </summary>
public bool Publish { get; set; } public bool IsPublished { get; set; }
public static TCommand ToCommand<TCommand, TDto>(TDto dto, TCommand command) where TCommand : UpsertCommand where TDto : UpsertDto public static TCommand ToCommand<TCommand, TDto>(TDto dto, TCommand command) where TCommand : UpsertCommand where TDto : UpsertDto
{ {

2
src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs

@ -146,7 +146,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// </returns> /// </returns>
[HttpPut] [HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/")] [Route("apps/{app}/schemas/{name}/fields/{id:long}/")]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)] [ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)] [ApiCosts(1)]
@ -172,7 +171,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// </returns> /// </returns>
[HttpPut] [HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/")] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/")]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppSchemasUpdate)] [ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)] [ApiCosts(1)]

16
src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs

@ -132,7 +132,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <param name="name">The name of the schema.</param> /// <param name="name">The name of the schema.</param>
/// <param name="request">The schema object that needs to updated.</param> /// <param name="request">The schema object that needs to updated.</param>
/// <returns> /// <returns>
/// 204 => Schema has been updated. /// 204 => Schema updated.
/// 400 => Schema properties are not valid. /// 400 => Schema properties are not valid.
/// 404 => Schema or app not found. /// 404 => Schema or app not found.
/// </returns> /// </returns>
@ -154,7 +154,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <param name="name">The name of the schema.</param> /// <param name="name">The name of the schema.</param>
/// <param name="request">The schema object that needs to updated.</param> /// <param name="request">The schema object that needs to updated.</param>
/// <returns> /// <returns>
/// 204 => Schema has been updated. /// 204 => Schema updated.
/// 400 => Schema properties are not valid. /// 400 => Schema properties are not valid.
/// 404 => Schema or app not found. /// 404 => Schema or app not found.
/// </returns> /// </returns>
@ -176,7 +176,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <param name="name">The name of the schema.</param> /// <param name="name">The name of the schema.</param>
/// <param name="request">The schema object that needs to updated.</param> /// <param name="request">The schema object that needs to updated.</param>
/// <returns> /// <returns>
/// 204 => Schema has been updated. /// 204 => Schema updated.
/// 404 => Schema or app not found. /// 404 => Schema or app not found.
/// </returns> /// </returns>
[HttpPut] [HttpPut]
@ -197,16 +197,16 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <param name="name">The name of the schema.</param> /// <param name="name">The name of the schema.</param>
/// <param name="request">The preview urls for the schema.</param> /// <param name="request">The preview urls for the schema.</param>
/// <returns> /// <returns>
/// 204 => Schema has been updated. /// 204 => Schema updated.
/// 404 => Schema or app not found. /// 404 => Schema or app not found.
/// </returns> /// </returns>
[HttpPut] [HttpPut]
[Route("apps/{app}/schemas/{name}/preview-urls")] [Route("apps/{app}/schemas/{name}/preview-urls")]
[ApiPermission(Permissions.AppSchemasUpdate)] [ApiPermission(Permissions.AppSchemasUpdate)]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PutPreviewUrls(string app, string name, [FromBody] PreviewUrlsDto request) public async Task<IActionResult> PutPreviewUrls(string app, string name, [FromBody] ConfigurePreviewUrlsDto request)
{ {
await CommandBus.PublishAsync(new ConfigurePreviewUrls { PreviewUrls = request ?? new PreviewUrlsDto() }); await CommandBus.PublishAsync(request.ToCommand());
return NoContent(); return NoContent();
} }
@ -218,7 +218,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <param name="name">The name of the schema.</param> /// <param name="name">The name of the schema.</param>
/// <param name="request">The schema scripts object that needs to updated.</param> /// <param name="request">The schema scripts object that needs to updated.</param>
/// <returns> /// <returns>
/// 204 => Schema has been updated. /// 204 => Schema updated.
/// 400 => Schema properties are not valid. /// 400 => Schema properties are not valid.
/// 404 => Schema or app not found. /// 404 => Schema or app not found.
/// </returns> /// </returns>
@ -226,7 +226,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[Route("apps/{app}/schemas/{name}/scripts/")] [Route("apps/{app}/schemas/{name}/scripts/")]
[ApiPermission(Permissions.AppSchemasScripts)] [ApiPermission(Permissions.AppSchemasScripts)]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PutSchemaScripts(string app, string name, [FromBody] ConfigureScriptsDto request) public async Task<IActionResult> PutSchemaScripts(string app, string name, [FromBody] SchemaScriptsDto request)
{ {
await CommandBus.PublishAsync(request.ToCommand()); await CommandBus.PublishAsync(request.ToCommand());

3
src/Squidex/Config/Domain/EntitiesServices.cs

@ -251,6 +251,9 @@ namespace Squidex.Config.Domain
services.AddTransientAs<ConvertEventStoreAppId>() services.AddTransientAs<ConvertEventStoreAppId>()
.As<IMigration>(); .As<IMigration>();
services.AddTransientAs<ClearSchemas>()
.As<IMigration>();
services.AddTransientAs<PopulateGrainIndexes>() services.AddTransientAs<PopulateGrainIndexes>()
.As<IMigration>(); .As<IMigration>();

2
src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html

@ -7,7 +7,7 @@
<ng-container tabs> <ng-container tabs>
<ul class="nav nav-tabs2"> <ul class="nav nav-tabs2">
<li class="nav-item" *ngFor="let script of editForm.form.controls | sqxKeys"> <li class="nav-item" *ngFor="let script of editForm.form.controls | sqxKeys">
<a class="nav-link" [class.active]="selectedField === script" (click)="selectField(script)">{{script.substr(6)}}</a> <a class="nav-link" [class.active]="selectedField === script" (click)="selectField(script)">{{script | titlecase}}</a>
</li> </li>
</ul> </ul>
</ng-container> </ng-container>

4
src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts

@ -26,7 +26,7 @@ export class SchemaScriptsFormComponent implements OnInit {
@Input() @Input()
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
public selectedField = 'scriptQuery'; public selectedField = 'query';
public editForm = new EditScriptsForm(this.formBuilder); public editForm = new EditScriptsForm(this.formBuilder);
@ -37,7 +37,7 @@ export class SchemaScriptsFormComponent implements OnInit {
} }
public ngOnInit() { public ngOnInit() {
this.editForm.load(this.schema); this.editForm.load(this.schema.scripts);
} }
public complete() { public complete() {

8
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -34,11 +34,11 @@
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-6 type"> <div class="col-6 type">
<label> <label>
<input type="radio" class="radio-input" formControlName="singleton" [value]="false" /> <input type="radio" class="radio-input" formControlName="isSingleton" [value]="false" />
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-auto"> <div class="col-auto">
<div class="type-icon" [class.active]="createForm.form.controls['singleton'].value !== true"> <div class="type-icon" [class.active]="createForm.form.controls['isSingleton'].value !== true">
<i class="icon-multiple-content"></i> <i class="icon-multiple-content"></i>
</div> </div>
</div> </div>
@ -52,11 +52,11 @@
</div> </div>
<div class="col-6 type"> <div class="col-6 type">
<label> <label>
<input type="radio" class="radio-input" formControlName="singleton" [value]="true" /> <input type="radio" class="radio-input" formControlName="isSingleton" [value]="true" />
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-auto"> <div class="col-auto">
<div class="type-icon" [class.active]="createForm.form.controls['singleton'].value === true"> <div class="type-icon" [class.active]="createForm.form.controls['isSingleton'].value === true">
<i class="icon-single-content"></i> <i class="icon-single-content"></i>
</div> </div>
</div> </div>

2
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts

@ -67,7 +67,7 @@ export class SchemaFormComponent implements OnInit {
const value = this.createForm.submit(); const value = this.createForm.submit();
if (value) { if (value) {
const schemaDto = Object.assign(value.import || {}, { name: value.name, singleton: value.singleton }); const schemaDto = Object.assign(value.import || {}, { name: value.name, isSingleton: value.isSingleton });
this.schemasState.create(schemaDto) this.schemasState.create(schemaDto)
.subscribe(dto => { .subscribe(dto => {

30
src/Squidex/app/shared/services/schemas.service.spec.ts

@ -284,11 +284,13 @@ describe('SchemasService', () => {
} }
} }
], ],
scriptQuery: '<script-query>', scripts: {
scriptCreate: '<script-create>', query: '<script-query>',
scriptUpdate: '<script-update>', create: '<script-create>',
scriptDelete: '<script-delete>', change: '<script-change>',
scriptChange: '<script-change>' delete: '<script-delete>',
update: '<script-update>'
}
}, { }, {
headers: { headers: {
etag: '2' etag: '2'
@ -316,13 +318,15 @@ describe('SchemasService', () => {
new RootFieldDto(20, 'field20', createProperties('Tags'), 'language', true, true, true) new RootFieldDto(20, 'field20', createProperties('Tags'), 'language', true, true, true)
], ],
{ {
'Default': 'url' query: '<script-query>',
create: '<script-create>',
change: '<script-change>',
delete: '<script-delete>',
update: '<script-update>'
}, },
'<script-query>', {
'<script-create>', 'Default': 'url'
'<script-update>', }));
'<script-delete>',
'<script-change>'));
})); }));
it('should make post request to create schema', it('should make post request to create schema',
@ -349,7 +353,7 @@ describe('SchemasService', () => {
} }
}); });
expect(schema!).toEqual(new SchemaDetailsDto('1', dto.name, '', new SchemaPropertiesDto(), true, false, now, user, now, user, new Version('2'), [], {})); expect(schema!).toEqual(new SchemaDetailsDto('1', dto.name, '', new SchemaPropertiesDto(), true, false, now, user, now, user, new Version('2'), [], {}, {}));
})); }));
it('should make put request to update schema', it('should make put request to update schema',
@ -370,7 +374,7 @@ describe('SchemasService', () => {
it('should make put request to update schema scripts', it('should make put request to update schema scripts',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateSchemaScriptsDto(); const dto = new UpdateSchemaScriptsDto({});
schemasService.putScripts('my-app', 'my-schema', dto, version).subscribe(); schemasService.putScripts('my-app', 'my-schema', dto, version).subscribe();

41
src/Squidex/app/shared/services/schemas.service.ts

@ -63,13 +63,9 @@ export class SchemaDetailsDto extends SchemaDto {
lastModified: DateTime, lastModified: DateTime,
lastModifiedBy: string, lastModifiedBy: string,
version: Version, version: Version,
public readonly fields: RootFieldDto[], public readonly fields: RootFieldDto[] = [],
public readonly previewUrls: { [name: string]: string }, public readonly scripts = {},
public readonly scriptQuery?: string, public readonly previewUrls = {}
public readonly scriptCreate?: string,
public readonly scriptUpdate?: string,
public readonly scriptDelete?: string,
public readonly scriptChange?: string
) { ) {
super(id, name, category, properties, isSingleton, isPublished, created, createdBy, lastModified, lastModifiedBy, version); super(id, name, category, properties, isSingleton, isPublished, created, createdBy, lastModified, lastModifiedBy, version);
@ -215,17 +211,6 @@ export class UpdateSchemaDto {
} }
} }
export class UpdateSchemaScriptsDto {
constructor(
public readonly scriptQuery?: string,
public readonly scriptCreate?: string,
public readonly scriptUpdate?: string,
public readonly scriptDelete?: string,
public readonly scriptChange?: string
) {
}
}
@Injectable() @Injectable()
export class SchemasService { export class SchemasService {
constructor( constructor(
@ -318,12 +303,8 @@ export class SchemasService {
DateTime.parseISO_UTC(body.lastModified), body.lastModifiedBy, DateTime.parseISO_UTC(body.lastModified), body.lastModifiedBy,
response.version, response.version,
fields, fields,
body.previewUrls || {}, body.scripts || {},
body.scriptQuery, body.previewUrls || {});
body.scriptCreate,
body.scriptUpdate,
body.scriptDelete,
body.scriptChange);
}), }),
pretifyError('Failed to load schema. Please reload.')); pretifyError('Failed to load schema. Please reload.'));
} }
@ -347,13 +328,7 @@ export class SchemasService {
now, user, now, user,
now, user, now, user,
response.version, response.version,
dto.fields || [], dto.fields || []);
{},
body.scriptQuery,
body.scriptCreate,
body.scriptUpdate,
body.scriptDelete,
body.scriptChange);
}), }),
tap(() => { tap(() => {
this.analytics.trackEvent('Schema', 'Created', appName); this.analytics.trackEvent('Schema', 'Created', appName);
@ -371,7 +346,7 @@ export class SchemasService {
pretifyError('Failed to delete schema. Please reload.')); pretifyError('Failed to delete schema. Please reload.'));
} }
public putScripts(appName: string, schemaName: string, dto: UpdateSchemaScriptsDto, version: Version): Observable<Versioned<any>> { public putScripts(appName: string, schemaName: string, dto: {}, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/scripts`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/scripts`);
return HTTP.putVersioned(this.http, url, dto, version).pipe( return HTTP.putVersioned(this.http, url, dto, version).pipe(
@ -421,7 +396,7 @@ export class SchemasService {
pretifyError('Failed to change category. Please reload.')); pretifyError('Failed to change category. Please reload.'));
} }
public putPreviewUrls(appName: string, schemaName: string, dto: { [name: string]: string }, version: Version): Observable<Versioned<any>> { public putPreviewUrls(appName: string, schemaName: string, dto: {}, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/preview-urls`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/preview-urls`);
return HTTP.putVersioned(this.http, url, dto, version).pipe( return HTTP.putVersioned(this.http, url, dto, version).pipe(

2
src/Squidex/app/shared/state/contents.forms.spec.ts

@ -371,7 +371,7 @@ describe('StringField', () => {
}); });
function createSchema(properties: SchemaPropertiesDto, index = 1, fields: RootFieldDto[]) { function createSchema(properties: SchemaPropertiesDto, index = 1, fields: RootFieldDto[]) {
return new SchemaDetailsDto('id' + index, 'schema' + index, '', properties, false, true, null!, null!, null!, null!, null!, fields, {}); return new SchemaDetailsDto('id' + index, 'schema' + index, '', properties, false, true, null!, null!, null!, null!, null!, fields);
} }
function createField(properties: FieldPropertiesDto, index = 1, partitioning = 'languages') { function createField(properties: FieldPropertiesDto, index = 1, partitioning = 'languages') {

12
src/Squidex/app/shared/state/schemas.forms.ts

@ -40,7 +40,7 @@ export class CreateSchemaForm extends Form<FormGroup> {
ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'Name can contain lower case letters (a-z), numbers and dashes only (not at the end).') ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'Name can contain lower case letters (a-z), numbers and dashes only (not at the end).')
] ]
], ],
singleton: false, isSingleton: false,
import: {} import: {}
})); }));
} }
@ -136,11 +136,11 @@ export class ConfigurePreviewUrlsForm extends Form<FormArray> {
export class EditScriptsForm extends Form<FormGroup> { export class EditScriptsForm extends Form<FormGroup> {
constructor(formBuilder: FormBuilder) { constructor(formBuilder: FormBuilder) {
super(formBuilder.group({ super(formBuilder.group({
scriptQuery: '', query: '',
scriptCreate: '', create: '',
scriptUpdate: '', change: '',
scriptDelete: '', delete: '',
scriptChange: '' update: ''
})); }));
} }
} }

32
src/Squidex/app/shared/state/schemas.state.spec.ts

@ -26,7 +26,6 @@ import {
UpdateFieldDto, UpdateFieldDto,
UpdateSchemaCategoryDto, UpdateSchemaCategoryDto,
UpdateSchemaDto, UpdateSchemaDto,
UpdateSchemaScriptsDto,
Version, Version,
Versioned Versioned
} from '@app/shared'; } from '@app/shared';
@ -56,8 +55,7 @@ describe('SchemasState', () => {
creation, creator, creation, creator,
creation, creator, creation, creator,
version, version,
[field1, field2], [field1, field2]);
{});
let dialogs: IMock<DialogService>; let dialogs: IMock<DialogService>;
let appsState: IMock<AppsState>; let appsState: IMock<AppsState>;
@ -264,44 +262,38 @@ describe('SchemasState', () => {
expectToBeModified(schema_1); expectToBeModified(schema_1);
}); });
it('should update script properties and update user info when preview urls configured', () => { it('should update script properties and update user info when scripts configured', () => {
const request = { const request = { query: '<query-script>' };
'Default': 'url'
};
schemasService.setup(x => x.putPreviewUrls(app, schema.name, It.isAny(), version)) schemasService.setup(x => x.putScripts(app, schema.name, It.isAny(), version))
.returns(() => of(new Versioned<any>(newVersion, {}))); .returns(() => of(new Versioned<any>(newVersion, {})));
schemasState.configurePreviewUrls(schema, request, modified).subscribe(); schemasState.configureScripts(schema, request, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1); const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.previewUrls).toEqual(request); expect(schema_1.scripts['query']).toEqual('<query-script>');
expectToBeModified(schema_1); expectToBeModified(schema_1);
}); });
it('should update script properties and update user info when scripts configured', () => { it('should update script properties and update user info when preview urls configured', () => {
const request = new UpdateSchemaScriptsDto('query', 'create', 'update', 'delete', 'change'); const request = { web: 'url' };
schemasService.setup(x => x.putScripts(app, schema.name, It.isAny(), version)) schemasService.setup(x => x.putPreviewUrls(app, schema.name, It.isAny(), version))
.returns(() => of(new Versioned<any>(newVersion, {}))); .returns(() => of(new Versioned<any>(newVersion, {})));
schemasState.configureScripts(schema, request, modified).subscribe(); schemasState.configurePreviewUrls(schema, request, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1); const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.scriptQuery).toEqual('query'); expect(schema_1.previewUrls).toEqual(request);
expect(schema_1.scriptCreate).toEqual('create');
expect(schema_1.scriptUpdate).toEqual('update');
expect(schema_1.scriptDelete).toEqual('delete');
expect(schema_1.scriptChange).toEqual('change');
expectToBeModified(schema_1); expectToBeModified(schema_1);
}); });
it('should add schema to snapshot when created', () => { it('should add schema to snapshot when created', () => {
const request = new CreateSchemaDto('newName'); const request = new CreateSchemaDto('newName');
const result = new SchemaDetailsDto('id4', 'newName', '', {}, false, false, modified, modifier, modified, modifier, version, [], {}); const result = new SchemaDetailsDto('id4', 'newName', '', {}, false, false, modified, modifier, modified, modifier, version);
schemasService.setup(x => x.postSchema(app, request, modifier, modified)) schemasService.setup(x => x.postSchema(app, request, modifier, modified))
.returns(() => of(result)); .returns(() => of(result));

13
src/Squidex/app/shared/state/schemas.state.ts

@ -34,8 +34,7 @@ import {
SchemasService, SchemasService,
UpdateFieldDto, UpdateFieldDto,
UpdateSchemaCategoryDto, UpdateSchemaCategoryDto,
UpdateSchemaDto, UpdateSchemaDto
UpdateSchemaScriptsDto
} from './../services/schemas.service'; } from './../services/schemas.service';
import { FieldPropertiesDto } from './../services/schemas.types'; import { FieldPropertiesDto } from './../services/schemas.types';
@ -195,7 +194,7 @@ export class SchemasState extends State<Snapshot> {
notify(this.dialogs)); notify(this.dialogs));
} }
public configurePreviewUrls(schema: SchemaDetailsDto, request: { [name: string]: string }, now?: DateTime): Observable<any> { public configurePreviewUrls(schema: SchemaDetailsDto, request: {}, now?: DateTime): Observable<any> {
return this.schemasService.putPreviewUrls(this.appName, schema.name, request, schema.version).pipe( return this.schemasService.putPreviewUrls(this.appName, schema.name, request, schema.version).pipe(
tap(dto => { tap(dto => {
this.replaceSchema(configurePreviewUrls(schema, request, this.user, dto.version, now)); this.replaceSchema(configurePreviewUrls(schema, request, this.user, dto.version, now));
@ -203,7 +202,7 @@ export class SchemasState extends State<Snapshot> {
notify(this.dialogs)); notify(this.dialogs));
} }
public configureScripts(schema: SchemaDetailsDto, request: UpdateSchemaScriptsDto, now?: DateTime): Observable<any> { public configureScripts(schema: SchemaDetailsDto, request: {}, now?: DateTime): Observable<any> {
return this.schemasService.putScripts(this.appName, schema.name, request, schema.version).pipe( return this.schemasService.putScripts(this.appName, schema.name, request, schema.version).pipe(
tap(dto => { tap(dto => {
this.replaceSchema(configureScripts(schema, request, this.user, dto.version, now)); this.replaceSchema(configureScripts(schema, request, this.user, dto.version, now));
@ -390,7 +389,7 @@ const changeCategory = <T extends SchemaDto>(schema: T, category: string, user:
version version
}); });
const configurePreviewUrls = (schema: SchemaDetailsDto, previewUrls: { [name: string]: string }, user: string, version: Version, now?: DateTime) => const configurePreviewUrls = (schema: SchemaDetailsDto, previewUrls: {}, user: string, version: Version, now?: DateTime) =>
schema.with({ schema.with({
previewUrls, previewUrls,
lastModified: now || DateTime.now(), lastModified: now || DateTime.now(),
@ -398,9 +397,9 @@ const configurePreviewUrls = (schema: SchemaDetailsDto, previewUrls: { [name: st
version version
}); });
const configureScripts = (schema: SchemaDetailsDto, scripts: UpdateSchemaScriptsDto, user: string, version: Version, now?: DateTime) => const configureScripts = (schema: SchemaDetailsDto, scripts: {}, user: string, version: Version, now?: DateTime) =>
schema.with({ schema.with({
...scripts, scripts,
lastModified: now || DateTime.now(), lastModified: now || DateTime.now(),
lastModifiedBy: user, lastModifiedBy: user,
version version

42
tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs

@ -275,6 +275,44 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas
Assert.Throws<ArgumentException>(() => schema_2.ReorderFields(new List<long> { 1, 4 })); Assert.Throws<ArgumentException>(() => schema_2.ReorderFields(new List<long> { 1, 4 }));
} }
[Fact]
public void Should_change_category()
{
var schema_1 = schema_0.ChangeCategory("Category");
Assert.Equal("Category", schema_1.Category);
}
[Fact]
public void Should_configure_scripts()
{
var scripts = new SchemaScripts
{
Query = "<query-script>"
};
var schema_1 = schema_0.ConfigureScripts(scripts);
Assert.Equal(scripts, schema_1.Scripts);
Assert.Equal("<query-script>", schema_1.Scripts.Query);
}
[Fact]
public void Should_configure_preview_urls()
{
var urls = new Dictionary<string, string>
{
["web"] = "Url"
};
var schema_1 = schema_0.ConfigurePreviewUrls(urls);
Assert.Equal(urls, schema_1.PreviewUrls);
Assert.Equal("Url", schema_1.PreviewUrls["web"]);
}
[Fact] [Fact]
public void Should_serialize_and_deserialize_schema() public void Should_serialize_and_deserialize_schema()
{ {
@ -285,9 +323,9 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas
{ {
["web"] = "Url" ["web"] = "Url"
}) })
.ConfigureScripts(new Dictionary<string, string> .ConfigureScripts(new SchemaScripts
{ {
["create"] = "<create-script>" Create = "<create-script>"
}); });
var schemaTarget = schemaSource.SerializeAndDeserialize(); var schemaTarget = schemaSource.SerializeAndDeserialize();

27
tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs

@ -59,9 +59,9 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
[Fact] [Fact]
public void Should_create_events_if_scripts_configured() public void Should_create_events_if_scripts_configured()
{ {
var scripts = new Dictionary<string, string> var scripts = new SchemaScripts
{ {
[Scripts.Create] = "<create-script>" Create = "<create-script>"
}; };
var sourceSchema = new Schema("source"); var sourceSchema = new Schema("source");
@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
{ {
var previewUrls = new Dictionary<string, string> var previewUrls = new Dictionary<string, string>
{ {
["Web"] = "Url" ["web"] = "Url"
}; };
var sourceSchema = new Schema("source"); var sourceSchema = new Schema("source");
@ -406,6 +406,27 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
); );
} }
[Fact]
public void Should_create_events_if_field_recreated()
{
var sourceSchema =
new Schema("source")
.AddString(stringId.Id, stringId.Name, Partitioning.Invariant);
var targetSchema =
new Schema("target")
.AddTags(stringId.Id, stringId.Name, Partitioning.Invariant);
var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator);
var createdId = NamedId.Of(50L, stringId.Name);
events.ShouldHaveSameEvents(
new FieldDeleted { FieldId = stringId },
new FieldAdded { FieldId = createdId, Name = stringId.Name, Partitioning = Partitioning.Invariant.Key, Properties = new TagsFieldProperties() }
);
}
[Fact] [Fact]
public void Should_create_events_if_nested_field_created() public void Should_create_events_if_nested_field_created()
{ {

11
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using NodaTime; using NodaTime;
@ -75,12 +74,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentGrainTests() public ContentGrainTests()
{ {
var scripts = new Dictionary<string, string> var scripts = new SchemaScripts
{ {
[Scripts.Change] = "<change-script>", Change = "<change-script>",
[Scripts.Create] = "<create-script>", Create = "<create-script>",
[Scripts.Delete] = "<delete-script>", Delete = "<delete-script>",
[Scripts.Update] = "<update-script>", Update = "<update-script>",
}; };
var schemaDef = var schemaDef =

4
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var schemaDef = var schemaDef =
new Schema(schemaId.Name) new Schema(schemaId.Name)
.ConfigureScripts(new Dictionary<string, string> { [Scripts.Query] = "<script-query>" }); .ConfigureScripts(new SchemaScripts { Query = "<query-script>" });
A.CallTo(() => schema.Id).Returns(schemaId.Id); A.CallTo(() => schema.Id).Returns(schemaId.Id);
A.CallTo(() => schema.AppId).Returns(appId); A.CallTo(() => schema.AppId).Returns(appId);
@ -397,7 +397,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
foreach (var id in ids) foreach (var id in ids)
{ {
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == id && x.Data == contentData), "<script-query>")) A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == id && x.Data == contentData), "<query-script>"))
.Returns(contentTransformed); .Returns(contentTransformed);
} }
} }

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
.AddArray(13, "my-array", Partitioning.Invariant, f => f .AddArray(13, "my-array", Partitioning.Invariant, f => f
.AddBoolean(121, "nested-boolean") .AddBoolean(121, "nested-boolean")
.AddNumber(122, "nested-number")) .AddNumber(122, "nested-number"))
.ConfigureScripts(new Dictionary<string, string> { [Scripts.Query] = "<script-query>" }) .ConfigureScripts(new SchemaScripts { Query = "<query-script>" })
.Publish(); .Publish();
A.CallTo(() => app.Id).Returns(appId); A.CallTo(() => app.Id).Returns(appId);

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/SingletonCommandMiddlewareTests.cs

@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public async Task Should_create_content_when_singleton_schema_is_created() public async Task Should_create_content_when_singleton_schema_is_created()
{ {
var context = var context =
new CommandContext(new CreateSchema { Singleton = true, Name = "my-schema" }, commandBus) new CommandContext(new CreateSchema { IsSingleton = true, Name = "my-schema" }, commandBus)
.Complete(); .Complete();
await sut.HandleAsync(context); await sut.HandleAsync(context);
@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public async Task Should_not_create_content_when_non_singleton_schema_is_created() public async Task Should_not_create_content_when_non_singleton_schema_is_created()
{ {
var context = var context =
new CommandContext(new CreateSchema { Singleton = false }, commandBus) new CommandContext(new CreateSchema { IsSingleton = false }, commandBus)
.Complete(); .Complete();
await sut.HandleAsync(context); await sut.HandleAsync(context);
@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public async Task Should_not_create_content_when_singleton_schema_not_created() public async Task Should_not_create_content_when_singleton_schema_not_created()
{ {
var context = var context =
new CommandContext(new CreateSchema { Singleton = true }, commandBus); new CommandContext(new CreateSchema { IsSingleton = true }, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);

12
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs

@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
var properties = new SchemaProperties(); var properties = new SchemaProperties();
var command = new CreateSchema { Name = SchemaName, SchemaId = SchemaId, Properties = properties, Singleton = true }; var command = new CreateSchema { Name = SchemaName, SchemaId = SchemaId, Properties = properties, IsSingleton = true };
var result = await sut.ExecuteAsync(CreateCommand(command)); var result = await sut.ExecuteAsync(CreateCommand(command));
@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new SchemaCreated { Schema = new Schema(command.Name, command.Properties, command.Singleton) }) CreateEvent(new SchemaCreated { Schema = new Schema(command.Name, command.Properties, command.IsSingleton) })
); );
} }
@ -140,9 +140,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
var command = new ConfigureScripts var command = new ConfigureScripts
{ {
Scripts = new Dictionary<string, string> Scripts = new SchemaScripts
{ {
[Scripts.Query] = "<script-query>" Query = "<query-script>"
} }
}; };
@ -642,9 +642,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
var command = new SynchronizeSchema var command = new SynchronizeSchema
{ {
Scripts = new Dictionary<string, string> Scripts = new SchemaScripts
{ {
[Scripts.Query] = "<script-query>" Query = "<query-script"
}, },
PreviewUrls = new Dictionary<string, string> PreviewUrls = new Dictionary<string, string>
{ {

8
tools/Migrate_01/MigrationPath.cs

@ -16,7 +16,7 @@ namespace Migrate_01
{ {
public sealed class MigrationPath : IMigrationPath public sealed class MigrationPath : IMigrationPath
{ {
private const int CurrentVersion = 13; private const int CurrentVersion = 14;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public MigrationPath(IServiceProvider serviceProvider) public MigrationPath(IServiceProvider serviceProvider)
@ -84,6 +84,12 @@ namespace Migrate_01
yield return serviceProvider.GetRequiredService<ConvertRuleEventsJson>(); yield return serviceProvider.GetRequiredService<ConvertRuleEventsJson>();
} }
// Version 14: Schema refactoring
if (version < 14)
{
yield return serviceProvider.GetRequiredService<ClearSchemas>();
}
// Version 01: Introduce app patterns. // Version 01: Introduce app patterns.
if (version < 1) if (version < 1)
{ {

30
tools/Migrate_01/Migrations/ClearSchemas.cs

@ -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.Migrations;
using Squidex.Infrastructure.States;
namespace Migrate_01.Migrations
{
public sealed class ClearSchemas : IMigration
{
private readonly IStore<Guid> store;
public ClearSchemas(IStore<Guid> store)
{
this.store = store;
}
public Task UpdateAsync()
{
return store.ClearSnapshotsAsync<Guid, SchemaState>();
}
}
}

12
tools/Migrate_01/OldEvents/ScriptsConfigured.cs

@ -32,31 +32,31 @@ namespace Migrate_01.OldEvents
public IEvent Migrate() public IEvent Migrate()
{ {
var scripts = new Dictionary<string, string>(); var scripts = new SchemaScripts();
if (!string.IsNullOrWhiteSpace(ScriptQuery)) if (!string.IsNullOrWhiteSpace(ScriptQuery))
{ {
scripts[Scripts.Query] = ScriptQuery; scripts.Query = ScriptQuery;
} }
if (!string.IsNullOrWhiteSpace(ScriptCreate)) if (!string.IsNullOrWhiteSpace(ScriptCreate))
{ {
scripts[Scripts.Create] = ScriptCreate; scripts.Create = ScriptCreate;
} }
if (!string.IsNullOrWhiteSpace(ScriptUpdate)) if (!string.IsNullOrWhiteSpace(ScriptUpdate))
{ {
scripts[Scripts.Update] = ScriptUpdate; scripts.Update = ScriptUpdate;
} }
if (!string.IsNullOrWhiteSpace(ScriptDelete)) if (!string.IsNullOrWhiteSpace(ScriptDelete))
{ {
scripts[Scripts.Delete] = ScriptDelete; scripts.Delete = ScriptDelete;
} }
if (!string.IsNullOrWhiteSpace(ScriptChange)) if (!string.IsNullOrWhiteSpace(ScriptChange))
{ {
scripts[Scripts.Change] = ScriptChange; scripts.Change = ScriptChange;
} }
return SimpleMapper.Map(this, new SchemaScriptsConfigured { Scripts = scripts }); return SimpleMapper.Map(this, new SchemaScriptsConfigured { Scripts = scripts });

Loading…
Cancel
Save