Browse Source

Merge branch 'release/6.x'

# Conflicts:
#	CHANGELOG.md
#	backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsCacheGrain.cs
#	backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesCacheGrain.cs
#	backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasCacheGrain.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsCacheGrainTests.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesCacheGrainTests.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasCacheGrainTests.cs
pull/899/head
Sebastian 3 years ago
parent
commit
07eccc69ff
  1. 1
      backend/i18n/source/backend_en.json
  2. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  3. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SingletonExtensions.cs
  4. 2
      backend/src/Squidex.Infrastructure/Tasks/Scheduler.cs
  5. 4
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs
  6. 2
      backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs
  7. 3
      backend/src/Squidex/Config/Domain/BackupsServices.cs
  8. 12
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs
  9. 25
      backend/tests/Squidex.Infrastructure.Tests/Tasks/SchedulerTests.cs
  10. 2
      frontend/src/app/features/content/shared/list/content.component.html
  11. 2
      frontend/src/app/features/content/shared/references/content-creator.component.ts
  12. 7
      frontend/src/app/features/schemas/pages/schema/fields/types/array-validation.component.ts
  13. 2
      frontend/src/app/features/schemas/pages/schema/fields/types/references-validation.component.html
  14. 2
      frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.html
  15. 4
      frontend/src/app/features/settings/pages/workflows/workflow.component.html
  16. 2
      frontend/src/app/shared/components/forms/rich-editor.component.ts
  17. 2
      frontend/src/app/shared/components/references/content-selector.component.ts
  18. 11
      frontend/src/app/shared/state/schema-tag-source.ts

1
backend/i18n/source/backend_en.json

@ -142,6 +142,7 @@
"contents.singletonNotChangeable": "Singleton content cannot be updated.", "contents.singletonNotChangeable": "Singleton content cannot be updated.",
"contents.singletonNotCreatable": "Singleton content cannot be created.", "contents.singletonNotCreatable": "Singleton content cannot be created.",
"contents.singletonNotDeletable": "Singleton content cannot be deleted.", "contents.singletonNotDeletable": "Singleton content cannot be deleted.",
"contents.componentNotCreatable": "Component content cannot be created.",
"contents.statusNotValid": "Status is not defined in the workflow.", "contents.statusNotValid": "Status is not defined in the workflow.",
"contents.statusTransitionNotAllowed": "Cannot change status from {oldStatus} to {newStatus}.", "contents.statusTransitionNotAllowed": "Cannot change status from {oldStatus} to {newStatus}.",
"contents.validation.aspectRatio": "Must have aspect ratio {width}:{height}.", "contents.validation.aspectRatio": "Must have aspect ratio {width}:{height}.",

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

