Browse Source

Prevent duplicate references and a setting to enforce it.

pull/357/head
Sebastian 7 years ago
parent
commit
38fc575eac
  1. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  3. 10
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs
  4. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs
  5. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs
  6. 11
      src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html
  7. 3
      src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.ts
  8. 11
      src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html
  9. 3
      src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts
  10. 2
      src/Squidex/app/shared/services/schemas.types.ts
  11. 4
      src/Squidex/app/shared/state/contents.forms.spec.ts
  12. 8
      src/Squidex/app/shared/state/contents.forms.ts
  13. 59
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs
  14. 21
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs

2
src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs

@ -33,6 +33,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public int? AspectHeight { get; set; }
public bool AllowDuplicates { get; set; }
public ReadOnlyCollection<string> AllowedExtensions { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)

2
src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs

@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public int? MaxItems { get; set; }
public bool AllowDuplicates { get; set; }
public Guid SchemaId { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)

10
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs

@ -55,6 +55,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems);
}
if (!field.Properties.AllowDuplicates)
{
yield return new UniqueValuesValidator<Guid>();
}
yield return new AssetsValidator(field.Properties);
}
@ -125,6 +130,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems);
}
if (!field.Properties.AllowDuplicates)
{
yield return new UniqueValuesValidator<Guid>();
}
if (field.Properties.SchemaId != Guid.Empty)
{
yield return new ReferencesValidator(field.Properties.SchemaId);

5
src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs

@ -73,6 +73,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
/// </summary>
public string[] AllowedExtensions { get; set; }
/// <summary>
/// True, if duplicate values are allowed.
/// </summary>
public bool AllowDuplicates { get; set; }
public override FieldProperties ToProperties()
{
var result = SimpleMapper.Map(this, new AssetsFieldProperties());

5
src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs

@ -23,6 +23,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
/// </summary>
public int? MaxItems { get; set; }
/// <summary>
/// True, if duplicate values are allowed.
/// </summary>
public bool AllowDuplicates { get; set; }
/// <summary>
/// The id of the referenced schema.
/// </summary>

11
src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html

@ -94,6 +94,17 @@
<label class="col-form-label">px</label>
</div>
</div>
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldAllowDuplicates" formControlName="allowDuplicates" />
<label class="form-check-label" for="{{field.fieldId}}_fieldAllowDuplicates">
Allow duplicate values
</label>
</div>
</div>
</div>
<div class="form-group2 row">
<label class="col-3 col-form-label">

3
src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.ts

@ -61,5 +61,8 @@ export class AssetsValidationComponent implements OnInit {
this.editForm.setControl('aspectHeight',
new FormControl(this.properties.aspectHeight));
this.editForm.setControl('allowDuplicates',
new FormControl(this.properties.allowDuplicates));
}
}

11
src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html

@ -21,6 +21,17 @@
</div>
</div>
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldAllowDuplicates" formControlName="allowDuplicates" />
<label class="form-check-label" for="{{field.fieldId}}_fieldAllowDuplicates">
Allow duplicate values
</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Items</label>

3
src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts

@ -31,6 +31,9 @@ export class ReferencesValidationComponent implements OnInit {
}
public ngOnInit() {
this.editForm.setControl('allowDuplicates',
new FormControl(this.properties.allowDuplicates));
this.editForm.setControl('maxItems',
new FormControl(this.properties.maxItems));

2
src/Squidex/app/shared/services/schemas.types.ts

@ -169,6 +169,7 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
public readonly maxHeight?: number;
public readonly aspectWidth?: number;
public readonly aspectHeight?: number;
public readonly allowDuplicates?: boolean;
public get isSortable() {
return false;
@ -296,6 +297,7 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
public readonly minItems?: number;
public readonly maxItems?: number;
public readonly schemaId?: string;
public readonly allowDuplicates?: boolean;
public get isSortable() {
return false;

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

@ -145,7 +145,7 @@ describe('AssetsField', () => {
const field = createField(new AssetsFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 }));
it('should create validators', () => {
expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(2);
expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3);
});
it('should format to empty string if null', () => {
@ -328,7 +328,7 @@ describe('ReferencesField', () => {
const field = createField(new ReferencesFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 }));
it('should create validators', () => {
expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(2);
expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3);
});
it('should format to empty string if null', () => {

8
src/Squidex/app/shared/state/contents.forms.ts

@ -197,6 +197,10 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor<ValidatorF
ValidatorsEx.betweenLength(properties.minItems, properties.maxItems)
];
if (!properties.allowDuplicates) {
validators.push(ValidatorsEx.uniqueStrings());
}
return validators;
}
@ -205,6 +209,10 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor<ValidatorF
ValidatorsEx.betweenLength(properties.minItems, properties.maxItems)
];
if (!properties.allowDuplicates) {
validators.push(ValidatorsEx.uniqueStrings());
}
return validators;
}

59
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs

@ -49,7 +49,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
PixelHeight = null
};
private readonly AssetInfo image = new AssetInfo
private readonly AssetInfo image1 = new AssetInfo
{
AssetId = Guid.NewGuid(),
FileName = "MyImage.png",
FileSize = 1024 * 8,
IsImage = true,
PixelWidth = 800,
PixelHeight = 600
};
private readonly AssetInfo image2 = new AssetInfo
{
AssetId = Guid.NewGuid(),
FileName = "MyImage.png",
@ -63,7 +73,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
public AssetsFieldTests()
{
ctx = ValidationTestExtensions.Assets(image, document);
ctx = ValidationTestExtensions.Assets(image1, image2, document);
}
[Fact]
@ -99,7 +109,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MinItems = 2, MaxItems = 2 });
await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(image1.AssetId, image2.AssetId), errors, ctx);
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_duplicate_values_are_ignored()
{
var sut = Field(new AssetsFieldProperties { AllowDuplicates = true });
await sut.ValidateAsync(CreateValue(image1.AssetId, image1.AssetId), errors, ctx);
Assert.Empty(errors);
}
@ -142,7 +162,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MinItems = 3 });
await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(image1.AssetId, image2.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." });
@ -153,7 +173,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MaxItems = 1 });
await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(image1.AssetId, image2.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." });
@ -177,7 +197,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MinSize = 5 * 1024 });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[1]: \'4 kB\' less than minimum of \'5 kB\'." });
@ -188,7 +208,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MaxSize = 5 * 1024 });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[2]: \'8 kB\' greater than maximum of \'5 kB\'." });
@ -199,18 +219,29 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MustBeImage = true });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[1]: Not an image." });
}
[Fact]
public async Task Should_add_error_if_values_contains_duplicate()
{
var sut = Field(new AssetsFieldProperties { MustBeImage = true });
await sut.ValidateAsync(CreateValue(image1.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." });
}
[Fact]
public async Task Should_add_error_if_image_width_is_too_small()
{
var sut = Field(new AssetsFieldProperties { MinWidth = 1000 });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[2]: Width \'800px\' less than minimum of \'1000px\'." });
@ -221,7 +252,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MaxWidth = 700 });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[2]: Width \'800px\' greater than maximum of \'700px\'." });
@ -232,7 +263,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MinHeight = 800 });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[2]: Height \'600px\' less than minimum of \'800px\'." });
@ -243,7 +274,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { MaxHeight = 500 });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[2]: Height \'600px\' greater than maximum of \'500px\'." });
@ -254,7 +285,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { AspectWidth = 1, AspectHeight = 1 });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[] { "[2]: Aspect ratio not '1:1'." });
@ -265,7 +296,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
{
var sut = Field(new AssetsFieldProperties { AllowedExtensions = ReadOnlyCollection.Create("mp4") });
await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx);
await sut.ValidateAsync(CreateValue(document.AssetId, image1.AssetId), errors, ctx);
errors.Should().BeEquivalentTo(
new[]

21
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs

@ -61,6 +61,16 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
Assert.Empty(errors);
}
[Fact]
public async Task Should_not_add_error_if_duplicate_values_are_allowed()
{
var sut = Field(new ReferencesFieldProperties { MinItems = 2, MaxItems = 2, AllowDuplicates = true });
await sut.ValidateAsync(CreateValue(ref1, ref1), errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_add_error_if_references_are_required_and_null()
{
@ -127,6 +137,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
new[] { $"Contains invalid reference '{ref1}'." });
}
[Fact]
public async Task Should_add_error_if_reference_contains_duplicate_values()
{
var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId });
await sut.ValidateAsync(CreateValue(ref1, ref1), errors, ValidationTestExtensions.References(ref1));
errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." });
}
private static IJsonValue CreateValue(params Guid[] ids)
{
return ids == null ? JsonValue.Null : JsonValue.Array(ids.Select(x => (object)x.ToString()).ToArray());

Loading…
Cancel
Save