From de1f4c563f9366b8dc03477e6613d00ea5ecd10d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 2 May 2019 10:01:09 +0200 Subject: [PATCH 01/12] Unique validator. --- .../angular/forms/error-formatting.spec.ts | 6 ++++ .../angular/forms/error-formatting.ts | 1 + .../angular/forms/validators.spec.ts | 34 +++++++++++++++++++ .../app/framework/angular/forms/validators.ts | 23 ++++++++++++- 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/Squidex/app/framework/angular/forms/error-formatting.spec.ts b/src/Squidex/app/framework/angular/forms/error-formatting.spec.ts index f6ac790c3..88b4206ad 100644 --- a/src/Squidex/app/framework/angular/forms/error-formatting.spec.ts +++ b/src/Squidex/app/framework/angular/forms/error-formatting.spec.ts @@ -132,6 +132,12 @@ describe('formatErrors', () => { expect(error).toEqual('MY_FIELD contains an invalid value: 4.'); }); + it('should format uniqueStrings', () => { + const error = validate(['1', '2', '2', '3'], ValidatorsEx.uniqueStrings()); + + expect(error).toEqual('MY_FIELD must not contain duplicate values.'); + }); + it('should format match', () => { const formControl1 = new FormControl(1); const formControl2 = new FormControl(2); diff --git a/src/Squidex/app/framework/angular/forms/error-formatting.ts b/src/Squidex/app/framework/angular/forms/error-formatting.ts index a54d23941..1b479eb8e 100644 --- a/src/Squidex/app/framework/angular/forms/error-formatting.ts +++ b/src/Squidex/app/framework/angular/forms/error-formatting.ts @@ -26,6 +26,7 @@ const DEFAULT_ERRORS: { [key: string]: string } = { patternmessage: '{message}', required: '{field} is required.', requiredTrue: '{field} is required.', + uniquestrings: '{field} must not contain duplicate values.', validdatetime: '{field} is not a valid date time.', validvalues: '{field} is not a valid value.', validarrayvalues: '{field} contains an invalid value: {invalidvalue}.' diff --git a/src/Squidex/app/framework/angular/forms/validators.spec.ts b/src/Squidex/app/framework/angular/forms/validators.spec.ts index 1e0911720..9cd351171 100644 --- a/src/Squidex/app/framework/angular/forms/validators.spec.ts +++ b/src/Squidex/app/framework/angular/forms/validators.spec.ts @@ -371,4 +371,38 @@ describe('ValidatorsEx.pattern', () => { expect(error).toEqual(expected); }); +}); + +describe('ValidatorsEx.uniqueStrings', () => { + it('should return null when value is null', () => { + const input = new FormControl(null); + + const error = ValidatorsEx.uniqueStrings()(input); + + expect(error).toBeNull(); + }); + + it('should return null when value is not a string array', () => { + const input = new FormControl([1, 2, 3]); + + const error = ValidatorsEx.uniqueStrings()(input); + + expect(error).toBeNull(); + }); + + it('should return null when values are unique', () => { + const input = new FormControl(['1', '2', '3']); + + const error = ValidatorsEx.uniqueStrings()(input); + + expect(error).toBeNull(); + }); + + it('should return error when values are not unique', () => { + const input = new FormControl(['1', '2', '2', '3']); + + const error = ValidatorsEx.uniqueStrings()(input); + + expect(error).toEqual({ uniquestrings: false }); + }); }); \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/validators.ts b/src/Squidex/app/framework/angular/forms/validators.ts index a0c61f551..3a08ed7c4 100644 --- a/src/Squidex/app/framework/angular/forms/validators.ts +++ b/src/Squidex/app/framework/angular/forms/validators.ts @@ -7,7 +7,7 @@ import { AbstractControl, ValidatorFn, Validators } from '@angular/forms'; -import { DateTime } from '@app/framework/internal'; +import { DateTime, Types } from '@app/framework/internal'; function isEmptyInputValue(value: any): boolean { return value == null || value.length === 0; @@ -175,4 +175,25 @@ export module ValidatorsEx { return null; }; } + + export function uniqueStrings(): ValidatorFn { + return (control: AbstractControl) => { + if (isEmptyInputValue(control.value) || !Types.isArrayOfString(control.value)) { + return null; + } + + const a: string[] = control.value; + const unique: { [key: string]: boolean } = {}; + + for (let value of a) { + if (unique[value]) { + return { uniquestrings: false }; + } else { + unique[value] = true; + } + } + + return null; + }; + } } \ No newline at end of file From 38fc575eac23a6ba8def5ca17fbc87739414c006 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 2 May 2019 18:18:57 +0200 Subject: [PATCH 02/12] Prevent duplicate references and a setting to enforce it. --- .../Schemas/AssetsFieldProperties.cs | 2 + .../Schemas/ReferencesFieldProperties.cs | 2 + .../ValidateContent/ValidatorsFactory.cs | 10 ++++ .../Models/Fields/AssetsFieldPropertiesDto.cs | 5 ++ .../Fields/ReferencesFieldPropertiesDto.cs | 5 ++ .../types/assets-validation.component.html | 11 ++++ .../types/assets-validation.component.ts | 3 + .../references-validation.component.html | 11 ++++ .../types/references-validation.component.ts | 3 + .../app/shared/services/schemas.types.ts | 2 + .../app/shared/state/contents.forms.spec.ts | 4 +- .../app/shared/state/contents.forms.ts | 8 +++ .../ValidateContent/AssetsFieldTests.cs | 59 ++++++++++++++----- .../ValidateContent/ReferencesFieldTests.cs | 21 +++++++ 14 files changed, 130 insertions(+), 16 deletions(-) diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs index 49e4d3c63..7566e6864 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs +++ b/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 AllowedExtensions { get; set; } public override T Accept(IFieldPropertiesVisitor visitor) diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs index c6af16a90..632e5b9bf 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs +++ b/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(IFieldPropertiesVisitor visitor) diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs index af7e3e698..ae6351a91 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs +++ b/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(); + } + 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(); + } + if (field.Properties.SchemaId != Guid.Empty) { yield return new ReferencesValidator(field.Properties.SchemaId); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs index 30a43fd08..9ef6cff0c 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs @@ -73,6 +73,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public string[] AllowedExtensions { get; set; } + /// + /// True, if duplicate values are allowed. + /// + public bool AllowDuplicates { get; set; } + public override FieldProperties ToProperties() { var result = SimpleMapper.Map(this, new AssetsFieldProperties()); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs index 07ee52dc5..8e73ed212 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs @@ -23,6 +23,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public int? MaxItems { get; set; } + /// + /// True, if duplicate values are allowed. + /// + public bool AllowDuplicates { get; set; } + /// /// The id of the referenced schema. /// diff --git a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html index 722b4b726..98c89760d 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/assets-validation.component.html @@ -94,6 +94,17 @@ + +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts index a7fcc797f..013a5141e 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts +++ b/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)); diff --git a/src/Squidex/app/shared/services/schemas.types.ts b/src/Squidex/app/shared/services/schemas.types.ts index ba94c6567..1da787806 100644 --- a/src/Squidex/app/shared/services/schemas.types.ts +++ b/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; diff --git a/src/Squidex/app/shared/state/contents.forms.spec.ts b/src/Squidex/app/shared/state/contents.forms.spec.ts index 7f7d11b04..bf7424bf0 100644 --- a/src/Squidex/app/shared/state/contents.forms.spec.ts +++ b/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', () => { diff --git a/src/Squidex/app/shared/state/contents.forms.ts b/src/Squidex/app/shared/state/contents.forms.ts index 20c07a9bf..f63e51c53 100644 --- a/src/Squidex/app/shared/state/contents.forms.ts +++ b/src/Squidex/app/shared/state/contents.forms.ts @@ -197,6 +197,10 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor (object)x.ToString()).ToArray()); From 9b56f886f2a40aebf44bb8f511b64b1ce3ded81f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 2 May 2019 21:21:18 +0200 Subject: [PATCH 03/12] Fix for restore. --- .../Validators/UniqueValuesValidator.cs | 32 ++++++++++ .../Apps/BackupApps.cs | 4 +- .../Validators/UniqueValuesValidatorTests.cs | 61 +++++++++++++++++++ tools/Migrate_01/OldEvents/AssetRenamed.cs | 3 +- tools/Migrate_01/OldEvents/AssetTagged.cs | 3 +- 5 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs new file mode 100644 index 000000000..7c948165b --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public sealed class UniqueValuesValidator : IValidator + { + public Task ValidateAsync(object value, ValidationContext context, AddError addError) + { + if (value is IEnumerable items && items.Any()) + { + var itemsArray = items.ToArray(); + + if (itemsArray.Length != itemsArray.Distinct().Count()) + { + addError(context.Path, "Must not contain duplicate values."); + } + } + + return TaskHelper.Done; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs b/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs index 7e1e46ef5..04c99856d 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs @@ -127,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Apps { if (!(isReserved = await appsByNameIndex.ReserveAppAsync(appId, appName))) { - throw new BackupRestoreException("The app id or name is not available."); + throw new BackupRestoreException("vThe app id or name is not available."); } } @@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Apps { if (isReserved) { - await appsByNameIndex.ReserveAppAsync(appId, appName); + await appsByNameIndex.RemoveReservationAsync(appId, appName); } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs new file mode 100644 index 000000000..d85013ce7 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs @@ -0,0 +1,61 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Squidex.Domain.Apps.Core.ValidateContent.Validators; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators +{ + public class UniqueValuesValidatorTests + { + private readonly List errors = new List(); + + [Fact] + public async Task Should_not_add_error_if_value_is_null() + { + var sut = new UniqueValuesValidator(); + + await sut.ValidateAsync(null, errors); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_value_is_not_collection() + { + var sut = new UniqueValuesValidator(); + + await sut.ValidateAsync("value", errors); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_array_contains_no_duplicates() + { + var sut = new UniqueValuesValidator(); + + await sut.ValidateAsync(new[] { 1, 2, 3 }, errors); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_add_error_if_array_contains_duplicates() + { + var sut = new UniqueValuesValidator(); + + await sut.ValidateAsync(new[] { 1, 2, 2, 3 }, errors); + + errors.Should().BeEquivalentTo( + new[] { "Must not contain duplicate values." }); + } + } +} diff --git a/tools/Migrate_01/OldEvents/AssetRenamed.cs b/tools/Migrate_01/OldEvents/AssetRenamed.cs index 44c899953..427c12fec 100644 --- a/tools/Migrate_01/OldEvents/AssetRenamed.cs +++ b/tools/Migrate_01/OldEvents/AssetRenamed.cs @@ -9,6 +9,7 @@ using System; using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; namespace Migrate_01.OldEvents { @@ -20,7 +21,7 @@ namespace Migrate_01.OldEvents public IEvent Migrate() { - return new AssetAnnotated { FileName = FileName }; + return SimpleMapper.Map(this, new AssetAnnotated()); } } } diff --git a/tools/Migrate_01/OldEvents/AssetTagged.cs b/tools/Migrate_01/OldEvents/AssetTagged.cs index d3c12807e..a67874cce 100644 --- a/tools/Migrate_01/OldEvents/AssetTagged.cs +++ b/tools/Migrate_01/OldEvents/AssetTagged.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; namespace Migrate_01.OldEvents { @@ -21,7 +22,7 @@ namespace Migrate_01.OldEvents public IEvent Migrate() { - return new AssetAnnotated { Tags = Tags }; + return SimpleMapper.Map(this, new AssetAnnotated()); } } } From d8ffb4309350b7cc2a6525623e9b159174d97d89 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 2 May 2019 21:32:28 +0200 Subject: [PATCH 04/12] Checkbox styling. --- .../Backups/BackupContentController.cs | 23 ++++++++++++++++--- .../forms/checkbox-group.component.html | 4 ++-- .../forms/checkbox-group.component.scss | 8 +++++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs b/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs index 25c965033..5965fd808 100644 --- a/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs +++ b/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs @@ -6,8 +6,11 @@ // ========================================================================== using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Orleans; +using Squidex.Domain.Apps.Entities.Backup; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; using Squidex.Web; @@ -21,11 +24,13 @@ namespace Squidex.Areas.Api.Controllers.Backups public class BackupContentController : ApiController { private readonly IAssetStore assetStore; + private readonly IGrainFactory grainFactory; - public BackupContentController(ICommandBus commandBus, IAssetStore assetStore) + public BackupContentController(ICommandBus commandBus, IAssetStore assetStore, IGrainFactory grainFactory) : base(commandBus) { this.assetStore = assetStore; + this.grainFactory = grainFactory; } /// @@ -43,9 +48,21 @@ namespace Squidex.Areas.Api.Controllers.Backups [ProducesResponseType(typeof(FileResult), 200)] [ApiCosts(0)] [AllowAnonymous] - public IActionResult GetBackupContent(string app, Guid id) + public async Task GetBackupContent(string app, Guid id) { - return new FileCallbackResult("application/zip", "Backup.zip", false, bodyStream => + var backupGrain = grainFactory.GetGrain(AppId); + + var backups = await backupGrain.GetStateAsync(); + var backup = backups.Value.Find(x => x.Id == id); + + if (backup == null || backup.Status != JobStatus.Completed) + { + return NotFound(); + } + + var fileName = $"backup-{app}-{backup.Started:yyyy-MM-dd_HH-mm-ss}"; + + return new FileCallbackResult("application/zip", fileName, false, bodyStream => { return assetStore.DownloadAsync(id.ToString(), 0, null, bodyStream); }); diff --git a/src/Squidex/app/framework/angular/forms/checkbox-group.component.html b/src/Squidex/app/framework/angular/forms/checkbox-group.component.html index f3245e6f2..03ac612dd 100644 --- a/src/Squidex/app/framework/angular/forms/checkbox-group.component.html +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.html @@ -1,4 +1,4 @@ - +
- \ No newline at end of file +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/checkbox-group.component.scss b/src/Squidex/app/framework/angular/forms/checkbox-group.component.scss index c30feb8f0..773582ba7 100644 --- a/src/Squidex/app/framework/angular/forms/checkbox-group.component.scss +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.scss @@ -2,11 +2,15 @@ @import '_vars'; .form-check { - display: inline-block; + display: block; margin-left: 0; - margin-right: 1rem; + margin-bottom: .5rem; } .form-check-input { margin-top: .4rem; +} + +label { + min-width: 5rem; } \ No newline at end of file From 49dbd6f7af109e55bcc9af6809209c9681d59062 Mon Sep 17 00:00:00 2001 From: "alwinnen@gmail.com" Date: Sat, 4 May 2019 12:45:59 +0200 Subject: [PATCH 05/12] fix "maximum call stack size exceeded" error with rich text editor --- .../angular/forms/control-errors.component.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.ts b/src/Squidex/app/framework/angular/forms/control-errors.component.ts index 010e2777e..22bd8af72 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -60,10 +60,7 @@ export class ControlErrorsComponent extends StatefulComponent implements public ngOnDestroy() { super.ngOnDestroy(); - - if (this.control && this.originalMarkAsTouched) { - this.control['markAsTouched'] = this.originalMarkAsTouched; - } + this.restoreOriginalMarkAsTouchedFunction(); } public ngOnChanges() { @@ -87,6 +84,7 @@ export class ControlErrorsComponent extends StatefulComponent implements if (this.control !== control) { this.unsubscribeAll(); + this.restoreOriginalMarkAsTouchedFunction(); this.control = control; @@ -112,6 +110,12 @@ export class ControlErrorsComponent extends StatefulComponent implements this.createMessages(); } + private restoreOriginalMarkAsTouchedFunction() { + if (this.control && this.originalMarkAsTouched) { + this.control['markAsTouched'] = this.originalMarkAsTouched; + } + } + private createMessages() { const errors: string[] = []; From 2ffa2df03ce9dd833d1b75b970458f495a3f5e12 Mon Sep 17 00:00:00 2001 From: Alexander Winnen <7125443+awinnen@users.noreply.github.com> Date: Sat, 4 May 2019 13:13:12 +0200 Subject: [PATCH 06/12] fix indention --- .../angular/forms/control-errors.component.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.ts b/src/Squidex/app/framework/angular/forms/control-errors.component.ts index 22bd8af72..d58093df1 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -60,7 +60,7 @@ export class ControlErrorsComponent extends StatefulComponent implements public ngOnDestroy() { super.ngOnDestroy(); - this.restoreOriginalMarkAsTouchedFunction(); + this.restoreOriginalMarkAsTouchedFunction(); } public ngOnChanges() { @@ -84,7 +84,7 @@ export class ControlErrorsComponent extends StatefulComponent implements if (this.control !== control) { this.unsubscribeAll(); - this.restoreOriginalMarkAsTouchedFunction(); + this.restoreOriginalMarkAsTouchedFunction(); this.control = control; @@ -110,11 +110,11 @@ export class ControlErrorsComponent extends StatefulComponent implements this.createMessages(); } - private restoreOriginalMarkAsTouchedFunction() { - if (this.control && this.originalMarkAsTouched) { - this.control['markAsTouched'] = this.originalMarkAsTouched; - } + private restoreOriginalMarkAsTouchedFunction() { + if (this.control && this.originalMarkAsTouched) { + this.control['markAsTouched'] = this.originalMarkAsTouched; } + } private createMessages() { const errors: string[] = []; @@ -135,4 +135,4 @@ export class ControlErrorsComponent extends StatefulComponent implements this.next(s => ({ ...s, errorMessages: errors })); } } -} \ No newline at end of file +} From f73f822d1076765b396ed4804f4785efaf51b2e1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 4 May 2019 14:31:13 +0200 Subject: [PATCH 07/12] Backup name fixed. --- .../Areas/Api/Controllers/Backups/BackupContentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs b/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs index 5965fd808..6c8eeba2f 100644 --- a/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs +++ b/src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs @@ -60,7 +60,7 @@ namespace Squidex.Areas.Api.Controllers.Backups return NotFound(); } - var fileName = $"backup-{app}-{backup.Started:yyyy-MM-dd_HH-mm-ss}"; + var fileName = $"backup-{app}-{backup.Started:yyyy-MM-dd_HH-mm-ss}.zip"; return new FileCallbackResult("application/zip", fileName, false, bodyStream => { From ec02459cc498aebcc786d668b345345ae18fa19a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 4 May 2019 15:23:40 +0200 Subject: [PATCH 08/12] Restore fixes. --- .../Actions/Algolia/AlgoliaAction.cs | 2 +- .../ElasticSearch/ElasticSearchAction.cs | 2 +- .../Apps/BackupApps.cs | 4 +- .../Backup/BackupGrain.cs | 54 +++++++++++++------ .../Backup/BackupHandler.cs | 2 +- .../Backup/Helpers/Downloader.cs | 4 +- .../Backup/Helpers/Safe.cs | 14 ++--- .../Backup/IBackupArchiveLocation.cs | 5 +- .../Backup/RestoreGrain.cs | 46 +++++++++------- .../Backup/TempFolderBackupArchiveLocation.cs | 7 ++- .../pages/backups/backups-page.component.html | 2 +- src/Squidex/appsettings.json | 6 ++- src/Squidex/package-lock.json | 10 ++-- tools/Migrate_00/Program.cs | 2 +- tools/Migrate_01/RebuildOptions.cs | 2 + tools/Migrate_01/RebuildRunner.cs | 11 +++- 16 files changed, 110 insertions(+), 63 deletions(-) diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs index eb6c041a4..99436019c 100644 --- a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs @@ -15,7 +15,7 @@ namespace Squidex.Extensions.Actions.Algolia IconImage = "", IconColor = "#0d9bf9", Display = "Populate Algolia index", - Description = "Populate and synchronize indices in Algolia for full text search.", + Description = "Populate and synchronize indexes in Algolia for full text search.", ReadMore = "https://www.algolia.com/")] public sealed class AlgoliaAction : RuleAction { diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 826f86f6d..67cf4e7d5 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -17,7 +17,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch IconImage = "", IconColor = "#1e5470", Display = "Populate ElasticSearch index", - Description = "Populate and synchronize indices in ElasticSearch for full text search.", + Description = "Populate and synchronize indexes in ElasticSearch for full text search.", ReadMore = "https://www.elastic.co/")] public sealed class ElasticSearchAction : RuleAction { diff --git a/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs b/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs index 04c99856d..d6f389ecc 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs @@ -127,11 +127,11 @@ namespace Squidex.Domain.Apps.Entities.Apps { if (!(isReserved = await appsByNameIndex.ReserveAppAsync(appId, appName))) { - throw new BackupRestoreException("vThe app id or name is not available."); + throw new BackupRestoreException("The app id or name is not available."); } } - public override async Task CleanupRestoreAsync(Guid appId) + public override async Task CleanupRestoreErrorAsync(Guid appId) { if (isReserved) { diff --git a/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs b/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs index fe6a484ec..4c892946d 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using NodaTime; using Orleans.Concurrency; using Squidex.Domain.Apps.Entities.Backup.Helpers; @@ -34,8 +35,8 @@ namespace Squidex.Domain.Apps.Entities.Backup private readonly IAssetStore assetStore; private readonly IBackupArchiveLocation backupArchiveLocation; private readonly IClock clock; - private readonly IEnumerable handlers; private readonly IJsonSerializer serializer; + private readonly IServiceProvider serviceProvider; private readonly IEventDataFormatter eventDataFormatter; private readonly IEventStore eventStore; private readonly ISemanticLog log; @@ -48,8 +49,8 @@ namespace Squidex.Domain.Apps.Entities.Backup IClock clock, IEventStore eventStore, IEventDataFormatter eventDataFormatter, - IEnumerable handlers, IJsonSerializer serializer, + IServiceProvider serviceProvider, ISemanticLog log, IStore store) : base(store) @@ -59,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Backup Guard.NotNull(clock, nameof(clock)); Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(handlers, nameof(handlers)); + Guard.NotNull(serviceProvider, nameof(serviceProvider)); Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(log, nameof(log)); @@ -68,8 +69,8 @@ namespace Squidex.Domain.Apps.Entities.Backup this.clock = clock; this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; - this.handlers = handlers; this.serializer = serializer; + this.serviceProvider = serviceProvider; this.log = log; } @@ -86,10 +87,12 @@ namespace Squidex.Domain.Apps.Entities.Backup { if (!job.Stopped.HasValue) { + var jobId = job.Id.ToString(); + job.Stopped = clock.GetCurrentInstant(); - await Safe.DeleteAsync(backupArchiveLocation, job.Id, log); - await Safe.DeleteAsync(assetStore, job.Id, log); + await Safe.DeleteAsync(backupArchiveLocation, jobId, log); + await Safe.DeleteAsync(assetStore, jobId, log); job.Status = JobStatus.Failed; @@ -120,15 +123,29 @@ namespace Squidex.Domain.Apps.Entities.Backup currentTask = new CancellationTokenSource(); currentJob = job; - var lastTimestamp = job.Started; - State.Jobs.Insert(0, job); await WriteStateAsync(); + Process(job, currentTask.Token); + } + + private void Process(BackupStateJob job, CancellationToken ct) + { + ProcessAsync(job, ct).Forget(); + } + + private async Task ProcessAsync(BackupStateJob job, CancellationToken ct) + { + var jobId = job.Id.ToString(); + + var handlers = CreateHandlers(); + + var lastTimestamp = job.Started; + try { - using (var stream = await backupArchiveLocation.OpenStreamAsync(job.Id)) + using (var stream = await backupArchiveLocation.OpenStreamAsync(jobId)) { using (var writer = new BackupWriter(serializer, stream, true)) { @@ -162,16 +179,16 @@ namespace Squidex.Domain.Apps.Entities.Backup stream.Position = 0; - currentTask.Token.ThrowIfCancellationRequested(); + ct.ThrowIfCancellationRequested(); - await assetStore.UploadAsync(job.Id.ToString(), 0, null, stream, false, currentTask.Token); + await assetStore.UploadAsync(jobId, 0, null, stream, false, currentTask.Token); } job.Status = JobStatus.Completed; } catch (Exception ex) { - log.LogError(ex, job.Id.ToString(), (ctx, w) => w + log.LogError(ex, jobId, (ctx, w) => w .WriteProperty("action", "makeBackup") .WriteProperty("status", "failed") .WriteProperty("backupId", ctx)); @@ -180,7 +197,7 @@ namespace Squidex.Domain.Apps.Entities.Backup } finally { - await Safe.DeleteAsync(backupArchiveLocation, job.Id, log); + await Safe.DeleteAsync(backupArchiveLocation, jobId, log); job.Stopped = clock.GetCurrentInstant(); @@ -220,8 +237,10 @@ namespace Squidex.Domain.Apps.Entities.Backup } else { - await Safe.DeleteAsync(backupArchiveLocation, job.Id, log); - await Safe.DeleteAsync(assetStore, job.Id, log); + var jobId = job.Id.ToString(); + + await Safe.DeleteAsync(backupArchiveLocation, jobId, log); + await Safe.DeleteAsync(assetStore, jobId, log); State.Jobs.Remove(job); @@ -229,6 +248,11 @@ namespace Squidex.Domain.Apps.Entities.Backup } } + private IEnumerable CreateHandlers() + { + return serviceProvider.GetRequiredService>(); + } + public Task>> GetStateAsync() { return J.AsTask(State.Jobs.OfType().ToList()); diff --git a/src/Squidex.Domain.Apps.Entities/Backup/BackupHandler.cs b/src/Squidex.Domain.Apps.Entities/Backup/BackupHandler.cs index b891d58d9..dc0bb7b0d 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/BackupHandler.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/BackupHandler.cs @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Backup return TaskHelper.Done; } - public virtual Task CleanupRestoreAsync(Guid appId) + public virtual Task CleanupRestoreErrorAsync(Guid appId) { return TaskHelper.Done; } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs b/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs index 9529c4bf5..5a8aaff9f 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers { public static class Downloader { - public static async Task DownloadAsync(this IBackupArchiveLocation backupArchiveLocation, Uri url, Guid id) + public static async Task DownloadAsync(this IBackupArchiveLocation backupArchiveLocation, Uri url, string id) { if (string.Equals(url.Scheme, "file")) { @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers } } - public static async Task OpenArchiveAsync(this IBackupArchiveLocation backupArchiveLocation, Guid id, IJsonSerializer serializer) + public static async Task OpenArchiveAsync(this IBackupArchiveLocation backupArchiveLocation, string id, IJsonSerializer serializer) { Stream stream = null; diff --git a/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Safe.cs b/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Safe.cs index 9019ce27b..4938b7788 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Safe.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Safe.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers { public static class Safe { - public static async Task DeleteAsync(IBackupArchiveLocation backupArchiveLocation, Guid id, ISemanticLog log) + public static async Task DeleteAsync(IBackupArchiveLocation backupArchiveLocation, string id, ISemanticLog log) { try { @@ -22,33 +22,33 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers } catch (Exception ex) { - log.LogError(ex, id.ToString(), (logOperationId, w) => w + log.LogError(ex, id, (logOperationId, w) => w .WriteProperty("action", "deleteArchive") .WriteProperty("status", "failed") .WriteProperty("operationId", logOperationId)); } } - public static async Task DeleteAsync(IAssetStore assetStore, Guid id, ISemanticLog log) + public static async Task DeleteAsync(IAssetStore assetStore, string id, ISemanticLog log) { try { - await assetStore.DeleteAsync(id.ToString(), 0, null); + await assetStore.DeleteAsync(id, 0, null); } catch (Exception ex) { - log.LogError(ex, id.ToString(), (logOperationId, w) => w + log.LogError(ex, id, (logOperationId, w) => w .WriteProperty("action", "deleteBackup") .WriteProperty("status", "failed") .WriteProperty("operationId", logOperationId)); } } - public static async Task CleanupRestoreAsync(BackupHandler handler, Guid appId, Guid id, ISemanticLog log) + public static async Task CleanupRestoreErrorAsync(BackupHandler handler, Guid appId, Guid id, ISemanticLog log) { try { - await handler.CleanupRestoreAsync(appId); + await handler.CleanupRestoreErrorAsync(appId); } catch (Exception ex) { diff --git a/src/Squidex.Domain.Apps.Entities/Backup/IBackupArchiveLocation.cs b/src/Squidex.Domain.Apps.Entities/Backup/IBackupArchiveLocation.cs index 298372a9c..96c498639 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/IBackupArchiveLocation.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/IBackupArchiveLocation.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.IO; using System.Threading.Tasks; @@ -13,8 +12,8 @@ namespace Squidex.Domain.Apps.Entities.Backup { public interface IBackupArchiveLocation { - Task OpenStreamAsync(Guid backupId); + Task OpenStreamAsync(string backupId); - Task DeleteArchiveAsync(Guid backupId); + Task DeleteArchiveAsync(string backupId); } } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs b/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs index 9ceec5592..d2cf81238 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using NodaTime; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; @@ -31,11 +32,11 @@ namespace Squidex.Domain.Apps.Entities.Backup private readonly IBackupArchiveLocation backupArchiveLocation; private readonly IClock clock; private readonly ICommandBus commandBus; - private readonly IEnumerable handlers; private readonly IJsonSerializer serializer; private readonly IEventStore eventStore; private readonly IEventDataFormatter eventDataFormatter; private readonly ISemanticLog log; + private readonly IServiceProvider serviceProvider; private readonly IStreamNameResolver streamNameResolver; private RestoreStateJob CurrentJob @@ -48,9 +49,9 @@ namespace Squidex.Domain.Apps.Entities.Backup ICommandBus commandBus, IEventStore eventStore, IEventDataFormatter eventDataFormatter, - IEnumerable handlers, IJsonSerializer serializer, ISemanticLog log, + IServiceProvider serviceProvider, IStreamNameResolver streamNameResolver, IStore store) : base(store) @@ -60,8 +61,8 @@ namespace Squidex.Domain.Apps.Entities.Backup Guard.NotNull(commandBus, nameof(commandBus)); Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(handlers, nameof(handlers)); Guard.NotNull(serializer, nameof(serializer)); + Guard.NotNull(serviceProvider, nameof(serviceProvider)); Guard.NotNull(store, nameof(store)); Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); Guard.NotNull(log, nameof(log)); @@ -71,8 +72,8 @@ namespace Squidex.Domain.Apps.Entities.Backup this.commandBus = commandBus; this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; - this.handlers = handlers; this.serializer = serializer; + this.serviceProvider = serviceProvider; this.streamNameResolver = streamNameResolver; this.log = log; } @@ -88,16 +89,18 @@ namespace Squidex.Domain.Apps.Entities.Backup { if (CurrentJob?.Status == JobStatus.Started) { + var handlers = CreateHandlers(); + Log("Failed due application restart"); CurrentJob.Status = JobStatus.Failed; - await CleanupAsync(); + await CleanupAsync(handlers); await WriteStateAsync(); } } - public Task RestoreAsync(Uri url, RefToken actor, string newAppName) + public async Task RestoreAsync(Uri url, RefToken actor, string newAppName) { Guard.NotNull(url, nameof(url)); Guard.NotNull(actor, nameof(actor)); @@ -122,9 +125,9 @@ namespace Squidex.Domain.Apps.Entities.Backup Url = url }; - Process(); + await WriteStateAsync(); - return TaskHelper.Done; + Process(); } private void Process() @@ -134,6 +137,8 @@ namespace Squidex.Domain.Apps.Entities.Backup private async Task ProcessAsync() { + var handlers = CreateHandlers(); + var logContext = (jobId: CurrentJob.Id.ToString(), jobUrl: CurrentJob.Url.ToString()); using (Profiler.StartSession()) @@ -157,11 +162,11 @@ namespace Squidex.Domain.Apps.Entities.Backup await DownloadAsync(); } - using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id, serializer)) + using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id.ToString(), serializer)) { using (Profiler.Trace("ReadEvents")) { - await ReadEventsAsync(reader); + await ReadEventsAsync(reader, handlers); } foreach (var handler in handlers) @@ -212,7 +217,7 @@ namespace Squidex.Domain.Apps.Entities.Backup Log("Failed with internal error"); } - await CleanupAsync(); + await CleanupAsync(handlers); CurrentJob.Status = JobStatus.Failed; @@ -258,15 +263,15 @@ namespace Squidex.Domain.Apps.Entities.Backup } } - private async Task CleanupAsync() + private async Task CleanupAsync(IEnumerable handlers) { - await Safe.DeleteAsync(backupArchiveLocation, CurrentJob.Id, log); + await Safe.DeleteAsync(backupArchiveLocation, CurrentJob.Id.ToString(), log); if (CurrentJob.AppId != Guid.Empty) { foreach (var handler in handlers) { - await Safe.CleanupRestoreAsync(handler, CurrentJob.AppId, CurrentJob.Id, log); + await Safe.CleanupRestoreErrorAsync(handler, CurrentJob.AppId, CurrentJob.Id, log); } } } @@ -275,22 +280,22 @@ namespace Squidex.Domain.Apps.Entities.Backup { Log("Downloading Backup"); - await backupArchiveLocation.DownloadAsync(CurrentJob.Url, CurrentJob.Id); + await backupArchiveLocation.DownloadAsync(CurrentJob.Url, CurrentJob.Id.ToString()); Log("Downloaded Backup"); } - private async Task ReadEventsAsync(BackupReader reader) + private async Task ReadEventsAsync(BackupReader reader, IEnumerable handlers) { await reader.ReadEventsAsync(streamNameResolver, eventDataFormatter, async storedEvent => { - await HandleEventAsync(reader, storedEvent.Stream, storedEvent.Event); + await HandleEventAsync(reader, handlers, storedEvent.Stream, storedEvent.Event); }); Log($"Reading {reader.ReadEvents} events and {reader.ReadAttachments} attachments completed.", true); } - private async Task HandleEventAsync(BackupReader reader, string stream, Envelope @event) + private async Task HandleEventAsync(BackupReader reader, IEnumerable handlers, string stream, Envelope @event) { if (@event.Payload is SquidexEvent squidexEvent) { @@ -340,6 +345,11 @@ namespace Squidex.Domain.Apps.Entities.Backup } } + private IEnumerable CreateHandlers() + { + return serviceProvider.GetRequiredService>(); + } + public Task> GetJobAsync() { return Task.FromResult>(CurrentJob); diff --git a/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs b/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs index 699ec524a..4f5822ce7 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.IO; using System.Threading.Tasks; using Squidex.Infrastructure.Tasks; @@ -14,14 +13,14 @@ namespace Squidex.Domain.Apps.Entities.Backup { public sealed class TempFolderBackupArchiveLocation : IBackupArchiveLocation { - public Task OpenStreamAsync(Guid backupId) + public Task OpenStreamAsync(string backupId) { var tempFile = GetTempFile(backupId); return Task.FromResult(new FileStream(tempFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)); } - public Task DeleteArchiveAsync(Guid backupId) + public Task DeleteArchiveAsync(string backupId) { var tempFile = GetTempFile(backupId); @@ -36,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Backup return TaskHelper.Done; } - private static string GetTempFile(Guid backupId) + private static string GetTempFile(string backupId) { return Path.Combine(Path.GetTempPath(), backupId + ".zip"); } diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html index 53d3871db..dddcf8cf6 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html @@ -40,7 +40,7 @@
-
+
diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index 802116cc0..21bfb1ac0 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -430,6 +430,10 @@ /* * Set to true to rebuild schemas. */ - "schemas": false + "schemas": false, + /* + * Set to true to rebuild indexes. + */ + "indexes": false } } diff --git a/src/Squidex/package-lock.json b/src/Squidex/package-lock.json index addc1d0f2..70a1d3fa3 100644 --- a/src/Squidex/package-lock.json +++ b/src/Squidex/package-lock.json @@ -2037,7 +2037,7 @@ "dependencies": { "jsesc": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, @@ -9249,7 +9249,7 @@ }, "node-fetch": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, "node-forge": { @@ -9639,7 +9639,7 @@ }, "onetime": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "dev": true }, @@ -13298,7 +13298,7 @@ "dependencies": { "json5": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { @@ -15408,7 +15408,7 @@ }, "whatwg-fetch": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, "which": { diff --git a/tools/Migrate_00/Program.cs b/tools/Migrate_00/Program.cs index a4b7f51a4..fd6e59b31 100644 --- a/tools/Migrate_00/Program.cs +++ b/tools/Migrate_00/Program.cs @@ -22,7 +22,7 @@ namespace Migrate_00 var collection = mongoDatabase.GetCollection("Events"); - Console.Write("Migrate Indices....."); + Console.Write("Migrate indexes....."); collection.Indexes.DropAll(); diff --git a/tools/Migrate_01/RebuildOptions.cs b/tools/Migrate_01/RebuildOptions.cs index 436841883..94f9743c9 100644 --- a/tools/Migrate_01/RebuildOptions.cs +++ b/tools/Migrate_01/RebuildOptions.cs @@ -15,6 +15,8 @@ namespace Migrate_01 public bool Contents { get; set; } + public bool Indexes { get; set; } + public bool Rules { get; set; } public bool Schemas { get; set; } diff --git a/tools/Migrate_01/RebuildRunner.cs b/tools/Migrate_01/RebuildRunner.cs index 742e9e670..c14120d5a 100644 --- a/tools/Migrate_01/RebuildRunner.cs +++ b/tools/Migrate_01/RebuildRunner.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; +using Migrate_01.Migrations; using Squidex.Infrastructure; namespace Migrate_01 @@ -15,15 +16,18 @@ namespace Migrate_01 public sealed class RebuildRunner { private readonly Rebuilder rebuilder; + private readonly PopulateGrainIndexes populateGrainIndexes; private readonly RebuildOptions rebuildOptions; - public RebuildRunner(Rebuilder rebuilder, IOptions rebuildOptions) + public RebuildRunner(Rebuilder rebuilder, IOptions rebuildOptions, PopulateGrainIndexes populateGrainIndexes) { Guard.NotNull(rebuilder, nameof(rebuilder)); Guard.NotNull(rebuildOptions, nameof(rebuildOptions)); + Guard.NotNull(populateGrainIndexes, nameof(populateGrainIndexes)); this.rebuilder = rebuilder; this.rebuildOptions = rebuildOptions.Value; + this.populateGrainIndexes = populateGrainIndexes; } public async Task RunAsync(CancellationToken ct) @@ -52,6 +56,11 @@ namespace Migrate_01 { await rebuilder.RebuildContentAsync(ct); } + + if (rebuildOptions.Indexes) + { + await populateGrainIndexes.UpdateAsync(); + } } } } From d5314fcdb71bacd8ddaedd2c40ff6b5de00e5c6f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 4 May 2019 15:25:52 +0200 Subject: [PATCH 09/12] Use local ct. --- src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs b/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs index 4c892946d..5adab061f 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs @@ -164,7 +164,7 @@ namespace Squidex.Domain.Apps.Entities.Backup job.HandledAssets = writer.WrittenAttachments; lastTimestamp = await WritePeriodically(lastTimestamp); - }, SquidexHeaders.AppId, Key.ToString(), null, currentTask.Token); + }, SquidexHeaders.AppId, Key.ToString(), null, ct); foreach (var handler in handlers) { @@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Entities.Backup ct.ThrowIfCancellationRequested(); - await assetStore.UploadAsync(jobId, 0, null, stream, false, currentTask.Token); + await assetStore.UploadAsync(jobId, 0, null, stream, false, ct); } job.Status = JobStatus.Completed; From 78f92150b4ebc723d5eaef8796c7ac41d3afa0f2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 4 May 2019 15:53:23 +0200 Subject: [PATCH 10/12] Use slugify.js table. --- .../StringExtensions.cs | 708 ++++++++++++------ 1 file changed, 460 insertions(+), 248 deletions(-) diff --git a/src/Squidex.Infrastructure/StringExtensions.cs b/src/Squidex.Infrastructure/StringExtensions.cs index 1cda44f3d..e0e198223 100644 --- a/src/Squidex.Infrastructure/StringExtensions.cs +++ b/src/Squidex.Infrastructure/StringExtensions.cs @@ -24,287 +24,499 @@ namespace Squidex.Infrastructure private static readonly Dictionary LowerCaseDiacritics; private static readonly Dictionary Diacritics = new Dictionary { + ['$'] = "dollar", + ['%'] = "percent", + ['&'] = "and", + ['<'] = "less", + ['>'] = "greater", + ['|'] = "or", + ['¢'] = "cent", + ['£'] = "pound", + ['¤'] = "currency", + ['¥'] = "yen", + ['©'] = "(c)", + ['ª'] = "a", + ['®'] = "(r)", + ['º'] = "o", ['À'] = "A", - ['à'] = "a", - ['Ā'] = "A", - ['Ġ'] = "G", - ['ŀ'] = "l", - ['Š'] = "S", - ['Ǡ'] = "A", - ['Ȁ'] = "A", ['Á'] = "A", - ['á'] = "a", - ['ā'] = "a", - ['ġ'] = "g", - ['Ł'] = "L", - ['š'] = "s", - ['ǡ'] = "a", - ['ȁ'] = "a", ['Â'] = "A", - ['â'] = "a", - ['Ă'] = "A", - ['Ģ'] = "G", - ['ł'] = "l", - ['Ţ'] = "T", - ['Ǣ'] = "Ae", - ['Ȃ'] = "A", ['Ã'] = "A", - ['ã'] = "a", - ['ă'] = "a", - ['ģ'] = "g", - ['Ń'] = "N", - ['ţ'] = "t", - ['ǣ'] = "ae", - ['ȃ'] = "a", - ['Ä'] = "Ae", - ['ä'] = "ae", - ['Ą'] = "A", - ['Ĥ'] = "H", - ['ń'] = "n", - ['Ť'] = "T", - ['DŽ'] = "DZ", - ['Ǥ'] = "G", - ['Ȅ'] = "E", + ['Ä'] = "AE", ['Å'] = "A", - ['å'] = "a", - ['ą'] = "a", - ['ĥ'] = "h", - ['Ņ'] = "N", - ['ť'] = "t", - ['Dž'] = "Dz", - ['ǥ'] = "g", - ['ȅ'] = "e", ['Æ'] = "AE", - ['æ'] = "ae", - ['Ć'] = "C", - ['Ħ'] = "H", - ['ņ'] = "n", - ['Ŧ'] = "T", - ['dž'] = "dz", - ['Ǧ'] = "G", - ['Ȇ'] = "E", ['Ç'] = "C", - ['ç'] = "c", - ['ć'] = "c", - ['ħ'] = "h", - ['Ň'] = "N", - ['ŧ'] = "t", - ['LJ'] = "W", - ['ǧ'] = "g", - ['ȇ'] = "e", ['È'] = "E", - ['è'] = "E", - ['Ĉ'] = "C", - ['Ĩ'] = "I", - ['ň'] = "n", - ['Ũ'] = "U", - ['Lj'] = "Lj", - ['Ǩ'] = "K", - ['Ȉ'] = "I", ['É'] = "E", - ['é'] = "e", - ['ĉ'] = "c", - ['ĩ'] = "i", - ['ʼn'] = "n", - ['ũ'] = "u", - ['lj'] = "lj", - ['ǩ'] = "k", - ['ȉ'] = "i", ['Ê'] = "E", - ['ê'] = "e", - ['Ċ'] = "C", - ['Ī'] = "I", - ['Ŋ'] = "n", - ['Ū'] = "U", - ['NJ'] = "NJ", - ['Ǫ'] = "O", - ['Ȋ'] = "I", ['Ë'] = "E", - ['ë'] = "e", - ['ċ'] = "c", - ['ī'] = "i", - ['ŋ'] = "n", - ['ū'] = "u", - ['Nj'] = "Nj", - ['ǫ'] = "o", - ['ȋ'] = "i", ['Ì'] = "I", - ['ì'] = "i", - ['Č'] = "C", - ['Ĭ'] = "I", - ['Ō'] = "O", - ['Ŭ'] = "U", - ['nj'] = "nj", - ['Ǭ'] = "O", - ['Ȍ'] = "O", ['Í'] = "I", - ['í'] = "i", - ['č'] = "c", - ['ĭ'] = "i", - ['ō'] = "o", - ['ŭ'] = "u", - ['Ǎ'] = "A", - ['ǭ'] = "o", - ['ȍ'] = "o", ['Î'] = "I", - ['î'] = "i", - ['Ď'] = "D", - ['Į'] = "I", - ['Ŏ'] = "O", - ['Ů'] = "U", - ['ǎ'] = "a", - ['Ǯ'] = "z", - ['Ȏ'] = "O", ['Ï'] = "I", - ['ï'] = "i", - ['ď'] = "d", - ['į'] = "i", - ['ŏ'] = "o", - ['ů'] = "u", - ['Ǐ'] = "I", - ['ǯ'] = "z", - ['ȏ'] = "o", ['Ð'] = "D", - ['ð'] = "d", - ['Đ'] = "D", - ['İ'] = "I", - ['Ő'] = "O", - ['Ű'] = "U", - ['ǐ'] = "i", - ['ǰ'] = "j", - ['Ȑ'] = "R", ['Ñ'] = "N", - ['ñ'] = "n", - ['đ'] = "d", - ['ı'] = "i", - ['ő'] = "o", - ['ű'] = "u", - ['Ǒ'] = "O", - ['DZ'] = "DZ", - ['ȑ'] = "r", ['Ò'] = "O", - ['ò'] = "o", - ['Ē'] = "E", - ['IJ'] = "LJ", - ['Œ'] = "OE", - ['Ų'] = "U", - ['ǒ'] = "o", - ['Dz'] = "Dz", - ['Ȓ'] = "R", ['Ó'] = "O", - ['ó'] = "o", - ['ē'] = "e", - ['ij'] = "ij", - ['œ'] = "oe", - ['ų'] = "u", - ['Ǔ'] = "U", - ['dz'] = "dz", - ['ȓ'] = "r", ['Ô'] = "O", - ['ô'] = "o", - ['Ĕ'] = "E", - ['Ĵ'] = "J", - ['Ŕ'] = "R", - ['Ŵ'] = "W", - ['ǔ'] = "u", - ['Ǵ'] = "G", - ['Ȕ'] = "U", ['Õ'] = "O", + ['Ö'] = "OE", + ['Ø'] = "O", + ['Ù'] = "U", + ['Ú'] = "U", + ['Û'] = "U", + ['Ü'] = "UE", + ['Ý'] = "Y", + ['Þ'] = "TH", + ['ß'] = "ss", + ['à'] = "a", + ['á'] = "a", + ['â'] = "a", + ['ã'] = "a", + ['ä'] = "ae", + ['å'] = "a", + ['æ'] = "ae", + ['ç'] = "c", + ['è'] = "e", + ['é'] = "e", + ['ê'] = "e", + ['ë'] = "e", + ['ì'] = "i", + ['í'] = "i", + ['î'] = "i", + ['ï'] = "i", + ['ð'] = "d", + ['ñ'] = "n", + ['ò'] = "o", + ['ó'] = "o", + ['ô'] = "o", ['õ'] = "o", - ['ĕ'] = "e", - ['ĵ'] = "j", - ['ŕ'] = "r", - ['ŵ'] = "w", - ['Ǖ'] = "U", - ['ǵ'] = "g", - ['ȕ'] = "u", - ['Ö'] = "Oe", ['ö'] = "oe", - ['Ė'] = "E", - ['Ķ'] = "K", - ['Ŗ'] = "R", - ['Ŷ'] = "Y", - ['ǖ'] = "u", - ['Ƕ'] = "Hj", - ['Ȗ'] = "U", - ['ė'] = "e", - ['ķ'] = "k", - ['ŗ'] = "r", - ['ŷ'] = "y", - ['Ǘ'] = "U", - ['ȗ'] = "u", - ['Ø'] = "O", ['ø'] = "o", - ['Ę'] = "E", - ['ĸ'] = "k", - ['Ř'] = "R", - ['Ÿ'] = "Y", - ['ǘ'] = "u", - ['Ǹ'] = "N", - ['Ș'] = "S", - ['Ù'] = "U", ['ù'] = "u", - ['ę'] = "e", - ['Ĺ'] = "L", - ['ř'] = "r", - ['Ź'] = "Z", - ['Ǚ'] = "U", - ['ǹ'] = "n", - ['ș'] = "s", - ['Ú'] = "U", ['ú'] = "u", - ['Ě'] = "E", - ['ĺ'] = "l", - ['Ś'] = "S", - ['ź'] = "z", - ['ǚ'] = "u", - ['Ǻ'] = "A", - ['Ț'] = "T", - ['Û'] = "U", ['û'] = "u", + ['ü'] = "ue", + ['ý'] = "y", + ['þ'] = "th", + ['ÿ'] = "y", + ['Ā'] = "A", + ['ā'] = "a", + ['Ă'] = "A", + ['ă'] = "a", + ['Ą'] = "A", + ['ą'] = "a", + ['Ć'] = "C", + ['ć'] = "c", + ['Č'] = "C", + ['č'] = "c", + ['Ď'] = "D", + ['ď'] = "d", + ['Đ'] = "DJ", + ['đ'] = "dj", + ['Ē'] = "E", + ['ē'] = "e", + ['Ė'] = "E", + ['ė'] = "e", + ['Ę'] = "e", + ['ę'] = "e", + ['Ě'] = "E", ['ě'] = "e", + ['Ğ'] = "G", + ['ğ'] = "g", + ['Ģ'] = "G", + ['ģ'] = "g", + ['Ĩ'] = "I", + ['ĩ'] = "i", + ['Ī'] = "i", + ['ī'] = "i", + ['Į'] = "I", + ['į'] = "i", + ['İ'] = "I", + ['ı'] = "i", + ['Ķ'] = "k", + ['ķ'] = "k", ['Ļ'] = "L", + ['ļ'] = "l", + ['Ľ'] = "L", + ['ľ'] = "l", + ['Ł'] = "L", + ['ł'] = "l", + ['Ń'] = "N", + ['ń'] = "n", + ['Ņ'] = "N", + ['ņ'] = "n", + ['Ň'] = "N", + ['ň'] = "n", + ['Ő'] = "O", + ['ő'] = "o", + ['Œ'] = "OE", + ['œ'] = "oe", + ['Ŕ'] = "R", + ['ŕ'] = "r", + ['Ř'] = "R", + ['ř'] = "r", + ['Ś'] = "S", ['ś'] = "s", + ['Ş'] = "S", + ['ş'] = "s", + ['Š'] = "S", + ['š'] = "s", + ['Ţ'] = "T", + ['ţ'] = "t", + ['Ť'] = "T", + ['ť'] = "t", + ['Ũ'] = "U", + ['ũ'] = "u", + ['Ū'] = "u", + ['ū'] = "u", + ['Ů'] = "U", + ['ů'] = "u", + ['Ű'] = "U", + ['ű'] = "u", + ['Ų'] = "U", + ['ų'] = "u", + ['Ź'] = "Z", + ['ź'] = "z", ['Ż'] = "Z", - ['Ǜ'] = "U", - ['ǻ'] = "a", - ['ț'] = "t", - ['Ü'] = "Ue", - ['ü'] = "ue", - ['Ĝ'] = "G", - ['ļ'] = "l", - ['Ŝ'] = "S", ['ż'] = "z", - ['ǜ'] = "u", - ['Ǽ'] = "AE", - ['Ȝ'] = "z", - ['Ý'] = "Y", - ['ý'] = "y", - ['ĝ'] = "g", - ['Ľ'] = "L", - ['ŝ'] = "s", ['Ž'] = "Z", - ['ǝ'] = "e", - ['ǽ'] = "ae", - ['ȝ'] = "z", - ['Þ'] = "p", - ['þ'] = "p", - ['Ğ'] = "G", - ['ľ'] = "L", - ['Ş'] = "S", ['ž'] = "z", - ['Ǟ'] = "A", - ['Ǿ'] = "O", - ['Ȟ'] = "H", - ['ß'] = "ss", - ['ÿ'] = "y", - ['ğ'] = "g", - ['Ŀ'] = "L", - ['ş'] = "s", - ['ſ'] = "l", - ['ǟ'] = "a", - ['ǿ'] = "o", - ['ȟ'] = "h" + ['ƒ'] = "f", + ['Ơ'] = "O", + ['ơ'] = "o", + ['Ư'] = "U", + ['ư'] = "u", + ['Lj'] = "LJ", + ['lj'] = "lj", + ['Nj'] = "NJ", + ['nj'] = "nj", + ['Ș'] = "S", + ['ș'] = "s", + ['Ț'] = "T", + ['ț'] = "t", + ['˚'] = "o", + ['Ά'] = "A", + ['Έ'] = "E", + ['Ή'] = "H", + ['Ί'] = "I", + ['Ό'] = "O", + ['Ύ'] = "Y", + ['Ώ'] = "W", + ['ΐ'] = "i", + ['Α'] = "A", + ['Β'] = "B", + ['Γ'] = "G", + ['Δ'] = "D", + ['Ε'] = "E", + ['Ζ'] = "Z", + ['Η'] = "H", + ['Θ'] = "8", + ['Ι'] = "I", + ['Κ'] = "K", + ['Λ'] = "L", + ['Μ'] = "M", + ['Ν'] = "N", + ['Ξ'] = "3", + ['Ο'] = "O", + ['Π'] = "P", + ['Ρ'] = "R", + ['Σ'] = "S", + ['Τ'] = "T", + ['Υ'] = "Y", + ['Φ'] = "F", + ['Χ'] = "X", + ['Ψ'] = "PS", + ['Ω'] = "W", + ['Ϊ'] = "I", + ['Ϋ'] = "Y", + ['ά'] = "a", + ['έ'] = "e", + ['ή'] = "h", + ['ί'] = "i", + ['ΰ'] = "y", + ['α'] = "a", + ['β'] = "b", + ['γ'] = "g", + ['δ'] = "d", + ['ε'] = "e", + ['ζ'] = "z", + ['η'] = "h", + ['θ'] = "8", + ['ι'] = "i", + ['κ'] = "k", + ['λ'] = "l", + ['μ'] = "m", + ['ν'] = "n", + ['ξ'] = "3", + ['ο'] = "o", + ['π'] = "p", + ['ρ'] = "r", + ['ς'] = "s", + ['σ'] = "s", + ['τ'] = "t", + ['υ'] = "y", + ['φ'] = "f", + ['χ'] = "x", + ['ψ'] = "ps", + ['ω'] = "w", + ['ϊ'] = "i", + ['ϋ'] = "y", + ['ό'] = "o", + ['ύ'] = "y", + ['ώ'] = "w", + ['Ё'] = "Yo", + ['Ђ'] = "DJ", + ['Є'] = "Ye", + ['І'] = "I", + ['Ї'] = "Yi", + ['Ј'] = "J", + ['Љ'] = "LJ", + ['Њ'] = "NJ", + ['Ћ'] = "C", + ['Џ'] = "DZ", + ['А'] = "A", + ['Б'] = "B", + ['В'] = "V", + ['Г'] = "G", + ['Д'] = "D", + ['Е'] = "E", + ['Ж'] = "Zh", + ['З'] = "Z", + ['И'] = "I", + ['Й'] = "J", + ['К'] = "K", + ['Л'] = "L", + ['М'] = "M", + ['Н'] = "N", + ['О'] = "O", + ['П'] = "P", + ['Р'] = "R", + ['С'] = "S", + ['Т'] = "T", + ['У'] = "U", + ['Ф'] = "F", + ['Х'] = "H", + ['Ц'] = "C", + ['Ч'] = "Ch", + ['Ш'] = "Sh", + ['Щ'] = "Sh", + ['Ъ'] = "U", + ['Ы'] = "Y", + ['Ь'] = "b", + ['Э'] = "E", + ['Ю'] = "Yu", + ['Я'] = "Ya", + ['а'] = "a", + ['б'] = "b", + ['в'] = "v", + ['г'] = "g", + ['д'] = "d", + ['е'] = "e", + ['ж'] = "zh", + ['з'] = "z", + ['и'] = "i", + ['й'] = "j", + ['к'] = "k", + ['л'] = "l", + ['м'] = "m", + ['н'] = "n", + ['о'] = "o", + ['п'] = "p", + ['р'] = "r", + ['с'] = "s", + ['т'] = "t", + ['у'] = "u", + ['ф'] = "f", + ['х'] = "h", + ['ц'] = "c", + ['ч'] = "ch", + ['ш'] = "sh", + ['щ'] = "sh", + ['ъ'] = "u", + ['ы'] = "y", + ['ь'] = "s", + ['э'] = "e", + ['ю'] = "yu", + ['я'] = "ya", + ['ё'] = "yo", + ['ђ'] = "dj", + ['є'] = "ye", + ['і'] = "i", + ['ї'] = "yi", + ['ј'] = "j", + ['љ'] = "lj", + ['њ'] = "nj", + ['ћ'] = "c", + ['џ'] = "dz", + ['Ґ'] = "G", + ['ґ'] = "g", + ['฿'] = "baht", + ['ა'] = "a", + ['ბ'] = "b", + ['გ'] = "g", + ['დ'] = "d", + ['ე'] = "e", + ['ვ'] = "v", + ['ზ'] = "z", + ['თ'] = "t", + ['ი'] = "i", + ['კ'] = "k", + ['ლ'] = "l", + ['მ'] = "m", + ['ნ'] = "n", + ['ო'] = "o", + ['პ'] = "p", + ['ჟ'] = "zh", + ['რ'] = "r", + ['ს'] = "s", + ['ტ'] = "t", + ['უ'] = "u", + ['ფ'] = "f", + ['ქ'] = "k", + ['ღ'] = "gh", + ['ყ'] = "q", + ['შ'] = "sh", + ['ჩ'] = "ch", + ['ც'] = "ts", + ['ძ'] = "dz", + ['წ'] = "ts", + ['ჭ'] = "ch", + ['ხ'] = "kh", + ['ჯ'] = "j", + ['ჰ'] = "h", + ['ẞ'] = "SS", + ['Ạ'] = "A", + ['ạ'] = "a", + ['Ả'] = "A", + ['ả'] = "a", + ['Ấ'] = "A", + ['ấ'] = "a", + ['Ầ'] = "A", + ['ầ'] = "a", + ['Ẩ'] = "A", + ['ẩ'] = "a", + ['Ẫ'] = "A", + ['ẫ'] = "a", + ['Ậ'] = "A", + ['ậ'] = "a", + ['Ắ'] = "A", + ['ắ'] = "a", + ['Ằ'] = "A", + ['ằ'] = "a", + ['Ẳ'] = "A", + ['ẳ'] = "a", + ['Ẵ'] = "A", + ['ẵ'] = "a", + ['Ặ'] = "A", + ['ặ'] = "a", + ['Ẹ'] = "E", + ['ẹ'] = "e", + ['Ẻ'] = "E", + ['ẻ'] = "e", + ['Ẽ'] = "E", + ['ẽ'] = "e", + ['Ế'] = "E", + ['ế'] = "e", + ['Ề'] = "E", + ['ề'] = "e", + ['Ể'] = "E", + ['ể'] = "e", + ['Ễ'] = "E", + ['ễ'] = "e", + ['Ệ'] = "E", + ['ệ'] = "e", + ['Ỉ'] = "I", + ['ỉ'] = "i", + ['Ị'] = "I", + ['ị'] = "i", + ['Ọ'] = "O", + ['ọ'] = "o", + ['Ỏ'] = "O", + ['ỏ'] = "o", + ['Ố'] = "O", + ['ố'] = "o", + ['Ồ'] = "O", + ['ồ'] = "o", + ['Ổ'] = "O", + ['ổ'] = "o", + ['Ỗ'] = "O", + ['ỗ'] = "o", + ['Ộ'] = "O", + ['ộ'] = "o", + ['Ớ'] = "O", + ['ớ'] = "o", + ['Ờ'] = "O", + ['ờ'] = "o", + ['Ở'] = "O", + ['ở'] = "o", + ['Ỡ'] = "O", + ['ỡ'] = "o", + ['Ợ'] = "O", + ['ợ'] = "o", + ['Ụ'] = "U", + ['ụ'] = "u", + ['Ủ'] = "U", + ['ủ'] = "u", + ['Ứ'] = "U", + ['ứ'] = "u", + ['Ừ'] = "U", + ['ừ'] = "u", + ['Ử'] = "U", + ['ử'] = "u", + ['Ữ'] = "U", + ['ữ'] = "u", + ['Ự'] = "U", + ['ự'] = "u", + ['Ỳ'] = "Y", + ['ỳ'] = "y", + ['Ỵ'] = "Y", + ['ỵ'] = "y", + ['Ỷ'] = "Y", + ['ỷ'] = "y", + ['Ỹ'] = "Y", + ['ỹ'] = "y", + ['‘'] = "\'", + ['’'] = "\'", + ['“'] = "\\\"", + ['”'] = "\\\"", + ['†'] = "+", + ['•'] = "*", + ['…'] = "...", + ['₠'] = "ecu", + ['₢'] = "cruzeiro", + ['₣'] = "french franc", + ['₤'] = "lira", + ['₥'] = "mill", + ['₦'] = "naira", + ['₧'] = "peseta", + ['₨'] = "rupee", + ['₩'] = "won", + ['₪'] = "new shequel", + ['₫'] = "dong", + ['€'] = "euro", + ['₭'] = "kip", + ['₮'] = "tugrik", + ['₯'] = "drachma", + ['₰'] = "penny", + ['₱'] = "peso", + ['₲'] = "guarani", + ['₳'] = "austral", + ['₴'] = "hryvnia", + ['₵'] = "cedi", + ['₹'] = "indian rupee", + ['₽'] = "russian ruble", + ['₿'] = "bitcoin", + ['℠'] = "sm", + ['™'] = "tm", + ['∂'] = "d", + ['∆'] = "delta", + ['∑'] = "sum", + ['∞'] = "infinity", + ['♥'] = "love", + ['元'] = "yuan", + ['円'] = "yen", + ['﷼'] = "rial" }; static StringExtensions() @@ -530,7 +742,7 @@ namespace Squidex.Infrastructure if (LowerCaseDiacritics.TryGetValue(character, out var replacement)) { - if (singleCharDiactric) + if (singleCharDiactric && replacement.Length == 2) { result.Append(replacement[0]); } From 98a92bb9d5b55166905d11b096a5e9da0d4f03b5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 4 May 2019 15:55:34 +0200 Subject: [PATCH 11/12] Fix for PR. --- .../angular/forms/control-errors.component.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.ts b/src/Squidex/app/framework/angular/forms/control-errors.component.ts index d58093df1..a481fcdee 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -60,7 +60,8 @@ export class ControlErrorsComponent extends StatefulComponent implements public ngOnDestroy() { super.ngOnDestroy(); - this.restoreOriginalMarkAsTouchedFunction(); + + this.unsetCustomMarkAsTouchedFunction(); } public ngOnChanges() { @@ -84,7 +85,7 @@ export class ControlErrorsComponent extends StatefulComponent implements if (this.control !== control) { this.unsubscribeAll(); - this.restoreOriginalMarkAsTouchedFunction(); + this.unsetCustomMarkAsTouchedFunction(); this.control = control; @@ -110,10 +111,10 @@ export class ControlErrorsComponent extends StatefulComponent implements this.createMessages(); } - private restoreOriginalMarkAsTouchedFunction() { - if (this.control && this.originalMarkAsTouched) { - this.control['markAsTouched'] = this.originalMarkAsTouched; - } + private unsetCustomMarkAsTouchedFunction() { + if (this.control && this.originalMarkAsTouched) { + this.control['markAsTouched'] = this.originalMarkAsTouched; + } } private createMessages() { From b98c1ed523b326597b0fb5f6bbf9a454648bfa31 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 6 May 2019 15:14:16 +0200 Subject: [PATCH 12/12] Casing fix. --- .../Contents/NamedContentData.cs | 4 ++-- .../Json/Newtonsoft/ConverterContractResolver.cs | 11 ++--------- .../Json/Newtonsoft/ConverterContractResolverTests.cs | 11 +++++++++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs index fd298d5ef..d6afcd95f 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs @@ -13,12 +13,12 @@ namespace Squidex.Domain.Apps.Core.Contents public sealed class NamedContentData : ContentData, IEquatable { public NamedContentData() - : base(StringComparer.OrdinalIgnoreCase) + : base(StringComparer.Ordinal) { } public NamedContentData(int capacity) - : base(capacity, StringComparer.OrdinalIgnoreCase) + : base(capacity, StringComparer.Ordinal) { } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/ConverterContractResolver.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/ConverterContractResolver.cs index 53b203da3..a42d030f5 100644 --- a/src/Squidex.Infrastructure/Json/Newtonsoft/ConverterContractResolver.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/ConverterContractResolver.cs @@ -20,6 +20,8 @@ namespace Squidex.Infrastructure.Json.Newtonsoft public ConverterContractResolver(params JsonConverter[] converters) { + NamingStrategy = new CamelCaseNamingStrategy(false, true); + this.converters = converters; foreach (var converter in converters) @@ -34,15 +36,6 @@ namespace Squidex.Infrastructure.Json.Newtonsoft } } - protected override JsonDictionaryContract CreateDictionaryContract(Type objectType) - { - var contract = base.CreateDictionaryContract(objectType); - - contract.DictionaryKeyResolver = propertyName => propertyName; - - return contract; - } - protected override JsonConverter ResolveContractConverter(Type objectType) { var result = base.ResolveContractConverter(objectType); diff --git a/tests/Squidex.Infrastructure.Tests/Json/Newtonsoft/ConverterContractResolverTests.cs b/tests/Squidex.Infrastructure.Tests/Json/Newtonsoft/ConverterContractResolverTests.cs index aee6e6966..a3416165d 100644 --- a/tests/Squidex.Infrastructure.Tests/Json/Newtonsoft/ConverterContractResolverTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Json/Newtonsoft/ConverterContractResolverTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using Newtonsoft.Json; using NodaTime; using Squidex.Infrastructure.TestHelpers; @@ -81,5 +82,15 @@ namespace Squidex.Infrastructure.Json.Newtonsoft Assert.Equal(value, serialized); } + + [Fact] + public void Should_serialize_and_deserialize_dictionary() + { + var value = new Dictionary { ["Description"] = "value" }; + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } } }