Browse Source

Model classes.

pull/297/head
Sebastian 8 years ago
parent
commit
078fdc691f
  1. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs
  2. 13
      src/Squidex/app/features/administration/services/event-consumers.service.ts
  3. 20
      src/Squidex/app/features/administration/services/users.service.ts
  4. 30
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  5. 4
      src/Squidex/app/features/administration/state/users.state.ts
  6. 6
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  7. 4
      src/Squidex/app/features/content/shared/content-item.component.ts
  8. 12
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  9. 21
      src/Squidex/app/framework/state.ts
  10. 15
      src/Squidex/app/shared/services/app-clients.service.ts
  11. 15
      src/Squidex/app/shared/services/app-contributors.service.ts
  12. 15
      src/Squidex/app/shared/services/app-languages.service.ts
  13. 15
      src/Squidex/app/shared/services/app-patterns.service.ts
  14. 10
      src/Squidex/app/shared/services/apps.service.ts
  15. 57
      src/Squidex/app/shared/services/assets.service.ts
  16. 10
      src/Squidex/app/shared/services/backups.service.ts
  17. 24
      src/Squidex/app/shared/services/contents.service.spec.ts
  18. 46
      src/Squidex/app/shared/services/contents.service.ts
  19. 15
      src/Squidex/app/shared/services/plans.service.ts
  20. 32
      src/Squidex/app/shared/services/rules.service.ts
  21. 81
      src/Squidex/app/shared/services/schemas.fields.spec.ts
  22. 312
      src/Squidex/app/shared/services/schemas.service.spec.ts
  23. 664
      src/Squidex/app/shared/services/schemas.service.ts
  24. 2
      src/Squidex/app/shared/state/clients.state.ts
  25. 101
      src/Squidex/app/shared/state/contents.state.ts
  26. 44
      src/Squidex/app/shared/state/rules.state.ts
  27. 46
      src/Squidex/app/shared/state/schemas.state.spec.ts
  28. 382
      src/Squidex/app/shared/state/schemas.state.ts