@ -228,6 +228,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
private async Task CreateCore(CreateContent c, ContentOperation operation) private async Task CreateCore(CreateContent c, ContentOperation operation)
{ {
operation.MustNotCreateComponent();
operation.MustNotCreateSingleton(); operation.MustNotCreateSingleton();
operation.MustNotCreateForUnpublishedSchema(); operation.MustNotCreateForUnpublishedSchema();
operation.MustHaveData(c.Data); operation.MustHaveData(c.Data);

8
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SingletonExtensions.cs

@ -22,6 +22,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
} }
} }
public static void MustNotCreateComponent(this ContentOperation operation)
{
if (operation.SchemaDef.Type == SchemaType.Component)
{
throw new DomainException(T.Get("contents.componentNotCreatable"));
}
}
public static void MustNotCreateSingleton(this ContentOperation operation) public static void MustNotCreateSingleton(this ContentOperation operation)
{ {
if (operation.SchemaDef.Type == SchemaType.Singleton && operation.CommandId != operation.Schema.Id) if (operation.SchemaDef.Type == SchemaType.Singleton && operation.CommandId != operation.Schema.Id)

2
backend/src/Squidex.Infrastructure/Tasks/Scheduler.cs

@ -113,6 +113,8 @@ namespace Squidex.Infrastructure.Tasks
} }
finally finally
{ {
semaphore.Release();
if (Interlocked.Decrement(ref pendingTasks) <= 1) if (Interlocked.Decrement(ref pendingTasks) <= 1)
{ {
tcs.TrySetResult(true); tcs.TrySetResult(true);

4
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs

@ -154,12 +154,12 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
AddSelfLink(resources.Url<SchemasController>(x => nameof(x.GetSchema), values)); AddSelfLink(resources.Url<SchemasController>(x => nameof(x.GetSchema), values));
if (resources.CanReadContent(Name)) if (resources.CanReadContent(Name) && Type == SchemaType.Default)
{ {
AddGetLink("contents", resources.Url<ContentsController>(x => nameof(x.GetContents), values)); AddGetLink("contents", resources.Url<ContentsController>(x => nameof(x.GetContents), values));
} }
if (resources.CanCreateContent(Name)) if (resources.CanCreateContent(Name) && Type == SchemaType.Default)
{ {
AddPostLink("contents/create", resources.Url<ContentsController>(x => nameof(x.PostContent), values)); AddPostLink("contents/create", resources.Url<ContentsController>(x => nameof(x.PostContent), values));
AddPostLink("contents/create/publish", resources.Url<ContentsController>(x => nameof(x.PostContent), values) + "?publish=true"); AddPostLink("contents/create/publish", resources.Url<ContentsController>(x => nameof(x.PostContent), values) + "?publish=true");

2
backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs

@ -24,7 +24,7 @@ namespace Squidex.Areas.Api.Controllers.Translations.Models
public static TranslationDto FromDomain(TranslationResult translation) public static TranslationDto FromDomain(TranslationResult translation)
{ {
return SimpleMapper.Map(translation, new TranslationDto()); return SimpleMapper.Map(translation, new TranslationDto { Result = translation.Code });
} }
} }
} }

3
backend/src/Squidex/Config/Domain/BackupsServices.cs

@ -22,6 +22,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<TempFolderBackupArchiveLocation>() services.AddSingletonAs<TempFolderBackupArchiveLocation>()
.As<IBackupArchiveLocation>(); .As<IBackupArchiveLocation>();
services.AddSingletonAs<DefaultBackupHandlerFactory>()
.As<IBackupHandlerFactory>();
services.AddSingletonAs<DefaultBackupArchiveStore>() services.AddSingletonAs<DefaultBackupArchiveStore>()
.As<IBackupArchiveStore>(); .As<IBackupArchiveStore>();

12
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs

@ -34,6 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
private readonly ISchemaEntity normalUnpublishedSchema; private readonly ISchemaEntity normalUnpublishedSchema;
private readonly ISchemaEntity singletonSchema; private readonly ISchemaEntity singletonSchema;
private readonly ISchemaEntity singletonUnpublishedSchema; private readonly ISchemaEntity singletonUnpublishedSchema;
private readonly ISchemaEntity componentSchema;
private readonly RefToken actor = RefToken.User("123"); private readonly RefToken actor = RefToken.User("123");
public GuardContentTests() public GuardContentTests()
@ -49,6 +50,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
singletonSchema = singletonSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, type: SchemaType.Singleton).Publish()); Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, type: SchemaType.Singleton).Publish());
componentSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, type: SchemaType.Component).Publish());
} }
[Fact] [Fact]
@ -91,6 +95,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards
Assert.Throws<DomainException>(() => operation.MustNotCreateSingleton()); Assert.Throws<DomainException>(() => operation.MustNotCreateSingleton());
} }
[Fact]
public void Should_throw_exception_if_creating_component_content()
{
var operation = Operation(CreateContent(Status.Draft), componentSchema);
Assert.Throws<DomainException>(() => operation.MustNotCreateComponent());
}
[Fact] [Fact]
public void Should_not_throw_exception_if_creating_singleton_content_with_schema_id() public void Should_not_throw_exception_if_creating_singleton_content_with_schema_id()
{ {

25
backend/tests/Squidex.Infrastructure.Tests/Tasks/SchedulerTests.cs

@ -25,6 +25,21 @@ namespace Squidex.Infrastructure.Tasks
Assert.Equal(new[] { 1 }, results.ToArray()); Assert.Equal(new[] { 1 }, results.ToArray());
} }
[Fact]
public async Task Should_schedule_lot_of_tasks_with_limited_concurrency()
{
var limited = new Scheduler(1);
for (var i = 1; i <= 10; i++)
{
Schedule(i, limited);
}
await limited.CompleteAsync();
Assert.Equal(Enumerable.Range(1, 10).ToArray(), results.OrderBy(x => x).ToArray());
}
[Fact] [Fact]
public async Task Should_schedule_multiple_tasks() public async Task Should_schedule_multiple_tasks()
{ {
@ -83,5 +98,15 @@ namespace Squidex.Infrastructure.Tasks
results.Add(value); results.Add(value);
}); });
} }
private void Schedule(int value, Scheduler target)
{
target.Schedule(async _ =>
{
await Task.Delay(1);
results.Add(value);
});
}
} }
} }

2
frontend/src/app/features/content/shared/list/content.component.html

@ -43,7 +43,7 @@
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete" <a class="dropdown-item dropdown-item-delete" [class.disabled]="!content.canDelete"
(sqxConfirmClick)="delete.emit()" (sqxConfirmClick)="delete.emit()"
confirmTitle="i18n:contents.deleteConfirmTitle" confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteConfirmText" confirmText="i18n:contents.deleteConfirmText"

2
frontend/src/app/features/content/shared/references/content-creator.component.ts

@ -56,7 +56,7 @@ export class ContentCreatorComponent extends ResourceOwner implements OnInit {
this.schemas = this.schemasState.snapshot.schemas.filter(x => x.canContentsCreate); this.schemas = this.schemasState.snapshot.schemas.filter(x => x.canContentsCreate);
if (this.schemaIds && this.schemaIds.length > 0) { if (this.schemaIds && this.schemaIds.length > 0) {
this.schemas = this.schemas.filter(x => this.schemaIds!.indexOf(x.id) >= 0); this.schemas = this.schemas.filter(x => x.type === 'Default' && this.schemaIds!.indexOf(x.id) >= 0);
} }
const selectedSchema = this.schemas.find(x => x.name === this.schemaName) || this.schemas[0]; const selectedSchema = this.schemas.find(x => x.name === this.schemaName) || this.schemas[0];

7
frontend/src/app/features/schemas/pages/schema/fields/types/array-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { ArrayFieldPropertiesDto, FieldDto, SchemaTagSource } from '@app/shared'; import { ArrayFieldPropertiesDto, FieldDto } from '@app/shared';
@Component({ @Component({
selector: 'sqx-array-validation[field][fieldForm][properties]', selector: 'sqx-array-validation[field][fieldForm][properties]',
@ -23,9 +23,4 @@ export class ArrayValidationComponent {
@Input() @Input()
public properties!: ArrayFieldPropertiesDto; public properties!: ArrayFieldPropertiesDto;
constructor(
public readonly schemasSource: SchemaTagSource,
) {
}
} }

2
frontend/src/app/features/schemas/pages/schema/fields/types/references-validation.component.html

@ -4,7 +4,7 @@
<div class="col-9"> <div class="col-9">
<sqx-tag-editor placeholder="{{ 'common.tagAddSchema' | sqxTranslate }}" formControlName="schemaIds" <sqx-tag-editor placeholder="{{ 'common.tagAddSchema' | sqxTranslate }}" formControlName="schemaIds"
[converter]="(schemasSource.converter | async)!" [suggestions]="(schemasSource.converter | async)?.suggestions"> [converter]="(schemasSource.normalConverter | async)!" [suggestions]="(schemasSource.normalConverter | async)?.suggestions">
</sqx-tag-editor> </sqx-tag-editor>
</div> </div>
</div> </div>

2
frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.html

@ -91,7 +91,7 @@
<div class="col-9"> <div class="col-9">
<sqx-tag-editor placeholder="{{ 'common.tagAddSchema' | sqxTranslate }}" formControlName="schemaIds" <sqx-tag-editor placeholder="{{ 'common.tagAddSchema' | sqxTranslate }}" formControlName="schemaIds"
[converter]="(schemasSource.converter | async)!" [suggestions]="(schemasSource.converter | async)?.suggestions"> [converter]="(schemasSource.normalConverter | async)!" [suggestions]="(schemasSource.normalConverter | async)?.suggestions">
</sqx-tag-editor> </sqx-tag-editor>
</div> </div>
</div> </div>

4
frontend/src/app/features/settings/pages/workflows/workflow.component.html

@ -4,7 +4,7 @@
<span class="workflow-name">{{workflow.displayName}}</span> <span class="workflow-name">{{workflow.displayName}}</span>
</div> </div>
<div class="col col-tags"> <div class="col col-tags">
<sqx-tag-editor [converter]="(schemasSource.converter | async)!" [ngModel]="workflow.schemaIds" <sqx-tag-editor [converter]="(schemasSource.normalConverter | async)!" [ngModel]="workflow.schemaIds"
[styleBlank]="true" [styleBlank]="true"
[singleLine]="true" [singleLine]="true"
[readonly]="true"> [readonly]="true">
@ -81,7 +81,7 @@
[disabled]="!isEditable" [disabled]="!isEditable"
[ngModel]="workflow.schemaIds" [ngModel]="workflow.schemaIds"
(ngModelChange)="changeSchemaIds($event)" (ngModelChange)="changeSchemaIds($event)"
[suggestions]="(schemasSource.converter | async)?.suggestions"> [suggestions]="(schemasSource.normalConverter | async)?.suggestions">
</sqx-tag-editor> </sqx-tag-editor>
<sqx-form-hint> <sqx-form-hint>

2
frontend/src/app/shared/components/forms/rich-editor.component.ts

@ -119,7 +119,7 @@ export class RichEditorComponent extends StatefulControlComponent<{}, string> im
images_upload_handler: (blob: any, success: (url: string) => void, failure: (message: string) => void) => { images_upload_handler: (blob: any, success: (url: string) => void, failure: (message: string) => void) => {
const file = new File([blob.blob()], blob.filename(), { lastModified: new Date().getTime() }); const file = new File([blob.blob()], blob.filename(), { lastModified: new Date().getTime() });
self.assetUploader.uploadFile(file) self.assetUploader.uploadFile(file, undefined, this.folderId)
.subscribe({ .subscribe({
next: asset => { next: asset => {
if (Types.is(asset, AssetDto)) { if (Types.is(asset, AssetDto)) {

2
frontend/src/app/shared/components/references/content-selector.component.ts

@ -81,7 +81,7 @@ export class ContentSelectorComponent extends ResourceOwner implements OnInit {
this.schemas = this.schemasState.snapshot.schemas.filter(x => x.canReadContents); this.schemas = this.schemasState.snapshot.schemas.filter(x => x.canReadContents);
if (this.schemaIds && this.schemaIds.length > 0) { if (this.schemaIds && this.schemaIds.length > 0) {
this.schemas = this.schemas.filter(x => x.canReadContents && this.schemaIds!.includes(x.id)); this.schemas = this.schemas.filter(x => x.type === 'Default' && x.canReadContents && this.schemaIds!.includes(x.id));
} }
this.selectSchema(this.schemas[0]); this.selectSchema(this.schemas[0]);

11
frontend/src/app/shared/state/schema-tag-source.ts

@ -16,7 +16,12 @@ class SchemaConverter implements TagConverter {
constructor( constructor(
private readonly schemas: ReadonlyArray<SchemaDto>, private readonly schemas: ReadonlyArray<SchemaDto>,
normalOnly: boolean,
) { ) {
if (normalOnly) {
schemas = schemas.filter(x => x.type === 'Default');
}
this.suggestions = schemas.map(x => new TagValue(x.id, x.name, x.id)); this.suggestions = schemas.map(x => new TagValue(x.id, x.name, x.id));
} }
@ -45,7 +50,11 @@ class SchemaConverter implements TagConverter {
export class SchemaTagSource { export class SchemaTagSource {
public converter = public converter =
this.schemasState.schemas.pipe( this.schemasState.schemas.pipe(
map(x => new SchemaConverter(x))); map(x => new SchemaConverter(x, false)));
public normalConverter =
this.schemasState.schemas.pipe(
map(x => new SchemaConverter(x, true)));
constructor( constructor(
readonly schemasState: SchemasState, readonly schemasState: SchemasState,

Loading…
Cancel
Save