2
src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
Validate.It(() => "Cannot add a new field.", error => Validate.It(() => "Cannot add a new field.", error =>
{ {
if (!command.Partitioning.IsValidPartitioning()) if (command.ParentFieldId == null && !command.Partitioning.IsValidPartitioning())
{ {
error(new ValidationError("Partitioning is not valid.", nameof(command.Partitioning))); error(new ValidationError("Partitioning is not valid.", nameof(command.Partitioning)));
} }

13
src/Squidex/app/features/administration/services/event-consumers.service.ts

@ -11,9 +11,13 @@ import { Observable } from 'rxjs';
import '@app/framework/angular/http/http-extensions'; import '@app/framework/angular/http/http-extensions';
import { ApiUrlConfig, HTTP } from '@app/shared'; import {
ApiUrlConfig,
HTTP,
Model
} from '@app/shared';
export class EventConsumerDto { export class EventConsumerDto extends Model {
constructor( constructor(
public readonly name: string, public readonly name: string,
public readonly isStopped: boolean, public readonly isStopped: boolean,
@ -21,6 +25,11 @@ export class EventConsumerDto {
public readonly error: string, public readonly error: string,
public readonly position: string public readonly position: string
) { ) {
super();
}
public with(value: Partial<EventConsumerDto>): EventConsumerDto {
return this.clone(value);
} }
} }

20
src/Squidex/app/features/administration/services/users.service.ts

@ -11,23 +11,37 @@ import { Observable } from 'rxjs';
import '@app/framework/angular/http/http-extensions'; import '@app/framework/angular/http/http-extensions';
import { ApiUrlConfig, HTTP } from '@app/shared'; import {
ApiUrlConfig,
HTTP,
Model
} from '@app/shared';
export class UsersDto { export class UsersDto extends Model {
constructor( constructor(
public readonly total: number, public readonly total: number,
public readonly items: UserDto[] public readonly items: UserDto[]
) { ) {
super();
}
public with(value: Partial<UsersDto>): UsersDto {
return this.clone(value);
} }
} }
export class UserDto { export class UserDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly email: string, public readonly email: string,
public readonly displayName: string, public readonly displayName: string,
public readonly isLocked: boolean public readonly isLocked: boolean
) { ) {
super();
}
public with(value: Partial<UserDto>): UserDto {
return this.clone(value);
} }
} }

30
src/Squidex/app/features/administration/state/event-consumers.state.ts

@ -67,41 +67,41 @@ export class EventConsumersState extends State<Snapshot> {
}); });
} }
public start(es: EventConsumerDto): Observable<any> { public start(eventConsumer: EventConsumerDto): Observable<any> {
return this.eventConsumersService.putStart(es.name) return this.eventConsumersService.putStart(eventConsumer.name)
.do(() => { .do(() => {
this.replaceEventConsumer(setStopped(es, false)); this.replaceEventConsumer(setStopped(eventConsumer, false));
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public stop(es: EventConsumerDto): Observable<any> { public stop(eventConsumer: EventConsumerDto): Observable<any> {
return this.eventConsumersService.putStop(es.name) return this.eventConsumersService.putStop(eventConsumer.name)
.do(() => { .do(() => {
this.replaceEventConsumer(setStopped(es, true)); this.replaceEventConsumer(setStopped(eventConsumer, true));
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public reset(es: EventConsumerDto): Observable<any> { public reset(eventConsumer: EventConsumerDto): Observable<any> {
return this.eventConsumersService.putReset(es.name) return this.eventConsumersService.putReset(eventConsumer.name)
.do(() => { .do(() => {
this.replaceEventConsumer(reset(es)); this.replaceEventConsumer(reset(eventConsumer));
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
private replaceEventConsumer(es: EventConsumerDto) { private replaceEventConsumer(eventConsumer: EventConsumerDto) {
this.next(s => { this.next(s => {
const eventConsumers = s.eventConsumers.replaceBy('name', es); const eventConsumers = s.eventConsumers.replaceBy('name', eventConsumer);
return { ...s, eventConsumers }; return { ...s, eventConsumers };
}); });
} }
} }
const setStopped = (es: EventConsumerDto, isStoped: boolean) => const setStopped = (eventConsumer: EventConsumerDto, isStopped: boolean) =>
new EventConsumerDto(es.name, isStoped, false, es.error, es.position); eventConsumer.with({ isStopped });
const reset = (es: EventConsumerDto) => const reset = (eventConsumer: EventConsumerDto) =>
new EventConsumerDto(es.name, es.isStopped, true, es.error, es.position); eventConsumer.with({ isResetting: true });

4
src/Squidex/app/features/administration/state/users.state.ts

@ -247,7 +247,7 @@ export class UsersState extends State<Snapshot> {
const update = (user: UserDto, request: UpdateUserDto) => const update = (user: UserDto, request: UpdateUserDto) =>
new UserDto(user.id, request.email, request.displayName, user.isLocked); user.with(request);
const setLocked = (user: UserDto, isLocked: boolean) => const setLocked = (user: UserDto, isLocked: boolean) =>
new UserDto(user.id, user.email, user.displayName, isLocked); user.with({ isLocked });

6
src/Squidex/app/features/content/pages/content/content-field.component.ts

@ -10,9 +10,9 @@ import { AbstractControl, FormGroup } from '@angular/forms';
import { import {
AppLanguageDto, AppLanguageDto,
FieldDto,
fieldInvariant, fieldInvariant,
ImmutableArray ImmutableArray,
RootFieldDto
} from '@app/shared'; } from '@app/shared';
@Component({ @Component({
@ -22,7 +22,7 @@ import {
}) })
export class ContentFieldComponent implements OnChanges { export class ContentFieldComponent implements OnChanges {
@Input() @Input()
public field: FieldDto; public field: RootFieldDto;
@Input() @Input()
public fieldForm: FormGroup; public fieldForm: FormGroup;

4
src/Squidex/app/features/content/shared/content-item.component.ts

@ -12,10 +12,10 @@ import {
ContentDto, ContentDto,
ContentsState, ContentsState,
fadeAnimation, fadeAnimation,
FieldDto,
fieldInvariant, fieldInvariant,
ModalView, ModalView,
PatchContentForm, PatchContentForm,
RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto,
Types Types
} from '@app/shared'; } from '@app/shared';
@ -136,7 +136,7 @@ export class ContentItemComponent implements OnChanges {
} }
} }
private getRawValue(field: FieldDto): any { private getRawValue(field: RootFieldDto): any {
const contentField = this.content.dataDraft[field.name]; const contentField = this.content.dataDraft[field.name];
if (contentField) { if (contentField) {

12
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -9,15 +9,17 @@ import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { import {
AnyFieldDto,
AppPatternDto, AppPatternDto,
createProperties, createProperties,
EditFieldForm, EditFieldForm,
fadeAnimation, fadeAnimation,
FieldDto,
ImmutableArray, ImmutableArray,
ModalView, ModalView,
RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto,
SchemasState, SchemasState,
Types,
UpdateFieldDto UpdateFieldDto
} from '@app/shared'; } from '@app/shared';
@ -31,7 +33,7 @@ import {
}) })
export class FieldComponent implements OnInit { export class FieldComponent implements OnInit {
@Input() @Input()
public field: FieldDto; public field: AnyFieldDto;
@Input() @Input()
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
@ -56,7 +58,7 @@ export class FieldComponent implements OnInit {
this.editForm = new EditFieldForm(this.formBuilder); this.editForm = new EditFieldForm(this.formBuilder);
this.editForm.load(this.field.properties); this.editForm.load(this.field.properties);
if (this.field.isLocked) { if (Types.is(this.field, RootFieldDto) && this.field.isLocked) {
this.editForm.form.disable(); this.editForm.form.disable();
} }
} }
@ -95,7 +97,9 @@ export class FieldComponent implements OnInit {
} }
public lockField() { public lockField() {
this.schemasState.lockField(this.schema, this.field).onErrorResumeNext().subscribe(); if (Types.is(this.field, RootFieldDto)) {
this.schemasState.lockField(this.schema, this.field).onErrorResumeNext().subscribe();
}
} }
public save() { public save() {

21
src/Squidex/app/framework/state.ts

@ -78,6 +78,27 @@ export class Form<T extends AbstractControl> {
} }
} }
export class Model {
protected onCreated() {
return;
}
protected clone(update: ((v: any) => object) | object): any {
let values: object;
if (Types.isFunction(update)) {
values = update(<any>this);
} else {
values = update;
}
const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this, values);
clone.onCreated();
return clone;
}
}
export class State<T extends {}> { export class State<T extends {}> {
private readonly state: BehaviorSubject<T>; private readonly state: BehaviorSubject<T>;
private readonly initialState: T; private readonly initialState: T;

15
src/Squidex/app/shared/services/app-clients.service.ts

@ -15,25 +15,36 @@ import {
AnalyticsService, AnalyticsService,
ApiUrlConfig, ApiUrlConfig,
HTTP, HTTP,
Model,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export class AppClientsDto { export class AppClientsDto extends Model {
constructor( constructor(
public readonly clients: AppClientDto[], public readonly clients: AppClientDto[],
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public with(value: Partial<AppClientsDto>): AppClientsDto {
return this.clone(value);
} }
} }
export class AppClientDto { export class AppClientDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly name: string, public readonly name: string,
public readonly secret: string, public readonly secret: string,
public readonly permission: string public readonly permission: string
) { ) {
super();
}
public with(value: Partial<AppClientDto>): AppClientDto {
return this.clone(value);
} }
} }

15
src/Squidex/app/shared/services/app-contributors.service.ts

@ -15,24 +15,35 @@ import {
AnalyticsService, AnalyticsService,
ApiUrlConfig, ApiUrlConfig,
HTTP, HTTP,
Model,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export class AppContributorsDto { export class AppContributorsDto extends Model {
constructor( constructor(
public readonly contributors: AppContributorDto[], public readonly contributors: AppContributorDto[],
public readonly maxContributors: number, public readonly maxContributors: number,
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public with(value: Partial<AppContributorsDto>): AppContributorsDto {
return this.clone(value);
} }
} }
export class AppContributorDto { export class AppContributorDto extends Model {
constructor( constructor(
public readonly contributorId: string, public readonly contributorId: string,
public readonly permission: string public readonly permission: string
) { ) {
super();
}
public with(value: Partial<AppContributorDto>): AppContributorDto {
return this.clone(value);
} }
} }

15
src/Squidex/app/shared/services/app-languages.service.ts

@ -15,19 +15,25 @@ import {
AnalyticsService, AnalyticsService,
ApiUrlConfig, ApiUrlConfig,
HTTP, HTTP,
Model,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export class AppLanguagesDto { export class AppLanguagesDto extends Model {
constructor( constructor(
public readonly languages: AppLanguageDto[], public readonly languages: AppLanguageDto[],
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public with(value: Partial<AppLanguagesDto>): AppLanguagesDto {
return this.clone(value);
} }
} }
export class AppLanguageDto { export class AppLanguageDto extends Model {
constructor( constructor(
public readonly iso2Code: string, public readonly iso2Code: string,
public readonly englishName: string, public readonly englishName: string,
@ -35,6 +41,11 @@ export class AppLanguageDto {
public readonly isOptional: boolean, public readonly isOptional: boolean,
public readonly fallback: string[] public readonly fallback: string[]
) { ) {
super();
}
public with(value: Partial<AppLanguageDto>): AppLanguageDto {
return this.clone(value);
} }
} }

15
src/Squidex/app/shared/services/app-patterns.service.ts

@ -14,25 +14,36 @@ import '@app/framework/angular/http/http-extensions';
import { import {
ApiUrlConfig, ApiUrlConfig,
HTTP, HTTP,
Model,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export class AppPatternsDto { export class AppPatternsDto extends Model {
constructor( constructor(
public readonly patterns: AppPatternDto[], public readonly patterns: AppPatternDto[],
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public with(value: Partial<AppPatternsDto>): AppPatternsDto {
return this.clone(value);
} }
} }
export class AppPatternDto { export class AppPatternDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly name: string, public readonly name: string,
public readonly pattern: string, public readonly pattern: string,
public readonly message: string public readonly message: string
) { ) {
super();
}
public with(value: Partial<AppPatternDto>): AppPatternDto {
return this.clone(value);
} }
} }

10
src/Squidex/app/shared/services/apps.service.ts

@ -15,10 +15,11 @@ import {
AnalyticsService, AnalyticsService,
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP HTTP,
Model
} from '@app/framework'; } from '@app/framework';
export class AppDto { export class AppDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly name: string, public readonly name: string,
@ -28,6 +29,11 @@ export class AppDto {
public readonly planName: string, public readonly planName: string,
public readonly planUpgrade: string public readonly planUpgrade: string
) { ) {
super();
}
public with(value: Partial<AppDto>): AppDto {
return this.clone(value);
} }
} }

57
src/Squidex/app/shared/services/assets.service.ts

@ -14,20 +14,26 @@ import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP, HTTP,
Model,
Types, Types,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export class AssetsDto { export class AssetsDto extends Model {
constructor( constructor(
public readonly total: number, public readonly total: number,
public readonly items: AssetDto[] public readonly items: AssetDto[]
) { ) {
super();
}
public with(value: Partial<AssetsDto>): AssetsDto {
return this.clone(value);
} }
} }
export class AssetDto { export class AssetDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly createdBy: string, public readonly createdBy: string,
@ -45,40 +51,29 @@ export class AssetDto {
public readonly url: string, public readonly url: string,
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public with(value: Partial<AssetDto>): AssetDto {
return this.clone(value);
} }
public update(update: AssetReplacedDto, user: string, version: Version, now?: DateTime): AssetDto { public update(update: AssetReplacedDto, user: string, version: Version, now?: DateTime): AssetDto {
return new AssetDto( return this.with({
this.id, ...update,
this.createdBy, user, lastModified: now || DateTime.now(),
this.created, now || DateTime.now(), lastModifiedBy: user,
this.fileName, version
this.fileType, });
update.fileSize,
update.fileVersion,
update.mimeType,
update.isImage,
update.pixelWidth,
update.pixelHeight,
this.url,
version);
} }
public rename(name: string, user: string, version: Version, now?: DateTime): AssetDto { public rename(fileName: string, user: string, version: Version, now?: DateTime): AssetDto {
return new AssetDto( return this.with({
this.id, fileName,
this.createdBy, user, lastModified: now || DateTime.now(),
this.created, now || DateTime.now(), lastModifiedBy: user,
name, version
this.fileType, });
this.fileSize,
this.fileVersion,
this.mimeType,
this.isImage,
this.pixelWidth,
this.pixelHeight,
this.url,
version);
} }
} }

10
src/Squidex/app/shared/services/backups.service.ts

@ -14,10 +14,11 @@ import '@app/framework/angular/http/http-extensions';
import { import {
AnalyticsService, AnalyticsService,
ApiUrlConfig, ApiUrlConfig,
DateTime DateTime,
Model
} from '@app/framework'; } from '@app/framework';
export class BackupDto { export class BackupDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly started: DateTime, public readonly started: DateTime,
@ -26,6 +27,11 @@ export class BackupDto {
public readonly handledAssets: number, public readonly handledAssets: number,
public readonly isFailed: boolean public readonly isFailed: boolean
) { ) {
super();
}
public with(value: Partial<BackupDto>): BackupDto {
return this.clone(value);
} }
} }

24
src/Squidex/app/shared/services/contents.service.spec.ts

@ -89,17 +89,17 @@ describe('ContentsService', () => {
expect(contents).toEqual( expect(contents).toEqual(
new ContentsDto(10, [ new ContentsDto(10, [
new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1', new ContentDto('id1', 'Published',
DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2016-12-12T10:10'), 'Created1',
DateTime.parseISO_UTC('2017-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), 'LastModifiedBy1',
new ScheduleDto('Draft', 'Scheduler1', DateTime.parseISO_UTC('2018-12-12T10:10')), new ScheduleDto('Draft', 'Scheduler1', DateTime.parseISO_UTC('2018-12-12T10:10')),
true, true,
{}, {},
{}, {},
new Version('11')), new Version('11')),
new ContentDto('id2', 'Published', 'Created2', 'LastModifiedBy2', new ContentDto('id2', 'Published',
DateTime.parseISO_UTC('2016-10-12T10:10'), DateTime.parseISO_UTC('2016-10-12T10:10'), 'Created2',
DateTime.parseISO_UTC('2017-10-12T10:10'), DateTime.parseISO_UTC('2017-10-12T10:10'), 'LastModifiedBy2',
null, null,
false, false,
{}, {},
@ -183,9 +183,9 @@ describe('ContentsService', () => {
}); });
expect(content).toEqual( expect(content).toEqual(
new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1', new ContentDto('id1', 'Published',
DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2016-12-12T10:10'), 'Created1',
DateTime.parseISO_UTC('2017-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), 'LastModifiedBy1',
new ScheduleDto('Draft', 'Scheduler1', DateTime.parseISO_UTC('2018-12-12T10:10')), new ScheduleDto('Draft', 'Scheduler1', DateTime.parseISO_UTC('2018-12-12T10:10')),
true, true,
{}, {},
@ -224,9 +224,9 @@ describe('ContentsService', () => {
}); });
expect(content).toEqual( expect(content).toEqual(
new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1', new ContentDto('id1', 'Published',
DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2016-12-12T10:10'), 'Created1',
DateTime.parseISO_UTC('2017-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), 'LastModifiedBy1',
null, null,
true, true,
null, null,

46
src/Squidex/app/shared/services/contents.service.ts

@ -16,42 +16,58 @@ import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP, HTTP,
Model,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export class ContentsDto { export class ContentsDto extends Model {
constructor( constructor(
public readonly total: number, public readonly total: number,
public readonly items: ContentDto[] public readonly items: ContentDto[]
) { ) {
super();
} }
}
public with(value: Partial<ContentsDto>): ContentsDto {
return this.clone(value);
}}
export class ScheduleDto { export class ScheduleDto extends Model {
constructor( constructor(
public readonly status: string, public readonly status: string,
public readonly scheduledBy: string, public readonly scheduledBy: string,
public readonly dueTime: DateTime public readonly dueTime: DateTime
) { ) {
super();
}
public with(value: Partial<ScheduleDto>): ScheduleDto {
return this.clone(value);
} }
} }
export class ContentDto { export class ContentDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly status: string, public readonly status: string,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly created: DateTime, public readonly created: DateTime,
public readonly createdBy: string,
public readonly lastModified: DateTime, public readonly lastModified: DateTime,
public readonly lastModifiedBy: string,
public readonly scheduleJob: ScheduleDto | null, public readonly scheduleJob: ScheduleDto | null,
public readonly isPending: boolean, public readonly isPending: boolean,
public readonly data: object | any, public readonly data: object | any,
public readonly dataDraft: object, public readonly dataDraft: object,
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public with(value: Partial<ContentDto>): ContentDto {
return this.clone(value);
} }
} }
@ -107,10 +123,8 @@ export class ContentsService {
return new ContentDto( return new ContentDto(
item.id, item.id,
item.status, item.status,
item.createdBy, DateTime.parseISO_UTC(item.created), item.createdBy,
item.lastModifiedBy, DateTime.parseISO_UTC(item.lastModified), item.lastModifiedBy,
DateTime.parseISO_UTC(item.created),
DateTime.parseISO_UTC(item.lastModified),
item.scheduleJob item.scheduleJob
? new ScheduleDto( ? new ScheduleDto(
item.scheduleJob.status, item.scheduleJob.status,
@ -136,10 +150,8 @@ export class ContentsService {
return new ContentDto( return new ContentDto(
body.id, body.id,
body.status, body.status,
body.createdBy, DateTime.parseISO_UTC(body.created), body.createdBy,
body.lastModifiedBy, DateTime.parseISO_UTC(body.lastModified), body.lastModifiedBy,
DateTime.parseISO_UTC(body.created),
DateTime.parseISO_UTC(body.lastModified),
body.scheduleJob body.scheduleJob
? new ScheduleDto( ? new ScheduleDto(
body.scheduleJob.status, body.scheduleJob.status,
@ -174,10 +186,8 @@ export class ContentsService {
return new ContentDto( return new ContentDto(
body.id, body.id,
body.status, body.status,
body.createdBy, DateTime.parseISO_UTC(body.created), body.createdBy,
body.lastModifiedBy, DateTime.parseISO_UTC(body.lastModified), body.lastModifiedBy,
DateTime.parseISO_UTC(body.created),
DateTime.parseISO_UTC(body.lastModified),
null, null,
true, true,
null, null,

15
src/Squidex/app/shared/services/plans.service.ts

@ -15,11 +15,12 @@ import {
AnalyticsService, AnalyticsService,
ApiUrlConfig, ApiUrlConfig,
HTTP, HTTP,
Model,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export class PlansDto { export class PlansDto extends Model {
constructor( constructor(
public readonly currentPlanId: string, public readonly currentPlanId: string,
public readonly planOwner: string, public readonly planOwner: string,
@ -27,10 +28,15 @@ export class PlansDto {
public readonly plans: PlanDto[], public readonly plans: PlanDto[],
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public with(value: Partial<PlansDto>): PlansDto {
return this.clone(value);
} }
} }
export class PlanDto { export class PlanDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly name: string, public readonly name: string,
@ -41,6 +47,11 @@ export class PlanDto {
public readonly maxAssetSize: number, public readonly maxAssetSize: number,
public readonly maxContributors: number public readonly maxContributors: number
) { ) {
super();
}
public with(value: Partial<PlanDto>): PlanDto {
return this.clone(value);
} }
} }

32
src/Squidex/app/shared/services/rules.service.ts

@ -16,6 +16,7 @@ import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP, HTTP,
Model,
Version, Version,
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
@ -50,7 +51,7 @@ export const ruleActions: any = {
} }
}; };
export class RuleDto { export class RuleDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly createdBy: string, public readonly createdBy: string,
@ -64,10 +65,28 @@ export class RuleDto {
public readonly action: any, public readonly action: any,
public readonly actionType: string public readonly actionType: string
) { ) {
super();
}
public with(value: Partial<RuleDto>): RuleDto {
return this.clone(value);
}
}
export class RuleEventsDto extends Model {
constructor(
public readonly total: number,
public readonly items: RuleEventDto[]
) {
super();
}
public with(value: Partial<RuleEventsDto>): RuleEventsDto {
return this.clone(value);
} }
} }
export class RuleEventDto { export class RuleEventDto extends Model {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly created: DateTime, public readonly created: DateTime,
@ -79,14 +98,11 @@ export class RuleEventDto {
public readonly jobResult: string, public readonly jobResult: string,
public readonly numCalls: number public readonly numCalls: number
) { ) {
super();
} }
}
export class RuleEventsDto { public with(value: Partial<RuleEventDto>): RuleEventDto {
constructor( return this.clone(value);
public readonly total: number,
public readonly items: RuleEventDto[]
) {
} }
} }

81
src/Squidex/app/shared/services/schemas.fields.spec.ts

@ -8,15 +8,16 @@
import { DateTime } from '@app/framework'; import { DateTime } from '@app/framework';
import { import {
ArrayFieldPropertiesDto,
AssetsFieldPropertiesDto, AssetsFieldPropertiesDto,
BooleanFieldPropertiesDto, BooleanFieldPropertiesDto,
DateTimeFieldPropertiesDto, DateTimeFieldPropertiesDto,
FieldDto,
FieldPropertiesDto, FieldPropertiesDto,
GeolocationFieldPropertiesDto, GeolocationFieldPropertiesDto,
JsonFieldPropertiesDto, JsonFieldPropertiesDto,
NumberFieldPropertiesDto, NumberFieldPropertiesDto,
ReferencesFieldPropertiesDto, ReferencesFieldPropertiesDto,
RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto,
SchemaPropertiesDto, SchemaPropertiesDto,
StringFieldPropertiesDto, StringFieldPropertiesDto,
@ -43,9 +44,9 @@ describe('SchemaDetailsDto', () => {
}); });
it('should return configured fields as list fields if no schema field are declared', () => { it('should return configured fields as list fields if no schema field are declared', () => {
const field1 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, true), 1); const field1 = createField(new ArrayFieldPropertiesDto({ isListField: true }), 1);
const field2 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 2); const field2 = createField(new ArrayFieldPropertiesDto({ isListField: false }), 2);
const field3 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, true), 3); const field3 = createField(new ArrayFieldPropertiesDto({ isListField: true }), 3);
const schema = createSchema(new SchemaPropertiesDto(''), 1, [field1, field2, field3]); const schema = createSchema(new SchemaPropertiesDto(''), 1, [field1, field2, field3]);
@ -53,9 +54,9 @@ describe('SchemaDetailsDto', () => {
}); });
it('should return first fields as list fields if no schema field is declared', () => { it('should return first fields as list fields if no schema field is declared', () => {
const field1 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 1); const field1 = createField(new ArrayFieldPropertiesDto(), 1);
const field2 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 2); const field2 = createField(new ArrayFieldPropertiesDto(), 2);
const field3 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 3); const field3 = createField(new ArrayFieldPropertiesDto(), 3);
const schema = createSchema(new SchemaPropertiesDto(''), 1, [field1, field2, field3]); const schema = createSchema(new SchemaPropertiesDto(''), 1, [field1, field2, field3]);
@ -63,7 +64,7 @@ describe('SchemaDetailsDto', () => {
}); });
it('should return empty list fields if fields is empty', () => { it('should return empty list fields if fields is empty', () => {
const schema = createSchema(new SchemaPropertiesDto(''), 1, []); const schema = createSchema(new SchemaPropertiesDto(), 1, []);
expect(schema.listFields).toEqual([{ properties: {} }]); expect(schema.listFields).toEqual([{ properties: {} }]);
}); });
@ -71,50 +72,74 @@ describe('SchemaDetailsDto', () => {
describe('FieldDto', () => { describe('FieldDto', () => {
it('should return label as display name', () => { it('should return label as display name', () => {
const field = createField(new AssetsFieldPropertiesDto('Label', null, null, null, true, false), 1); const field = createField(new AssetsFieldPropertiesDto({ label: 'Label' }), 1);
expect(field.displayName).toBe('Label'); expect(field.displayName).toBe('Label');
}); });
it('should return name as display name if label is null', () => { it('should return name as display name if label is null', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false), 1); const field = createField(new AssetsFieldPropertiesDto(), 1);
expect(field.displayName).toBe('field1'); expect(field.displayName).toBe('field1');
}); });
it('should return name as display name label is empty', () => { it('should return name as display name label is empty', () => {
const field = createField(new AssetsFieldPropertiesDto('', null, null, null, true, false), 1); const field = createField(new AssetsFieldPropertiesDto({ label: '' }), 1);
expect(field.displayName).toBe('field1'); expect(field.displayName).toBe('field1');
}); });
it('should return placeholder as display placeholder', () => { it('should return placeholder as display placeholder', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, 'Placeholder', null, true, false), 1); const field = createField(new AssetsFieldPropertiesDto({ placeholder: 'Placeholder' }), 1);
expect(field.displayPlaceholder).toBe('Placeholder'); expect(field.displayPlaceholder).toBe('Placeholder');
}); });
it('should return empty as display placeholder if placeholder is null', () => { it('should return empty as display placeholder if placeholder is null', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false)); const field = createField(new AssetsFieldPropertiesDto());
expect(field.displayPlaceholder).toBe(''); expect(field.displayPlaceholder).toBe('');
}); });
it('should return localizable if partitioning is language', () => { it('should return localizable if partitioning is language', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false), 1, 'language'); const field = createField(new AssetsFieldPropertiesDto(), 1, 'language');
expect(field.isLocalizable).toBeTruthy(); expect(field.isLocalizable).toBeTruthy();
}); });
it('should not return localizable if partitioning is invarient', () => { it('should not return localizable if partitioning is invarient', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false), 1, 'invariant'); const field = createField(new AssetsFieldPropertiesDto(), 1, 'invariant');
expect(field.isLocalizable).toBeFalsy(); expect(field.isLocalizable).toBeFalsy();
}); });
}); });
describe('ArrayField', () => {
const field = createField(new ArrayFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 }));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(3);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to asset count', () => {
expect(field.formatValue([1, 2, 3])).toBe('3 Items(s)');
});
it('should return zero formatting if other type', () => {
expect(field.formatValue(1)).toBe('0 Items');
});
it('should return null for default properties', () => {
expect(field.defaultValue()).toBeNull();
});
});
describe('AssetsField', () => { describe('AssetsField', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false, 1, 1)); const field = createField(new AssetsFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(3); expect(field.createValidators(false).length).toBe(3);
@ -138,7 +163,7 @@ describe('AssetsField', () => {
}); });
describe('TagsField', () => { describe('TagsField', () => {
const field = createField(new TagsFieldPropertiesDto(null, null, null, null, true, false, 1, 1)); const field = createField(new TagsFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(3); expect(field.createValidators(false).length).toBe(3);
@ -162,7 +187,7 @@ describe('TagsField', () => {
}); });
describe('BooleanField', () => { describe('BooleanField', () => {
const field = createField(new BooleanFieldPropertiesDto(null, null, null, null, true, false, false, 'Checkbox')); const field = createField(new BooleanFieldPropertiesDto('Checkbox', { isRequired: true }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1); expect(field.createValidators(false).length).toBe(1);
@ -189,7 +214,7 @@ describe('BooleanField', () => {
describe('DateTimeField', () => { describe('DateTimeField', () => {
const now = DateTime.parseISO_UTC('2017-10-12T16:30:10Z'); const now = DateTime.parseISO_UTC('2017-10-12T16:30:10Z');
const field = createField(new DateTimeFieldPropertiesDto(null, null, null, null, true, false, 'Date')); const field = createField(new DateTimeFieldPropertiesDto('DateTime', { isRequired: true }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1); expect(field.createValidators(false).length).toBe(1);
@ -204,13 +229,13 @@ describe('DateTimeField', () => {
}); });
it('should format to date', () => { it('should format to date', () => {
const dateField = createField(new DateTimeFieldPropertiesDto(null, null, null, null, true, false, 'Date')); const dateField = createField(new DateTimeFieldPropertiesDto('Date'));
expect(dateField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12'); expect(dateField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12');
}); });
it('should format to date time', () => { it('should format to date time', () => {
const dateTimeField = createField(new DateTimeFieldPropertiesDto(null, null, null, null, true, false, 'DateTime')); const dateTimeField = createField(new DateTimeFieldPropertiesDto('DateTime'));
expect(dateTimeField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12 16:00:00'); expect(dateTimeField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12 16:00:00');
}); });
@ -235,7 +260,7 @@ describe('DateTimeField', () => {
}); });
describe('GeolocationField', () => { describe('GeolocationField', () => {
const field = createField(new GeolocationFieldPropertiesDto(null, null, null, null, true, false, 'Default')); const field = createField(new GeolocationFieldPropertiesDto({ isRequired: true }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1); expect(field.createValidators(false).length).toBe(1);
@ -255,7 +280,7 @@ describe('GeolocationField', () => {
}); });
describe('JsonField', () => { describe('JsonField', () => {
const field = createField(new JsonFieldPropertiesDto(null, null, null, null, true, false)); const field = createField(new JsonFieldPropertiesDto({ isRequired: true }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1); expect(field.createValidators(false).length).toBe(1);
@ -275,7 +300,7 @@ describe('JsonField', () => {
}); });
describe('NumberField', () => { describe('NumberField', () => {
const field = createField(new NumberFieldPropertiesDto(null, null, null, null, true, false, false, 'Input', undefined, 3, 1, [1, 2, 3])); const field = createField(new NumberFieldPropertiesDto('Input', { isRequired: true, minValue: 1, maxValue: 6, allowedValues: [1, 3] }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(4); expect(field.createValidators(false).length).toBe(4);
@ -297,7 +322,7 @@ describe('NumberField', () => {
}); });
describe('ReferencesField', () => { describe('ReferencesField', () => {
const field = createField(new ReferencesFieldPropertiesDto(null, null, null, null, true, false, 1, 1)); const field = createField(new ReferencesFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(3); expect(field.createValidators(false).length).toBe(3);
@ -321,7 +346,7 @@ describe('ReferencesField', () => {
}); });
describe('StringField', () => { describe('StringField', () => {
const field = createField(new StringFieldPropertiesDto(null, null, null, null, true, false, false, 'Input', undefined, 'pattern', undefined, 3, 1, ['1', '2'])); const field = createField(new StringFieldPropertiesDto('Input', { isRequired: true, pattern: 'pattern', minLength: 1, maxLength: 5, allowedValues: ['a', 'b'] }));
it('should create validators', () => { it('should create validators', () => {
expect(field.createValidators(false).length).toBe(5); expect(field.createValidators(false).length).toBe(5);
@ -342,10 +367,10 @@ describe('StringField', () => {
}); });
}); });
function createSchema(properties: SchemaPropertiesDto, index = 1, fields: FieldDto[]) { function createSchema(properties: SchemaPropertiesDto, index = 1, fields: RootFieldDto[]) {
return new SchemaDetailsDto('id' + index, 'schema' + index, '', properties, true, null!, null!, null!, null!, null!, fields); return new SchemaDetailsDto('id' + index, 'schema' + index, '', properties, true, null!, null!, null!, null!, null!, fields);
} }
function createField(properties: FieldPropertiesDto, index = 1, partitioning = 'languages') { function createField(properties: FieldPropertiesDto, index = 1, partitioning = 'languages') {
return new FieldDto(index, 'field' + index, false, false, false, partitioning, properties); return new RootFieldDto(index, 'field' + index, properties, partitioning);
} }

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

@ -16,6 +16,8 @@ import {
CreateSchemaDto, CreateSchemaDto,
DateTime, DateTime,
FieldDto, FieldDto,
NestedFieldDto,
RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto,
SchemaDto, SchemaDto,
SchemaPropertiesDto, SchemaPropertiesDto,
@ -103,13 +105,13 @@ describe('SchemasService', () => {
]); ]);
expect(schemas).toEqual([ expect(schemas).toEqual([
new SchemaDto('id1', 'name1', 'category1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1', new SchemaDto('id1', 'name1', 'category1', new SchemaPropertiesDto('label1', 'hints1'), true,
DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2016-12-12T10:10'), 'Created1',
DateTime.parseISO_UTC('2017-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), 'LastModifiedBy1',
new Version('11')), new Version('11')),
new SchemaDto('id2', 'name2', 'category2', new SchemaPropertiesDto('label2', 'hints2'), true, 'Created2', 'LastModifiedBy2', new SchemaDto('id2', 'name2', 'category2', new SchemaPropertiesDto('label2', 'hints2'), true,
DateTime.parseISO_UTC('2016-10-12T10:10'), DateTime.parseISO_UTC('2016-10-12T10:10'), 'Created2',
DateTime.parseISO_UTC('2017-10-12T10:10'), DateTime.parseISO_UTC('2017-10-12T10:10'), 'LastModifiedBy2',
new Version('22')) new Version('22'))
]); ]);
})); }));
@ -143,30 +145,50 @@ describe('SchemasService', () => {
}, },
fields: [ fields: [
{ {
fieldId: 1, fieldId: 11,
name: 'field1', name: 'field11',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
properties: { properties: {
fieldType: 'Number' fieldType: 'Array'
} },
nested: [
{
fieldId: 101,
name: 'field101',
isHidden: true,
isDisabled: true,
properties: {
fieldType: 'String'
}
},
{
fieldId: 102,
name: 'field102',
isHidden: true,
isDisabled: true,
properties: {
fieldType: 'Number'
}
}
]
}, },
{ {
fieldId: 2, fieldId: 12,
name: 'field2', name: 'field12',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
properties: { properties: {
fieldType: 'String' fieldType: 'Assets'
} }
}, },
{ {
fieldId: 3, fieldId: 13,
name: 'field3', name: 'field13',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
@ -176,8 +198,8 @@ describe('SchemasService', () => {
} }
}, },
{ {
fieldId: 4, fieldId: 14,
name: 'field4', name: 'field14',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
@ -187,41 +209,41 @@ describe('SchemasService', () => {
} }
}, },
{ {
fieldId: 5, fieldId: 15,
name: 'field5', name: 'field15',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
properties: { properties: {
fieldType: 'Json' fieldType: 'Geolocation'
} }
}, },
{ {
fieldId: 6, fieldId: 16,
name: 'field6', name: 'field16',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
properties: { properties: {
fieldType: 'Geolocation' fieldType: 'Json'
} }
}, },
{ {
fieldId: 7, fieldId: 17,
name: 'field7', name: 'field17',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
properties: { properties: {
fieldType: 'Assets' fieldType: 'Number'
} }
}, },
{ {
fieldId: 8, fieldId: 18,
name: 'field8', name: 'field18',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
@ -231,8 +253,19 @@ describe('SchemasService', () => {
} }
}, },
{ {
fieldId: 9, fieldId: 19,
name: 'field9', name: 'field19',
isLocked: true,
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'String'
}
},
{
fieldId: 20,
name: 'field20',
isLocked: true, isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
@ -254,20 +287,24 @@ describe('SchemasService', () => {
}); });
expect(schema).toEqual( expect(schema).toEqual(
new SchemaDetailsDto('id1', 'name1', 'category1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1', new SchemaDetailsDto('id1', 'name1', 'category1', new SchemaPropertiesDto('label1', 'hints1'), true,
DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2016-12-12T10:10'), 'Created1',
DateTime.parseISO_UTC('2017-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), 'LastModifiedBy1',
new Version('2'), new Version('2'),
[ [
new FieldDto(1, 'field1', true, true, true, 'language', createProperties('Number')), new RootFieldDto(11, 'field11', createProperties('Array'), 'language', true, true, true, [
new FieldDto(2, 'field2', true, true, true, 'language', createProperties('String')), new NestedFieldDto(101, 'field101', createProperties('String'), 11, true, true),
new FieldDto(3, 'field3', true, true, true, 'language', createProperties('Boolean')), new NestedFieldDto(102, 'field102', createProperties('Number'), 11, true, true)
new FieldDto(4, 'field4', true, true, true, 'language', createProperties('DateTime')), ]),
new FieldDto(5, 'field5', true, true, true, 'language', createProperties('Json')), new RootFieldDto(12, 'field12', createProperties('Assets'), 'language', true, true, true),
new FieldDto(6, 'field6', true, true, true, 'language', createProperties('Geolocation')), new RootFieldDto(13, 'field13', createProperties('Boolean'), 'language', true, true, true),
new FieldDto(7, 'field7', true, true, true, 'language', createProperties('Assets')), new RootFieldDto(14, 'field14', createProperties('DateTime'), 'language', true, true, true),
new FieldDto(8, 'field8', true, true, true, 'language', createProperties('References')), new RootFieldDto(15, 'field15', createProperties('Geolocation'), 'language', true, true, true),
new FieldDto(9, 'field9', true, true, true, 'language', createProperties('Tags')) new RootFieldDto(16, 'field16', createProperties('Json'), 'language', true, true, true),
new RootFieldDto(17, 'field17', createProperties('Number'), 'language', true, true, true),
new RootFieldDto(18, 'field18', createProperties('References'), 'language', true, true, true),
new RootFieldDto(19, 'field19', createProperties('String'), 'language', true, true, true),
new RootFieldDto(20, 'field20', createProperties('Tags'), 'language', true, true, true)
], ],
'<script-query>', '<script-query>',
'<script-create>', '<script-create>',
@ -301,7 +338,52 @@ describe('SchemasService', () => {
}); });
expect(schema).toEqual( expect(schema).toEqual(
new SchemaDetailsDto('1', dto.name, '', new SchemaPropertiesDto(), false, user, user, now, now, new Version('2'), [])); new SchemaDetailsDto('1', dto.name, '', new SchemaPropertiesDto(), false, now, user, now, user, new Version('2'), []));
}));
it('should make put request to update schema',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateSchemaDto('label', 'hints');
schemasService.putSchema('my-app', 'my-schema', dto, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
it('should make put request to update schema scripts',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateSchemaScriptsDto();
schemasService.putScripts('my-app', 'my-schema', dto, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/scripts');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
it('should make put request to update category',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateSchemaCategoryDto();
schemasService.putCategory('my-app', 'my-schema', dto, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/category');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
})); }));
it('should make post request to add field', it('should make post request to add field',
@ -311,7 +393,7 @@ describe('SchemasService', () => {
let field: FieldDto | null = null; let field: FieldDto | null = null;
schemasService.postField('my-app', 'my-schema', dto, version).subscribe(result => { schemasService.postField('my-app', 'my-schema', dto, undefined, version).subscribe(result => {
field = result.payload; field = result.payload;
}); });
@ -323,17 +405,15 @@ describe('SchemasService', () => {
req.flush({ id: 123 }); req.flush({ id: 123 });
expect(field).toEqual( expect(field).toEqual(
new FieldDto(123, dto.name, false, false, false, dto.partitioning, dto.properties)); new RootFieldDto(123, dto.name, dto.properties, dto.partitioning));
})); }));
it('should make put request to update schema', it('should make put request to publish schema',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateSchemaDto('label', 'hints'); schemasService.publishSchema('my-app', 'my-schema', version).subscribe();
schemasService.putSchema('my-app', 'my-schema', dto, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/publish');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBe(version.value);
@ -341,14 +421,12 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to update schema scripts', it('should make put request to unpublish schema',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateSchemaScriptsDto(); schemasService.unpublishSchema('my-app', 'my-schema', version).subscribe();
schemasService.putScripts('my-app', 'my-schema', dto, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/scripts'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/unpublish');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBe(version.value);
@ -356,19 +434,26 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to update category', it('should make post request to add nested field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateSchemaCategoryDto(); const dto = new AddFieldDto('name', 'invariant', createProperties('Number'));
schemasService.putCategory('my-app', 'my-schema', dto, version).subscribe(); let field: FieldDto | null = null;
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/category'); schemasService.postField('my-app', 'my-schema', dto, 13, version).subscribe(result => {
field = result.payload;
});
expect(req.request.method).toEqual('PUT'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested');
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({}); req.flush({ id: 123 });
expect(field).toEqual(
new NestedFieldDto(123, dto.name, dto.properties, 13));
})); }));
it('should make put request to update field', it('should make put request to update field',
@ -376,7 +461,7 @@ describe('SchemasService', () => {
const dto = new UpdateFieldDto(createProperties('Number')); const dto = new UpdateFieldDto(createProperties('Number'));
schemasService.putField('my-app', 'my-schema', 1, dto, version).subscribe(); schemasService.putField('my-app', 'my-schema', 1, dto, undefined, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1');
@ -386,12 +471,27 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to update nested field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = new UpdateFieldDto(createProperties('Number'));
schemasService.putField('my-app', 'my-schema', 1, dto, 13, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested/1');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
it('should make put request to update field ordering', it('should make put request to update field ordering',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
const dto = [1, 2, 3]; const dto = [1, 2, 3];
schemasService.putFieldOrdering('my-app', 'my-schema', dto, version).subscribe(); schemasService.putFieldOrdering('my-app', 'my-schema', dto, undefined, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/ordering'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/ordering');
@ -401,12 +501,14 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to publish schema', it('should make put request to update nested field ordering',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.publishSchema('my-app', 'my-schema', version).subscribe(); const dto = [1, 2, 3];
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/publish'); schemasService.putFieldOrdering('my-app', 'my-schema', dto, 13, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested/ordering');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBe(version.value);
@ -414,12 +516,12 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to unpublish schema', it('should make put request to enable field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.unpublishSchema('my-app', 'my-schema', version).subscribe(); schemasService.enableField('my-app', 'my-schema', 1, undefined, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/unpublish'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/enable');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBe(version.value);
@ -427,12 +529,12 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to enable field', it('should make put request to enable nested field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.enableField('my-app', 'my-schema', 1, version).subscribe(); schemasService.enableField('my-app', 'my-schema', 1, 13, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/enable'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested/1/enable');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBe(version.value);
@ -443,7 +545,7 @@ describe('SchemasService', () => {
it('should make put request to disable field', it('should make put request to disable field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.disableField('my-app', 'my-schema', 1, version).subscribe(); schemasService.disableField('my-app', 'my-schema', 1, undefined, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/disable'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/disable');
@ -453,12 +555,12 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to lock field', it('should make put request to disable nested field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.lockField('my-app', 'my-schema', 1, version).subscribe(); schemasService.disableField('my-app', 'my-schema', 1, 13, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/lock'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested/1/disable');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBe(version.value);
@ -469,7 +571,7 @@ describe('SchemasService', () => {
it('should make put request to show field', it('should make put request to show field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.showField('my-app', 'my-schema', 1, version).subscribe(); schemasService.showField('my-app', 'my-schema', 1, undefined, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/show'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/show');
@ -479,10 +581,23 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to show nested field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.showField('my-app', 'my-schema', 1, 13, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested/1/show');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
it('should make put request to hide field', it('should make put request to hide field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.hideField('my-app', 'my-schema', 1, version).subscribe(); schemasService.hideField('my-app', 'my-schema', 1, undefined, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/hide'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/hide');
@ -492,10 +607,23 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to hide nested field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.hideField('my-app', 'my-schema', 1, 13, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested/1/hide');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
it('should make delete request to delete field', it('should make delete request to delete field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.deleteField('my-app', 'my-schema', 1, version).subscribe(); schemasService.deleteField('my-app', 'my-schema', 1, undefined, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1');
@ -505,6 +633,19 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make delete request to delete nested field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.deleteField('my-app', 'my-schema', 1, 13, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/13/nested/1');
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
it('should make delete request to delete schema', it('should make delete request to delete schema',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
@ -517,4 +658,17 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to lock field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.lockField('my-app', 'my-schema', 1, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/lock');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
}); });

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

@ -17,6 +17,7 @@ import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP, HTTP,
Model,
StringHelper, StringHelper,
ValidatorsEx, ValidatorsEx,
Version, Version,
@ -60,32 +61,35 @@ export function createProperties(fieldType: string, values: Object | null = null
let properties: FieldPropertiesDto; let properties: FieldPropertiesDto;
switch (fieldType) { switch (fieldType) {
case 'Number': case 'Array':
properties = new NumberFieldPropertiesDto(null, null, null, null, false, false, false, 'Input'); properties = new ArrayFieldPropertiesDto();
break; break;
case 'String': case 'Assets':
properties = new StringFieldPropertiesDto(null, null, null, null, false, false, false, 'Input'); properties = new AssetsFieldPropertiesDto();
break; break;
case 'Boolean': case 'Boolean':
properties = new BooleanFieldPropertiesDto(null, null, null, null, false, false, false, 'Checkbox'); properties = new BooleanFieldPropertiesDto('Checkbox');
break; break;
case 'DateTime': case 'DateTime':
properties = new DateTimeFieldPropertiesDto(null, null, null, null, false, false, 'DateTime'); properties = new DateTimeFieldPropertiesDto('DateTime');
break; break;
case 'Geolocation': case 'Geolocation':
properties = new GeolocationFieldPropertiesDto(null, null, null, null, false, false, 'Map'); properties = new GeolocationFieldPropertiesDto();
break; break;
case 'Json': case 'Json':
properties = new JsonFieldPropertiesDto(null, null, null, null, false, false); properties = new JsonFieldPropertiesDto();
break;
case 'Number':
properties = new NumberFieldPropertiesDto('Input');
break; break;
case 'References': case 'References':
properties = new ReferencesFieldPropertiesDto(null, null, null, null, false, false); properties = new ReferencesFieldPropertiesDto();
break; break;
case 'Assets': case 'String':
properties = new AssetsFieldPropertiesDto(null, null, null, null, false, false); properties = new StringFieldPropertiesDto('Input');
break; break;
case 'Tags': case 'Tags':
properties = new TagsFieldPropertiesDto(null, null, null, null, false, false); properties = new TagsFieldPropertiesDto();
break; break;
default: default:
throw 'Invalid properties type'; throw 'Invalid properties type';
@ -98,8 +102,8 @@ export function createProperties(fieldType: string, values: Object | null = null
return properties; return properties;
} }
export class SchemaDto { export class SchemaDto extends Model {
public readonly displayName = StringHelper.firstNonEmpty(this.properties.label, this.name); public displayName: string;
constructor( constructor(
public readonly id: string, public readonly id: string,
@ -107,27 +111,42 @@ export class SchemaDto {
public readonly category: string, public readonly category: string,
public readonly properties: SchemaPropertiesDto, public readonly properties: SchemaPropertiesDto,
public readonly isPublished: boolean, public readonly isPublished: boolean,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly created: DateTime, public readonly created: DateTime,
public readonly createdBy: string,
public readonly lastModified: DateTime, public readonly lastModified: DateTime,
public readonly lastModifiedBy: string,
public readonly version: Version public readonly version: Version
) { ) {
super();
}
public onCreated() {
this.displayName = StringHelper.firstNonEmpty(this.properties.label, this.name);
}
public with(value: Partial<SchemaDto>): SchemaDto {
return this.clone(value);
} }
} }
export class SchemaDetailsDto extends SchemaDto { export class SchemaDetailsDto extends SchemaDto {
public readonly listFields: FieldDto[]; public listFields: RootFieldDto[];
constructor(id: string, name: string, category: string, properties: SchemaPropertiesDto, isPublished: boolean, createdBy: string, lastModifiedBy: string, created: DateTime, lastModified: DateTime, version: Version, constructor(id: string, name: string, category: string, properties: SchemaPropertiesDto, isPublished: boolean, created: DateTime, createdBy: string, lastModified: DateTime, lastModifiedBy: string, version: Version,
public readonly fields: FieldDto[], public readonly fields: RootFieldDto[],
public readonly scriptQuery?: string, public readonly scriptQuery?: string,
public readonly scriptCreate?: string, public readonly scriptCreate?: string,
public readonly scriptUpdate?: string, public readonly scriptUpdate?: string,
public readonly scriptDelete?: string, public readonly scriptDelete?: string,
public readonly scriptChange?: string public readonly scriptChange?: string
) { ) {
super(id, name, category, properties, isPublished, createdBy, lastModifiedBy, created, lastModified, version); super(id, name, category, properties, isPublished, created, createdBy, lastModified, lastModifiedBy, version);
this.onCreated();
}
public onCreated() {
super.onCreated();
this.listFields = this.fields.filter(x => x.properties.isListField); this.listFields = this.fields.filter(x => x.properties.isListField);
@ -139,23 +158,27 @@ export class SchemaDetailsDto extends SchemaDto {
this.listFields = [<any>{ properties: {} }]; this.listFields = [<any>{ properties: {} }];
} }
} }
}
export class FieldDto { public with(value: Partial<SchemaDetailsDto>): SchemaDetailsDto {
public readonly displayName = StringHelper.firstNonEmpty(this.properties.label, this.name); return this.clone(value);
public readonly displayPlaceholder = this.properties.placeholder || ''; }
}
public readonly isLocalizable = this.partitioning !== 'invariant'; export class FieldDto extends Model {
public displayName: string;
public displayPlaceholder: string;
constructor( constructor(
public readonly fieldId: number, public readonly fieldId: number,
public readonly name: string, public readonly name: string,
public readonly isLocked: boolean,
public readonly isHidden: boolean,
public readonly isDisabled: boolean,
public readonly partitioning: string,
public readonly properties: FieldPropertiesDto public readonly properties: FieldPropertiesDto
) { ) {
super();
}
public onCreated() {
this.displayName = StringHelper.firstNonEmpty(this.properties.label, this.name);
this.displayPlaceholder = this.properties.placeholder || '';
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -169,18 +192,66 @@ export class FieldDto {
public defaultValue(): any { public defaultValue(): any {
return this.properties.getDefaultValue(); return this.properties.getDefaultValue();
} }
public with(value: Partial<FieldDto>): FieldDto {
return this.clone(value);
}
}
export class RootFieldDto extends FieldDto {
public readonly isLocalizable = this.partitioning === 'language';
constructor(fieldId: number, name: string, properties: FieldPropertiesDto,
public readonly partitioning: string,
public readonly isHidden: boolean = false,
public readonly isDisabled: boolean = false,
public readonly isLocked: boolean = false,
public readonly nested: NestedFieldDto[] = []
) {
super(fieldId, name, properties);
this.onCreated();
}
public with(value: Partial<RootFieldDto>): RootFieldDto {
return this.clone(value);
}
}
export class NestedFieldDto extends FieldDto {
constructor(fieldId: number, name: string, properties: FieldPropertiesDto,
public readonly parentId: number,
public readonly isHidden: boolean = false,
public readonly isDisabled: boolean = false
) {
super(fieldId, name, properties);
this.onCreated();
}
public with(value: Partial<NestedFieldDto>): NestedFieldDto {
return this.clone(value);
}
} }
export type AnyFieldDto = RootFieldDto | NestedFieldDto;
export abstract class FieldPropertiesDto { export abstract class FieldPropertiesDto {
constructor( public abstract fieldType: string;
public readonly fieldType: string,
public readonly label: string | null, public readonly editorUrl?: string;
public readonly hints: string | null, public readonly label?: string;
public readonly placeholder: string | null, public readonly hints?: string;
public readonly editorUrl: string | null, public readonly placeholder?: string;
public readonly isRequired: boolean, public readonly isRequired: boolean = false;
public readonly isListField: boolean public readonly isListField: boolean = false;
constructor(public readonly editor: string,
props?: Partial<FieldPropertiesDto>
) { ) {
if (props) {
Object.assign(this, props);
}
} }
public abstract formatValue(value: any): string; public abstract formatValue(value: any): string;
@ -192,20 +263,16 @@ export abstract class FieldPropertiesDto {
} }
} }
export class StringFieldPropertiesDto extends FieldPropertiesDto { export class ArrayFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'Array';
isRequired: boolean,
isListField: boolean, public readonly minItems?: number;
public readonly inlineEditable: boolean, public readonly maxItems?: number;
public readonly editor: string,
public readonly defaultValue?: string, constructor(
public readonly pattern?: string, props?: Partial<ArrayFieldPropertiesDto>
public readonly patternMessage?: string,
public readonly minLength?: number,
public readonly maxLength?: number,
public readonly allowedValues?: string[]
) { ) {
super('String', label, hints, placeholder, editorUrl, isRequired, isListField); super('Default', props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -213,58 +280,52 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto {
return ''; return '';
} }
return value; if (value.length) {
return `${value.length} Items(s)`;
} else {
return '0 Items';
}
} }
public createValidators(isOptional: false): ValidatorFn[] { public createValidators(isOptional: boolean): ValidatorFn[] {
const validators: ValidatorFn[] = []; const validators: ValidatorFn[] = [];
if (this.isRequired && !isOptional) { if (this.isRequired && !isOptional) {
validators.push(Validators.required); validators.push(Validators.required);
} }
if (this.minLength) { if (this.minItems) {
validators.push(Validators.minLength(this.minLength)); validators.push(Validators.minLength(this.minItems));
}
if (this.maxLength) {
validators.push(Validators.maxLength(this.maxLength));
}
if (this.pattern && this.pattern.length > 0) {
validators.push(ValidatorsEx.pattern(this.pattern, this.patternMessage));
} }
if (this.allowedValues && this.allowedValues.length > 0) { if (this.maxItems) {
const values: (string | null)[] = this.allowedValues; validators.push(Validators.maxLength(this.maxItems));
if (this.isRequired && !isOptional) {
validators.push(ValidatorsEx.validValues(values));
} else {
validators.push(ValidatorsEx.validValues(values.concat([null])));
}
} }
return validators; return validators;
} }
public getDefaultValue(): any {
return this.defaultValue;
}
} }
export class NumberFieldPropertiesDto extends FieldPropertiesDto { export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'Assets';
isRequired: boolean,
isListField: boolean, public readonly minItems?: number;
public readonly inlineEditable: boolean, public readonly maxItems?: number;
public readonly editor: string, public readonly minSize?: number;
public readonly defaultValue?: number, public readonly maxSize?: number;
public readonly maxValue?: number, public readonly allowedExtensions?: string[];
public readonly minValue?: number, public readonly mustBeImage?: boolean;
public readonly allowedValues?: number[] public readonly minWidth?: number;
public readonly maxWidth?: number;
public readonly minHeight?: number;
public readonly maxHeight?: number;
public readonly aspectWidth?: number;
public readonly aspectHeight?: number;
constructor(
props?: Partial<AssetsFieldPropertiesDto>
) { ) {
super('Number', label, hints, placeholder, editorUrl, isRequired, isListField); super('Default', props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -272,7 +333,11 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
return ''; return '';
} }
return value; if (value.length) {
return `${value.length} Asset(s)`;
} else {
return '0 Assets';
}
} }
public createValidators(isOptional: boolean): ValidatorFn[] { public createValidators(isOptional: boolean): ValidatorFn[] {
@ -282,22 +347,43 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
validators.push(Validators.required); validators.push(Validators.required);
} }
if (this.minValue) { if (this.minItems) {
validators.push(Validators.min(this.minValue)); validators.push(Validators.minLength(this.minItems));
} }
if (this.maxValue) { if (this.maxItems) {
validators.push(Validators.max(this.maxValue)); validators.push(Validators.maxLength(this.maxItems));
} }
if (this.allowedValues && this.allowedValues.length > 0) { return validators;
const values: (number | null)[] = this.allowedValues; }
}
if (this.isRequired && !isOptional) { export class BooleanFieldPropertiesDto extends FieldPropertiesDto {
validators.push(ValidatorsEx.validValues(values)); public readonly fieldType = 'Boolean';
} else {
validators.push(ValidatorsEx.validValues(values.concat([null]))); public readonly inlineEditable: boolean = false;
} public readonly defaultValue?: boolean;
constructor(editor: string,
props?: Partial<BooleanFieldPropertiesDto>
) {
super(editor, props);
}
public formatValue(value: any): string {
if (value === null || value === undefined) {
return '';
}
return value ? 'Yes' : 'No';
}
public createValidators(isOptional: boolean): ValidatorFn[] {
const validators: ValidatorFn[] = [];
if (this.isRequired && !isOptional) {
validators.push(Validators.required);
} }
return validators; return validators;
@ -309,16 +395,17 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
} }
export class DateTimeFieldPropertiesDto extends FieldPropertiesDto { export class DateTimeFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'DateTime';
isRequired: boolean,
isListField: boolean, public readonly defaultValue?: string;
public readonly editor: string, public readonly maxValue?: string;
public readonly defaultValue?: string, public readonly minValue?: string;
public readonly maxValue?: string, public readonly calculatedDefaultValue?: string;
public readonly minValue?: string,
public readonly calculatedDefaultValue?: string constructor(editor: string,
props?: Partial<DateTimeFieldPropertiesDto>
) { ) {
super('DateTime', label, hints, placeholder, editorUrl, isRequired, isListField); super(editor, props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -362,23 +449,21 @@ export class DateTimeFieldPropertiesDto extends FieldPropertiesDto {
} }
} }
export class BooleanFieldPropertiesDto extends FieldPropertiesDto { export class GeolocationFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'Geolocation';
isRequired: boolean,
isListField: boolean, constructor(
public readonly inlineEditable: boolean, props?: Partial<GeolocationFieldPropertiesDto>
public readonly editor: string,
public readonly defaultValue?: boolean
) { ) {
super('Boolean', label, hints, placeholder, editorUrl, isRequired, isListField); super('Default', props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
if (value === null || value === undefined) { if (!value) {
return ''; return '';
} }
return value ? 'Yes' : 'No'; return `${value.longitude}, ${value.latitude}`;
} }
public createValidators(isOptional: boolean): ValidatorFn[] { public createValidators(isOptional: boolean): ValidatorFn[] {
@ -390,19 +475,15 @@ export class BooleanFieldPropertiesDto extends FieldPropertiesDto {
return validators; return validators;
} }
public getDefaultValue(): any {
return this.defaultValue;
}
} }
export class GeolocationFieldPropertiesDto extends FieldPropertiesDto { export class JsonFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'Json';
isRequired: boolean,
isListField: boolean, constructor(
public readonly editor: string props?: Partial<JsonFieldPropertiesDto>
) { ) {
super('Geolocation', label, hints, placeholder, editorUrl, isRequired, isListField); super('Default', props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -410,7 +491,7 @@ export class GeolocationFieldPropertiesDto extends FieldPropertiesDto {
return ''; return '';
} }
return `${value.longitude}, ${value.latitude}`; return '<Json />';
} }
public createValidators(isOptional: boolean): ValidatorFn[] { public createValidators(isOptional: boolean): ValidatorFn[] {
@ -424,15 +505,19 @@ export class GeolocationFieldPropertiesDto extends FieldPropertiesDto {
} }
} }
export class ReferencesFieldPropertiesDto extends FieldPropertiesDto { export class NumberFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'Number';
isRequired: boolean,
isListField: boolean, public readonly inlineEditable: boolean = false;
public readonly minItems?: number, public readonly defaultValue?: number;
public readonly maxItems?: number, public readonly maxValue?: number;
public readonly schemaId?: string public readonly minValue?: number;
public readonly allowedValues?: number[];
constructor(editor: string,
props?: Partial<NumberFieldPropertiesDto>
) { ) {
super('References', label, hints, placeholder, editorUrl, isRequired, isListField); super(editor, props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -440,11 +525,7 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
return ''; return '';
} }
if (value.length) { return value;
return `${value.length} Reference(s)`;
} else {
return '0 References';
}
} }
public createValidators(isOptional: boolean): ValidatorFn[] { public createValidators(isOptional: boolean): ValidatorFn[] {
@ -454,36 +535,43 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
validators.push(Validators.required); validators.push(Validators.required);
} }
if (this.minItems) { if (this.minValue) {
validators.push(Validators.minLength(this.minItems)); validators.push(Validators.min(this.minValue));
} }
if (this.maxItems) { if (this.maxValue) {
validators.push(Validators.maxLength(this.maxItems)); validators.push(Validators.max(this.maxValue));
}
if (this.allowedValues && this.allowedValues.length > 0) {
const values: (number | null)[] = this.allowedValues;
if (this.isRequired && !isOptional) {
validators.push(ValidatorsEx.validValues(values));
} else {
validators.push(ValidatorsEx.validValues(values.concat([null])));
}
} }
return validators; return validators;
} }
public getDefaultValue(): any {
return this.defaultValue;
}
} }
export class AssetsFieldPropertiesDto extends FieldPropertiesDto { export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'References';
isRequired: boolean,
isListField: boolean, public readonly minItems?: number;
public readonly minItems?: number, public readonly maxItems?: number;
public readonly maxItems?: number, public readonly schemaId?: string;
public readonly minSize?: number,
public readonly maxSize?: number, constructor(
public readonly allowedExtensions?: string[], props?: Partial<ReferencesFieldPropertiesDto>
public readonly mustBeImage?: boolean,
public readonly minWidth?: number,
public readonly maxWidth?: number,
public readonly minHeight?: number,
public readonly maxHeight?: number,
public readonly aspectWidth?: number,
public readonly aspectHeight?: number
) { ) {
super('Assets', label, hints, placeholder, editorUrl, isRequired, isListField); super('Default', props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -492,9 +580,9 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
} }
if (value.length) { if (value.length) {
return `${value.length} Asset(s)`; return `${value.length} Reference(s)`;
} else { } else {
return '0 Assets'; return '0 References';
} }
} }
@ -517,14 +605,21 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
} }
} }
export class TagsFieldPropertiesDto extends FieldPropertiesDto { export class StringFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'String';
isRequired: boolean,
isListField: boolean, public readonly inlineEditable = false;
public readonly minItems?: number, public readonly defaultValue?: string;
public readonly maxItems?: number public readonly pattern?: string;
public readonly patternMessage?: string;
public readonly minLength?: number;
public readonly maxLength?: number;
public readonly allowedValues?: string[];
constructor(editor: string,
props?: Partial<StringFieldPropertiesDto>
) { ) {
super('Tags', label, hints, placeholder, editorUrl, isRequired, isListField); super(editor, props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -532,38 +627,56 @@ export class TagsFieldPropertiesDto extends FieldPropertiesDto {
return ''; return '';
} }
if (value.length) { return value;
return value.join(', ');
} else {
return '';
}
} }
public createValidators(isOptional: boolean): ValidatorFn[] { public createValidators(isOptional: false): ValidatorFn[] {
const validators: ValidatorFn[] = []; const validators: ValidatorFn[] = [];
if (this.isRequired && !isOptional) { if (this.isRequired && !isOptional) {
validators.push(Validators.required); validators.push(Validators.required);
} }
if (this.minItems) { if (this.minLength) {
validators.push(Validators.minLength(this.minItems)); validators.push(Validators.minLength(this.minLength));
} }
if (this.maxItems) { if (this.maxLength) {
validators.push(Validators.maxLength(this.maxItems)); validators.push(Validators.maxLength(this.maxLength));
}
if (this.pattern && this.pattern.length > 0) {
validators.push(ValidatorsEx.pattern(this.pattern, this.patternMessage));
}
if (this.allowedValues && this.allowedValues.length > 0) {
const values: (string | null)[] = this.allowedValues;
if (this.isRequired && !isOptional) {
validators.push(ValidatorsEx.validValues(values));
} else {
validators.push(ValidatorsEx.validValues(values.concat([null])));
}
} }
return validators; return validators;
} }
public getDefaultValue(): any {
return this.defaultValue;
}
} }
export class JsonFieldPropertiesDto extends FieldPropertiesDto { export class TagsFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null, public readonly fieldType = 'Tags';
isRequired: boolean,
isListField: boolean public readonly minItems?: number;
public readonly maxItems?: number;
constructor(
props?: Partial<TagsFieldPropertiesDto>
) { ) {
super('Json', label, hints, placeholder, editorUrl, isRequired, isListField); super('Default', props);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -571,7 +684,11 @@ export class JsonFieldPropertiesDto extends FieldPropertiesDto {
return ''; return '';
} }
return '<Json />'; if (value.length) {
return value.join(', ');
} else {
return '';
}
} }
public createValidators(isOptional: boolean): ValidatorFn[] { public createValidators(isOptional: boolean): ValidatorFn[] {
@ -581,6 +698,14 @@ export class JsonFieldPropertiesDto extends FieldPropertiesDto {
validators.push(Validators.required); validators.push(Validators.required);
} }
if (this.minItems) {
validators.push(Validators.minLength(this.minItems));
}
if (this.maxItems) {
validators.push(Validators.maxLength(this.maxItems));
}
return validators; return validators;
} }
} }
@ -627,7 +752,7 @@ export class UpdateFieldDto {
export class CreateSchemaDto { export class CreateSchemaDto {
constructor( constructor(
public readonly name: string, public readonly name: string,
public readonly fields?: FieldDto[], public readonly fields?: RootFieldDto[],
public readonly properties?: SchemaPropertiesDto public readonly properties?: SchemaPropertiesDto
) { ) {
} }
@ -668,13 +793,10 @@ export class SchemasService {
return new SchemaDto( return new SchemaDto(
item.id, item.id,
item.name, item.name,
item.category, item.category, properties,
properties,
item.isPublished, item.isPublished,
item.createdBy, DateTime.parseISO_UTC(item.created), item.createdBy,
item.lastModifiedBy, DateTime.parseISO_UTC(item.lastModified), item.lastModifiedBy,
DateTime.parseISO_UTC(item.created),
DateTime.parseISO_UTC(item.lastModified),
new Version(item.version.toString())); new Version(item.version.toString()));
}); });
}) })
@ -694,14 +816,34 @@ export class SchemasService {
item.properties.fieldType, item.properties.fieldType,
item.properties); item.properties);
return new FieldDto( let nested: NestedFieldDto[] | null = null;
if (item.nested && item.nested.length > 0) {
nested = item.nested.map((nestedItem: any) => {
const nestedPropertiesDto =
createProperties(
nestedItem.properties.fieldType,
nestedItem.properties);
return new NestedFieldDto(
nestedItem.fieldId,
nestedItem.name,
nestedPropertiesDto,
item.fieldId,
nestedItem.isHidden,
nestedItem.isDisabled);
});
}
return new RootFieldDto(
item.fieldId, item.fieldId,
item.name, item.name,
item.isLocked, propertiesDto,
item.partitioning,
item.isHidden, item.isHidden,
item.isDisabled, item.isDisabled,
item.partitioning, item.isLocked,
propertiesDto); nested || []);
}); });
const properties = new SchemaPropertiesDto(body.properties.label, body.properties.hints); const properties = new SchemaPropertiesDto(body.properties.label, body.properties.hints);
@ -712,10 +854,8 @@ export class SchemasService {
body.category, body.category,
properties, properties,
body.isPublished, body.isPublished,
body.createdBy, DateTime.parseISO_UTC(body.created), body.createdBy,
body.lastModifiedBy, DateTime.parseISO_UTC(body.lastModified), body.lastModifiedBy,
DateTime.parseISO_UTC(body.created),
DateTime.parseISO_UTC(body.lastModified),
response.version, response.version,
fields, fields,
body.scriptQuery, body.scriptQuery,
@ -742,10 +882,8 @@ export class SchemasService {
'', '',
dto.properties || new SchemaPropertiesDto(), dto.properties || new SchemaPropertiesDto(),
false, false,
user, now, user,
user, now, user,
now,
now,
response.version, response.version,
dto.fields || [], dto.fields || [],
body.scriptQuery, body.scriptQuery,
@ -760,30 +898,6 @@ export class SchemasService {
.pretifyError('Failed to create schema. Please reload.'); .pretifyError('Failed to create schema. Please reload.');
} }
public postField(appName: string, schemaName: string, dto: AddFieldDto, version: Version): Observable<Versioned<FieldDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`);
return HTTP.postVersioned<any>(this.http, url, dto, version)
.map(response => {
const body = response.payload.body;
const field = new FieldDto(
body.id,
dto.name,
false,
false,
false,
dto.partitioning,
dto.properties);
return new Versioned(response.version, field);
})
.do(() => {
this.analytics.trackEvent('Schema', 'FieldCreated', appName);
})
.pretifyError('Failed to add field. Please reload.');
}
public deleteSchema(appName: string, schemaName: string, version: Version): Observable<Versioned<any>> { public deleteSchema(appName: string, schemaName: string, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`);
@ -814,16 +928,6 @@ export class SchemasService {
.pretifyError('Failed to update schema. Please reload.'); .pretifyError('Failed to update schema. Please reload.');
} }
public putFieldOrdering(appName: string, schemaName: string, dto: number[], version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/ordering`);
return HTTP.putVersioned(this.http, url, { fieldIds: dto }, version)
.do(() => {
this.analytics.trackEvent('Schema', 'FieldsReordered', appName);
})
.pretifyError('Failed to reorder fields. Please reload.');
}
public publishSchema(appName: string, schemaName: string, version: Version): Observable<Versioned<any>> { public publishSchema(appName: string, schemaName: string, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`);
@ -854,8 +958,41 @@ export class SchemasService {
.pretifyError('Failed to change category. Please reload.'); .pretifyError('Failed to change category. Please reload.');
} }
public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto, version: Version): Observable<Versioned<any>> { public postField(appName: string, schemaName: string, dto: AddFieldDto, parentId: number | undefined, version: Version): Observable<Versioned<AnyFieldDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); const url = this.buildUrl(appName, schemaName, parentId, '');
return HTTP.postVersioned<any>(this.http, url, dto, version)
.map(response => {
const body = response.payload.body;
if (parentId) {
const field = new NestedFieldDto(body.id, dto.name, dto.properties, parentId);
return new Versioned(response.version, field);
} else {
const field = new RootFieldDto(body.id, dto.name, dto.properties, dto.partitioning);
return new Versioned(response.version, field);
}
})
.do(() => {
this.analytics.trackEvent('Schema', 'FieldCreated', appName);
})
.pretifyError('Failed to add field. Please reload.');
}
public putFieldOrdering(appName: string, schemaName: string, dto: number[], parentId: number | undefined, version: Version): Observable<Versioned<any>> {
const url = this.buildUrl(appName, schemaName, parentId, '/ordering');
return HTTP.putVersioned(this.http, url, { fieldIds: dto }, version)
.do(() => {
this.analytics.trackEvent('Schema', 'FieldsReordered', appName);
})
.pretifyError('Failed to reorder fields. Please reload.');
}
public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto, parentId: number | undefined, version: Version): Observable<Versioned<any>> {
const url = this.buildUrl(appName, schemaName, parentId, `/${fieldId}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.do(() => { .do(() => {
@ -864,38 +1001,38 @@ export class SchemasService {
.pretifyError('Failed to update field. Please reload.'); .pretifyError('Failed to update field. Please reload.');
} }
public enableField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<Versioned<any>> { public lockField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/enable`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/lock`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.do(() => { .do(() => {
this.analytics.trackEvent('Schema', 'FieldEnabled', appName); this.analytics.trackEvent('Schema', 'FieldLocked', appName);
}) })
.pretifyError('Failed to enable field. Please reload.'); .pretifyError('Failed to lock field. Please reload.');
} }
public disableField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<Versioned<any>> { public enableField(appName: string, schemaName: string, fieldId: number, parentId: number | undefined, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/disable`); const url = this.buildUrl(appName, schemaName, parentId, `/${fieldId}/enable`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.do(() => { .do(() => {
this.analytics.trackEvent('Schema', 'FieldDisabled', appName); this.analytics.trackEvent('Schema', 'FieldEnabled', appName);
}) })
.pretifyError('Failed to disable field. Please reload.'); .pretifyError('Failed to enable field. Please reload.');
} }
public lockField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<Versioned<any>> { public disableField(appName: string, schemaName: string, fieldId: number, parentId: number | undefined, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/lock`); const url = this.buildUrl(appName, schemaName, parentId, `/${fieldId}/disable`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.do(() => { .do(() => {
this.analytics.trackEvent('Schema', 'FieldLocked', appName); this.analytics.trackEvent('Schema', 'FieldDisabled', appName);
}) })
.pretifyError('Failed to lock field. Please reload.'); .pretifyError('Failed to disable field. Please reload.');
} }
public showField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<Versioned<any>> { public showField(appName: string, schemaName: string, fieldId: number, parentId: number | undefined, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`); const url = this.buildUrl(appName, schemaName, parentId, `/${fieldId}/show`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.do(() => { .do(() => {
@ -904,8 +1041,8 @@ export class SchemasService {
.pretifyError('Failed to show field. Please reload.'); .pretifyError('Failed to show field. Please reload.');
} }
public hideField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<Versioned<any>> { public hideField(appName: string, schemaName: string, fieldId: number, parentId: number | undefined, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/hide`); const url = this.buildUrl(appName, schemaName, parentId, `/${fieldId}/hide`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.do(() => { .do(() => {
@ -914,8 +1051,8 @@ export class SchemasService {
.pretifyError('Failed to hide field. Please reload.'); .pretifyError('Failed to hide field. Please reload.');
} }
public deleteField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<Versioned<any>> { public deleteField(appName: string, schemaName: string, fieldId: number, parentId: number | undefined, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); const url = this.buildUrl(appName, schemaName, parentId, `/${fieldId}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)
.do(() => { .do(() => {
@ -923,4 +1060,13 @@ export class SchemasService {
}) })
.pretifyError('Failed to delete field. Please reload.'); .pretifyError('Failed to delete field. Please reload.');
} }
private buildUrl(appName: string, schemaName: string, parentId: number | undefined, suffix: string) {
const url =
parentId ?
this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${parentId}/nested${suffix}`) :
this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields${suffix}`);
return url;
}
} }

2
src/Squidex/app/shared/state/clients.state.ts

@ -149,4 +149,4 @@ export class ClientsState extends State<Snapshot> {
} }
const update = (client: AppClientDto, request: UpdateAppClientDto) => const update = (client: AppClientDto, request: UpdateAppClientDto) =>
new AppClientDto(client.id, request.name || client.name, client.secret, request.permission || client.permission); client.with({ name: request.name || client.name, permission: request.permission || client.permission });

101
src/Squidex/app/shared/state/contents.state.ts

@ -439,74 +439,55 @@ export class ManualContentsState extends ContentsStateBase {
} }
} }
const changeScheduleStatus = (content: ContentDto, status: string, dueTime: string, user: string, version: Version, now?: DateTime) =>
content.with({
scheduleJob: new ScheduleDto(status, user, DateTime.parseISO_UTC(dueTime)),
lastModified: now || DateTime.now(),
lastModifiedBy: user,
version
});
const changeStatus = (content: ContentDto, status: string, user: string, version: Version, now?: DateTime) => const changeStatus = (content: ContentDto, status: string, user: string, version: Version, now?: DateTime) =>
new ContentDto( content.with({
content.id,
status, status,
content.createdBy, user, scheduleJob: null,
content.created, now || DateTime.now(), lastModified: now || DateTime.now(),
null, lastModifiedBy: user,
content.isPending, version
content.data, });
content.dataDraft,
version);
const changeScheduleStatus = (content: ContentDto, status: string, dueTime: string, user: string, version: Version, now?: DateTime) =>
new ContentDto(
content.id,
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
new ScheduleDto(status, user, DateTime.parseISO_UTC(dueTime)),
content.isPending,
content.data,
content.dataDraft,
version);
const updateData = (content: ContentDto, data: any, user: string, version: Version, now?: DateTime) => const updateData = (content: ContentDto, data: any, user: string, version: Version, now?: DateTime) =>
new ContentDto( content.with({
content.id,
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
content.scheduleJob,
content.isPending,
data,
data, data,
version); dataDraft: data,
lastModified: now || DateTime.now(),
lastModifiedBy: user,
version
});
const updateDataDraft = (content: ContentDto, data: any, user: string, version: Version, now?: DateTime) => const updateDataDraft = (content: ContentDto, data: any, user: string, version: Version, now?: DateTime) =>
new ContentDto( content.with({
content.id, isPending: true,
content.status, dataDraft: data,
content.createdBy, user, lastModified: now || DateTime.now(),
content.created, now || DateTime.now(), lastModifiedBy: user,
content.scheduleJob, version
true, });
content.data,
data,
version);
const confirmChanges = (content: ContentDto, user: string, version: Version, now?: DateTime) => const confirmChanges = (content: ContentDto, user: string, version: Version, now?: DateTime) =>
new ContentDto( content.with({
content.id, isPending: false,
content.status, data: content.dataDraft,
content.createdBy, user, lastModified: now || DateTime.now(),
content.created, now || DateTime.now(), lastModifiedBy: user,
null, version
false, });
content.dataDraft,
content.dataDraft,
version);
const discardChanges = (content: ContentDto, user: string, version: Version, now?: DateTime) => const discardChanges = (content: ContentDto, user: string, version: Version, now?: DateTime) =>
new ContentDto( content.with({
content.id, isPending: false,
content.status, dataDraft: content.data,
content.createdBy, user, lastModified: now || DateTime.now(),
content.created, now || DateTime.now(), lastModifiedBy: user,
content.scheduleJob, version
false, });
content.data,
content.data,
version);

44
src/Squidex/app/shared/state/rules.state.ts

@ -147,37 +147,27 @@ export class RulesState extends State<Snapshot> {
} }
const updateTrigger = (rule: RuleDto, trigger: any, user: string, version: Version, now?: DateTime) => const updateTrigger = (rule: RuleDto, trigger: any, user: string, version: Version, now?: DateTime) =>
new RuleDto( rule.with({
rule.id,
rule.createdBy, user,
rule.created, now || DateTime.now(),
version,
rule.isEnabled,
trigger, trigger,
trigger.triggerType, triggerType: trigger.triggerType,
rule.action, lastModified: now || DateTime.now(),
rule.action.actionType); lastModifiedBy: user,
version
});
const updateAction = (rule: RuleDto, action: any, user: string, version: Version, now?: DateTime) => const updateAction = (rule: RuleDto, action: any, user: string, version: Version, now?: DateTime) =>
new RuleDto( rule.with({
rule.id,
rule.createdBy, user,
rule.created, now || DateTime.now(),
version,
rule.isEnabled,
rule.trigger,
rule.trigger.triggerType,
action, action,
action.actionType); actionType: action.triggerType,
lastModified: now || DateTime.now(),
lastModifiedBy: user,
version
});
const setEnabled = (rule: RuleDto, isEnabled: boolean, user: string, version: Version, now?: DateTime) => const setEnabled = (rule: RuleDto, isEnabled: boolean, user: string, version: Version, now?: DateTime) =>
new RuleDto( rule.with({
rule.id,
rule.createdBy, user,
rule.created, now || DateTime.now(),
version,
isEnabled, isEnabled,
rule.trigger, lastModified: now || DateTime.now(),
rule.triggerType, lastModifiedBy: user,
rule.action, version
rule.actionType); });

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

@ -18,7 +18,8 @@ import {
CreateSchemaDto, CreateSchemaDto,
DateTime, DateTime,
DialogService, DialogService,
FieldDto, NestedFieldDto,
RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto,
SchemaDto, SchemaDto,
SchemasService, SchemasService,
@ -40,17 +41,20 @@ describe('SchemasState', () => {
const newVersion = new Version('2'); const newVersion = new Version('2');
const oldSchemas = [ const oldSchemas = [
new SchemaDto('id1', 'name1', 'category1', {}, false, creator, creator, creation, creation, version), new SchemaDto('id1', 'name1', 'category1', {}, false, creation, creator, creation, creator, version),
new SchemaDto('id2', 'name2', 'category2', {}, true , creator, creator, creation, creation, version) new SchemaDto('id2', 'name2', 'category2', {}, true , creation, creator, creation, creator, version)
]; ];
const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String')); const field1 = new RootFieldDto(1, '1', createProperties('String'), 'invariant');
const field2 = new FieldDto(2, '2', true, true, true, 'l', createProperties('Number')); const field2 = new RootFieldDto(2, '2', createProperties('Array'), 'invariant', false, false, false, [
new NestedFieldDto(3, '3', createProperties('Number'), 2),
new NestedFieldDto(4, '4', createProperties('String'), 2)
]);
const schema = const schema =
new SchemaDetailsDto('id2', 'name2', 'category2', {}, true, new SchemaDetailsDto('id2', 'name2', 'category2', {}, true,
creator, creator, creation, creator,
creation, creation, creation, creator,
version, version,
[field1, field2]); [field1, field2]);
@ -277,7 +281,7 @@ describe('SchemasState', () => {
it('should add schema to snapshot when created', () => { it('should add schema to snapshot when created', () => {
const request = new CreateSchemaDto('newName'); const request = new CreateSchemaDto('newName');
const result = new SchemaDetailsDto('id4', 'newName', '', {}, false, modifier, modifier, modified, modified, version, []); const result = new SchemaDetailsDto('id4', 'newName', '', {}, false, modified, modifier, modified, modifier, version, []);
schemasService.setup(x => x.postSchema(app, request, modifier, modified)) schemasService.setup(x => x.postSchema(app, request, modifier, modified))
.returns(() => Observable.of(result)); .returns(() => Observable.of(result));
@ -301,12 +305,12 @@ describe('SchemasState', () => {
it('should add field and update user info when field added', () => { it('should add field and update user info when field added', () => {
const request = new AddFieldDto(field1.name, field1.partitioning, field1.properties); const request = new AddFieldDto(field1.name, field1.partitioning, field1.properties);
const newField = new FieldDto(3, '3', false, false, false, 'l', createProperties('String')); const newField = new RootFieldDto(3, '3', createProperties('String'), 'invariant');
schemasService.setup(x => x.postField(app, schema.name, It.isAny(), version)) schemasService.setup(x => x.postField(app, schema.name, It.isAny(), undefined, version))
.returns(() => Observable.of(new Versioned<FieldDto>(newVersion, newField))); .returns(() => Observable.of(new Versioned<RootFieldDto>(newVersion, newField)));
schemasState.addField(schema, request, modified).subscribe(); schemasState.addField(schema, request, undefined, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1); const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
@ -315,7 +319,7 @@ describe('SchemasState', () => {
}); });
it('should remove field and update user info when field removed', () => { it('should remove field and update user info when field removed', () => {
schemasService.setup(x => x.deleteField(app, schema.name, field1.fieldId, version)) schemasService.setup(x => x.deleteField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {}))); .returns(() => Observable.of(new Versioned<any>(newVersion, {})));
schemasState.deleteField(schema, field1, modified).subscribe(); schemasState.deleteField(schema, field1, modified).subscribe();
@ -327,10 +331,10 @@ describe('SchemasState', () => {
}); });
it('should sort fields and update user info when fields sorted', () => { it('should sort fields and update user info when fields sorted', () => {
schemasService.setup(x => x.putFieldOrdering(app, schema.name, [field2.fieldId, field1.fieldId], version)) schemasService.setup(x => x.putFieldOrdering(app, schema.name, [field2.fieldId, field1.fieldId], undefined, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {}))); .returns(() => Observable.of(new Versioned<any>(newVersion, {})));
schemasState.sortFields(schema, [field2, field1], modified).subscribe(); schemasState.sortFields(schema, [field2, field1], undefined, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1); const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
@ -341,7 +345,7 @@ describe('SchemasState', () => {
it('should update field properties and update user info when field updated', () => { it('should update field properties and update user info when field updated', () => {
const request = new UpdateFieldDto(createProperties('String')); const request = new UpdateFieldDto(createProperties('String'));
schemasService.setup(x => x.putField(app, schema.name, field1.fieldId, request, version)) schemasService.setup(x => x.putField(app, schema.name, field1.fieldId, request, undefined, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {}))); .returns(() => Observable.of(new Versioned<any>(newVersion, {})));
schemasState.updateField(schema, field1, request, modified).subscribe(); schemasState.updateField(schema, field1, request, modified).subscribe();
@ -353,10 +357,10 @@ describe('SchemasState', () => {
}); });
it('should mark field hidden and update user info when field hidden', () => { it('should mark field hidden and update user info when field hidden', () => {
schemasService.setup(x => x.hideField(app, schema.name, field1.fieldId, version)) schemasService.setup(x => x.hideField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {}))); .returns(() => Observable.of(new Versioned<any>(newVersion, {})));
schemasState.hideField(schema, field1, modified).subscribe(); schemasState.hideField(schema, field1, undefined, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1); const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
@ -365,7 +369,7 @@ describe('SchemasState', () => {
}); });
it('should mark field disabled and update user info when field disabled', () => { it('should mark field disabled and update user info when field disabled', () => {
schemasService.setup(x => x.disableField(app, schema.name, field1.fieldId, version)) schemasService.setup(x => x.disableField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {}))); .returns(() => Observable.of(new Versioned<any>(newVersion, {})));
schemasState.disableField(schema, field1, modified).subscribe(); schemasState.disableField(schema, field1, modified).subscribe();
@ -389,7 +393,7 @@ describe('SchemasState', () => {
}); });
it('should unmark field hidden and update user info when field shown', () => { it('should unmark field hidden and update user info when field shown', () => {
schemasService.setup(x => x.showField(app, schema.name, field2.fieldId, version)) schemasService.setup(x => x.showField(app, schema.name, field2.fieldId, undefined, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {}))); .returns(() => Observable.of(new Versioned<any>(newVersion, {})));
schemasState.showField(schema, field2, modified).subscribe(); schemasState.showField(schema, field2, modified).subscribe();
@ -401,7 +405,7 @@ describe('SchemasState', () => {
}); });
it('should unmark field disabled and update user info when field enabled', () => { it('should unmark field disabled and update user info when field enabled', () => {
schemasService.setup(x => x.enableField(app, schema.name, field2.fieldId, version)) schemasService.setup(x => x.enableField(app, schema.name, field2.fieldId, undefined, version))
.returns(() => Observable.of(new Versioned<any>(newVersion, {}))); .returns(() => Observable.of(new Versioned<any>(newVersion, {})));
schemasState.enableField(schema, field2, modified).subscribe(); schemasState.enableField(schema, field2, modified).subscribe();

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

@ -27,10 +27,13 @@ import { AppsState } from './apps.state';
import { import {
AddFieldDto, AddFieldDto,
AnyFieldDto,
createProperties, createProperties,
CreateSchemaDto, CreateSchemaDto,
FieldDto, FieldDto,
FieldPropertiesDto, FieldPropertiesDto,
NestedFieldDto,
RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto,
SchemaDto, SchemaDto,
SchemaPropertiesDto, SchemaPropertiesDto,
@ -282,13 +285,6 @@ export class SchemasState extends State<Snapshot> {
}); });
} }
public addField(schema: SchemaDetailsDto, request: AddFieldDto, now?: DateTime): Observable<FieldDto> {
return this.schemasService.postField(this.appName, schema.name, request, schema.version)
.do(dto => {
this.replaceSchema(addField(schema, dto.payload, this.user, dto.version, now));
}).map(d => d.payload);
}
public publish(schema: SchemaDto, now?: DateTime): Observable<any> { public publish(schema: SchemaDto, now?: DateTime): Observable<any> {
return this.schemasService.publishSchema(this.appName, schema.name, schema.version) return this.schemasService.publishSchema(this.appName, schema.name, schema.version)
.do(dto => { .do(dto => {
@ -313,23 +309,23 @@ export class SchemasState extends State<Snapshot> {
.notify(this.dialogs); .notify(this.dialogs);
} }
public enableField(schema: SchemaDetailsDto, field: FieldDto, now?: DateTime): Observable<any> { public configureScripts(schema: SchemaDetailsDto, request: UpdateSchemaScriptsDto, now?: DateTime): Observable<any> {
return this.schemasService.enableField(this.appName, schema.name, field.fieldId, schema.version) return this.schemasService.putScripts(this.appName, schema.name, request, schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(updateField(schema, setDisabled(field, false), this.user, dto.version, now)); this.replaceSchema(configureScripts(schema, request, this.user, dto.version, now));
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public disableField(schema: SchemaDetailsDto, field: FieldDto, now?: DateTime): Observable<any> { public update(schema: SchemaDetailsDto, request: UpdateSchemaDto, now?: DateTime): Observable<any> {
return this.schemasService.disableField(this.appName, schema.name, field.fieldId, schema.version) return this.schemasService.putSchema(this.appName, schema.name, request, schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(updateField(schema, setDisabled(field, true), this.user, dto.version, now)); this.replaceSchema(updateProperties(schema, request, this.user, dto.version, now));
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public lockField(schema: SchemaDetailsDto, field: FieldDto, now?: DateTime): Observable<any> { public lockField(schema: SchemaDetailsDto, field: RootFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.lockField(this.appName, schema.name, field.fieldId, schema.version) return this.schemasService.lockField(this.appName, schema.name, field.fieldId, schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(updateField(schema, setLocked(field, true), this.user, dto.version, now)); this.replaceSchema(updateField(schema, setLocked(field, true), this.user, dto.version, now));
@ -337,62 +333,101 @@ export class SchemasState extends State<Snapshot> {
.notify(this.dialogs); .notify(this.dialogs);
} }
public showField(schema: SchemaDetailsDto, field: FieldDto, now?: DateTime): Observable<any> { public addField(schema: SchemaDetailsDto, request: AddFieldDto, parent?: RootFieldDto, now?: DateTime): Observable<FieldDto> {
return this.schemasService.showField(this.appName, schema.name, field.fieldId, schema.version) return this.schemasService.postField(this.appName, schema.name, request, pid(parent), schema.version)
.do(dto => {
if (Types.is(dto.payload, NestedFieldDto)) {
this.replaceSchema(updateField(schema, addNested(parent!, dto.payload), this.user, dto.version, now));
} else {
this.replaceSchema(addField(schema, dto.payload, this.user, dto.version, now));
}
}).map(d => d.payload);
}
public sortFields(schema: SchemaDetailsDto, fields: any[], parent?: RootFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.putFieldOrdering(this.appName, schema.name, fields.map(t => t.fieldId), pid(parent), schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(updateField(schema, setHidden(field, false), this.user, dto.version, now)); if (!parent) {
this.replaceSchema(replaceFields(schema, fields, this.user, dto.version, now));
} else {
this.replaceSchema(updateField(schema, replaceNested(parent, fields), this.user, dto.version, now));
}
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public hideField(schema: SchemaDetailsDto, field: FieldDto, now?: DateTime): Observable<any> { public enableField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.hideField(this.appName, schema.name, field.fieldId, schema.version) return this.schemasService.enableField(this.appName, schema.name, field.fieldId, pidof(field), schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(updateField(schema, setHidden(field, true), this.user, dto.version, now)); this.replaceField(schema, setDisabled(field, false), dto.version, now);
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public deleteField(schema: SchemaDetailsDto, field: FieldDto, now?: DateTime): Observable<any> { public disableField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.deleteField(this.appName, schema.name, field.fieldId, schema.version) return this.schemasService.disableField(this.appName, schema.name, field.fieldId, pidof(field), schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(removeField(schema, field, this.user, dto.version, now)); this.replaceField(schema, setDisabled(field, true), dto.version, now);
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public sortFields(schema: SchemaDetailsDto, fields: FieldDto[], now?: DateTime): Observable<any> { public showField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.putFieldOrdering(this.appName, schema.name, fields.map(t => t.fieldId), schema.version) return this.schemasService.showField(this.appName, schema.name, field.fieldId, pidof(field), schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(replaceFields(schema, fields, this.user, dto.version, now)); this.replaceField(schema, setHidden(field, false), dto.version, now);
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public updateField(schema: SchemaDetailsDto, field: FieldDto, request: UpdateFieldDto, now?: DateTime): Observable<any> { public hideField(schema: SchemaDetailsDto, field: AnyFieldDto, parent?: RootFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.putField(this.appName, schema.name, field.fieldId, request, schema.version) return this.schemasService.hideField(this.appName, schema.name, field.fieldId, pidof(field), schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(updateField(schema, update(field, request.properties), this.user, dto.version, now)); this.replaceField(schema, setHidden(field, true), dto.version, now);
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public configureScripts(schema: SchemaDetailsDto, request: UpdateSchemaScriptsDto, now?: DateTime): Observable<any> { public updateField(schema: SchemaDetailsDto, field: AnyFieldDto, request: UpdateFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.putScripts(this.appName, schema.name, request, schema.version) return this.schemasService.putField(this.appName, schema.name, field.fieldId, request, pidof(field), schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(configureScripts(schema, request, this.user, dto.version, now)); this.replaceField(schema, update(field, request.properties), dto.version, now);
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
public update(schema: SchemaDetailsDto, request: UpdateSchemaDto, now?: DateTime): Observable<any> { public deleteField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.putSchema(this.appName, schema.name, request, schema.version) return this.schemasService.deleteField(this.appName, schema.name, field.fieldId, pidof(field), schema.version)
.do(dto => { .do(dto => {
this.replaceSchema(updateProperties(schema, request, this.user, dto.version, now)); this.removeField(schema, field, dto.version, now);
}) })
.notify(this.dialogs); .notify(this.dialogs);
} }
private replaceField(schema: SchemaDetailsDto, field: AnyFieldDto, version: Version, now?: DateTime) {
if (Types.is(field, RootFieldDto)) {
this.replaceSchema(updateField(schema, field, this.user, version, now));
} else {
const parent = schema.fields.find(x => x.fieldId === field.parentId);
if (parent) {
this.replaceSchema(updateField(schema, updatedNested(parent, field), this.user, version, now));
}
}
}
private removeField(schema: SchemaDetailsDto, field: AnyFieldDto, version: Version, now?: DateTime) {
if (Types.is(field, RootFieldDto)) {
this.replaceSchema(removeField(schema, field, this.user, version, now));
} else {
const parent = schema.fields.find(x => x.fieldId === field.parentId);
if (parent) {
this.replaceSchema(updateField(schema, removeNested(parent, field), this.user, version, now));
}
}
}
private replaceSchema(schema: SchemaDto) { private replaceSchema(schema: SchemaDto) {
return this.next(s => { return this.next(s => {
const schemas = s.schemas.replaceBy('id', schema).sortByStringAsc(x => x.displayName); const schemas = s.schemas.replaceBy('id', schema).sortByStringAsc(x => x.displayName);
@ -444,204 +479,97 @@ function removeCategory(categories: { [name: string]: boolean }, category: strin
return categories; return categories;
} }
const setPublished = (schema: SchemaDto | SchemaDetailsDto, publish: boolean, user: string, version: Version, now?: DateTime) => { const setPublished = <T extends SchemaDto>(schema: T, isPublished: boolean, user: string, version: Version, now?: DateTime) =>
if (Types.is(schema, SchemaDetailsDto)) { <T>schema.with({
return new SchemaDetailsDto( isPublished,
schema.id, lastModified: now || DateTime.now(),
schema.name, lastModifiedBy: user,
schema.category, version
schema.properties, });
publish,
schema.createdBy, user,
schema.created, now || DateTime.now(), const changeCategory = <T extends SchemaDto>(schema: T, category: string, user: string, version: Version, now?: DateTime) =>
version, <T>schema.with({
schema.fields, category,
schema.scriptQuery, lastModified: now || DateTime.now(),
schema.scriptCreate, lastModifiedBy: user,
schema.scriptUpdate, version
schema.scriptDelete, });
schema.scriptChange);
} else {
return new SchemaDto(
schema.id,
schema.name,
schema.category,
schema.properties,
publish,
schema.createdBy, user,
schema.created, now || DateTime.now(),
version);
}
};
const changeCategory = (schema: SchemaDto | SchemaDetailsDto, category: string, user: string, version: Version, now?: DateTime) => {
if (Types.is(schema, SchemaDetailsDto)) {
return new SchemaDetailsDto(
schema.id,
schema.name,
category,
schema.properties,
schema.isPublished,
schema.createdBy, user,
schema.created, now || DateTime.now(),
version,
schema.fields,
schema.scriptQuery,
schema.scriptCreate,
schema.scriptUpdate,
schema.scriptDelete,
schema.scriptChange);
} else {
return new SchemaDto(
schema.id,
schema.name,
category,
schema.properties,
schema.isPublished,
schema.createdBy, user,
schema.created, now || DateTime.now(),
version);
}
};
const configureScripts = (schema: SchemaDetailsDto, scripts: UpdateSchemaScriptsDto, user: string, version: Version, now?: DateTime) => const configureScripts = (schema: SchemaDetailsDto, scripts: UpdateSchemaScriptsDto, user: string, version: Version, now?: DateTime) =>
new SchemaDetailsDto( schema.with({
schema.id, ...scripts,
schema.name, lastModified: now || DateTime.now(),
schema.category, lastModifiedBy: user,
schema.properties, version
schema.isPublished, });
schema.createdBy, user,
schema.created, now || DateTime.now(),
version,
schema.fields,
scripts.scriptQuery,
scripts.scriptCreate,
scripts.scriptUpdate,
scripts.scriptDelete,
scripts.scriptChange);
const updateProperties = (schema: SchemaDetailsDto, properties: SchemaPropertiesDto, user: string, version: Version, now?: DateTime) => const updateProperties = (schema: SchemaDetailsDto, properties: SchemaPropertiesDto, user: string, version: Version, now?: DateTime) =>
new SchemaDetailsDto( schema.with({
schema.id,
schema.name,
schema.category,
properties, properties,
schema.isPublished, lastModified: now || DateTime.now(),
schema.createdBy, user, lastModifiedBy: user,
schema.created, now || DateTime.now(), version
version, });
schema.fields,
schema.scriptQuery, const addField = (schema: SchemaDetailsDto, field: RootFieldDto, user: string, version: Version, now?: DateTime) =>
schema.scriptCreate, schema.with({
schema.scriptUpdate, fields: [...schema.fields, field],
schema.scriptDelete, lastModified: now || DateTime.now(),
schema.scriptChange); lastModifiedBy: user,
version
const addField = (schema: SchemaDetailsDto, field: FieldDto, user: string, version: Version, now?: DateTime) => });
new SchemaDetailsDto(
schema.id, const updateField = (schema: SchemaDetailsDto, field: RootFieldDto, user: string, version: Version, now?: DateTime) =>
schema.name, schema.with({
schema.category, fields: schema.fields.map(f => f.fieldId === field.fieldId ? field : f),
schema.properties, lastModified: now || DateTime.now(),
schema.isPublished, lastModifiedBy: user,
schema.createdBy, user, version
schema.created, now || DateTime.now(), });
version,
[...schema.fields, field], const replaceFields = (schema: SchemaDetailsDto, fields: RootFieldDto[], user: string, version: Version, now?: DateTime) =>
schema.scriptQuery, schema.with({
schema.scriptCreate,
schema.scriptUpdate,
schema.scriptDelete,
schema.scriptChange);
const updateField = (schema: SchemaDetailsDto, field: FieldDto, user: string, version: Version, now?: DateTime) =>
new SchemaDetailsDto(
schema.id,
schema.name,
schema.category,
schema.properties,
schema.isPublished,
schema.createdBy, user,
schema.created, now || DateTime.now(),
version,
schema.fields.map(f => f.fieldId === field.fieldId ? field : f),
schema.scriptQuery,
schema.scriptCreate,
schema.scriptUpdate,
schema.scriptDelete,
schema.scriptChange);
const replaceFields = (schema: SchemaDetailsDto, fields: FieldDto[], user: string, version: Version, now?: DateTime) =>
new SchemaDetailsDto(
schema.id,
schema.name,
schema.category,
schema.properties,
schema.isPublished,
schema.createdBy, user,
schema.created, now || DateTime.now(),
version,
fields, fields,
schema.scriptQuery, version,
schema.scriptCreate, lastModified: now || DateTime.now(),
schema.scriptUpdate, lastModifiedBy: user
schema.scriptDelete, });
schema.scriptChange);
const removeField = (schema: SchemaDetailsDto, field: FieldDto, user: string, version: Version, now?: DateTime) => const removeField = (schema: SchemaDetailsDto, field: FieldDto, user: string, version: Version, now?: DateTime) =>
new SchemaDetailsDto( schema.with({
schema.id, fields: schema.fields.filter(f => f.fieldId !== field.fieldId),
schema.name,
schema.category,
schema.properties,
schema.isPublished,
schema.createdBy, user,
schema.created, now || DateTime.now(),
version, version,
schema.fields.filter(f => f.fieldId !== field.fieldId), lastModified: now || DateTime.now(),
schema.scriptQuery, lastModifiedBy: user
schema.scriptCreate, });
schema.scriptUpdate,
schema.scriptDelete, const addNested = (parent: RootFieldDto, nested: NestedFieldDto) =>
schema.scriptChange); parent.with({ nested: [...parent.nested, nested] });
const setLocked = (field: FieldDto, isLocked: boolean) => const updatedNested = (parent: RootFieldDto, nested: NestedFieldDto) =>
new FieldDto( parent.with({ nested: parent.nested.map(f => f.fieldId === nested.fieldId ? nested : f) });
field.fieldId,
field.name, const replaceNested = (parent: RootFieldDto, nested: NestedFieldDto[]) =>
isLocked, parent.with({ nested });
field.isHidden,
field.isDisabled, const removeNested = (parent: RootFieldDto, nested: NestedFieldDto) =>
field.partitioning, parent.with({ nested: parent.nested.filter(f => f.fieldId !== nested.fieldId) });
field.properties);
const setLocked = (field: RootFieldDto, isLocked: boolean) =>
const setHidden = (field: FieldDto, isHidden: boolean) => field.with({ isLocked });
new FieldDto(
field.fieldId, const setHidden = <T extends FieldDto>(field: T, isHidden: boolean) =>
field.name, <T>field.with(<any>{ isHidden });
field.isLocked,
isHidden, const setDisabled = <T extends FieldDto>(field: T, isDisabled: boolean) =>
field.isDisabled, <T>field.with(<any>{ isDisabled });
field.partitioning,
field.properties); const update = <T extends FieldDto>(field: T, properties: FieldPropertiesDto) =>
<T>field.with({ properties });
const setDisabled = (field: FieldDto, isDisabled: boolean) =>
new FieldDto( const pid = (field?: RootFieldDto) =>
field.fieldId, field ? field.fieldId : undefined;
field.name,
field.isLocked, const pidof = (field: FieldDto) =>
field.isDisabled, Types.is(field, NestedFieldDto) ? field.parentId : undefined;
isDisabled,
field.partitioning,
field.properties);
const update = (field: FieldDto, properties: FieldPropertiesDto) =>
new FieldDto(
field.fieldId,
field.name,
field.isLocked,
field.isHidden,
field.isDisabled,
field.partitioning,
properties);
Loading…
Cancel
Save