Browse Source

More consistent UI code.

pull/95/head
Sebastian Stehle 9 years ago
parent
commit
f1f551cbb7
  1. 5
      src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs
  2. 2
      src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs
  3. 6
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  4. 23
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  5. 22
      src/Squidex/app/features/administration/pages/messages.ts
  6. 25
      src/Squidex/app/features/administration/pages/users/messages.ts
  7. 49
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  8. 4
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  9. 29
      src/Squidex/app/features/administration/pages/users/users-page.component.ts
  10. 5
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  11. 31
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  12. 47
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  13. 13
      src/Squidex/app/features/content/pages/messages.ts
  14. 5
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  15. 9
      src/Squidex/app/features/schemas/pages/messages.ts
  16. 10
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  17. 126
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  18. 22
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  19. 23
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  20. 14
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  21. 10
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  22. 4
      src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
  23. 10
      src/Squidex/app/features/webhooks/pages/webhooks-page.component.ts
  24. 7
      src/Squidex/app/framework/angular/http-extensions-impl.ts
  25. 10
      src/Squidex/app/framework/angular/validators.spec.ts
  26. 6
      src/Squidex/app/framework/angular/validators.ts
  27. 7
      src/Squidex/app/framework/utils/immutable-array.spec.ts
  28. 14
      src/Squidex/app/framework/utils/immutable-array.ts
  29. 75
      src/Squidex/app/shared/components/asset.component.ts
  30. 8
      src/Squidex/app/shared/services/app-clients.service.ts
  31. 4
      src/Squidex/app/shared/services/app-contributors.service.ts
  32. 4
      src/Squidex/app/shared/services/app-languages.service.ts
  33. 11
      src/Squidex/app/shared/services/apps.service.spec.ts
  34. 9
      src/Squidex/app/shared/services/apps.service.ts
  35. 15
      src/Squidex/app/shared/services/assets.service.spec.ts
  36. 56
      src/Squidex/app/shared/services/assets.service.ts
  37. 30
      src/Squidex/app/shared/services/contents.service.ts
  38. 12
      src/Squidex/app/shared/services/event-consumers.service.ts
  39. 23
      src/Squidex/app/shared/services/schemas.service.spec.ts
  40. 182
      src/Squidex/app/shared/services/schemas.service.ts
  41. 3
      src/Squidex/app/shared/services/users.service.spec.ts
  42. 32
      src/Squidex/app/shared/services/users.service.ts
  43. 7
      src/Squidex/app/shared/services/webhooks.service.spec.ts
  44. 17
      src/Squidex/app/shared/services/webhooks.service.ts

5
src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs

@ -23,5 +23,10 @@ namespace Squidex.Controllers.Api.Webhooks.Models
/// </summary> /// </summary>
[Required] [Required]
public string SharedSecret { get; set; } public string SharedSecret { get; set; }
/// <summary>
/// The id of the schema.
/// </summary>
public string SchemaId { get; set; }
} }
} }

2
src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs

@ -94,7 +94,7 @@ namespace Squidex.Controllers.Api.Webhooks
await CommandBus.PublishAsync(command); await CommandBus.PublishAsync(command);
return CreatedAtAction(nameof(GetWebhooks), new { app }, SimpleMapper.Map(command, new WebhookCreatedDto())); return CreatedAtAction(nameof(GetWebhooks), new { app }, SimpleMapper.Map(command, new WebhookCreatedDto { SchemaId = command.SchemaId.Id.ToString() }));
} }
/// <summary> /// <summary>

6
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -48,13 +48,13 @@
<span>{{eventConsumer.position}}</span> <span>{{eventConsumer.position}}</span>
</td> </td>
<td class="text-right"> <td class="text-right">
<button class="btn btn-link" (click)="reset(eventConsumer.name)" *ngIf="!eventConsumer.isResetting" title="Reset Event Consumer"> <button class="btn btn-link" (click)="reset(eventConsumer)" *ngIf="!eventConsumer.isResetting" title="Reset Event Consumer">
<i class="icon icon-reset"></i> <i class="icon icon-reset"></i>
</button> </button>
<button class="btn btn-link" (click)="start(eventConsumer.name)" *ngIf="eventConsumer.isStopped" title="Start Event Consumer"> <button class="btn btn-link" (click)="start(eventConsumer)" *ngIf="eventConsumer.isStopped" title="Start Event Consumer">
<i class="icon icon-play"></i> <i class="icon icon-play"></i>
</button> </button>
<button class="btn btn-link" (click)="stop(eventConsumer.name)" *ngIf="!eventConsumer.isStopped" title="Stop Event Consumer"> <button class="btn btn-link" (click)="stop(eventConsumer)" *ngIf="!eventConsumer.isStopped" title="Stop Event Consumer">
<i class="icon icon-pause"></i> <i class="icon icon-pause"></i>
</button> </button>
</td> </td>

23
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -42,7 +42,7 @@ export class EventConsumersPageComponent extends ComponentBase implements OnInit
public ngOnInit() { public ngOnInit() {
this.subscription = this.subscription =
Observable.timer(0, 4000) Observable.timer(0, 4000)
.switchMap(_ => this.eventConsumersService.getEventConsumers()) .switchMap(() => this.eventConsumersService.getEventConsumers())
.subscribe(dtos => { .subscribe(dtos => {
this.eventConsumers = ImmutableArray.of(dtos); this.eventConsumers = ImmutableArray.of(dtos);
}); });
@ -52,37 +52,28 @@ export class EventConsumersPageComponent extends ComponentBase implements OnInit
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
public start(name: string) { public start(consumer: EventConsumerDto) {
this.eventConsumersService.startEventConsumer(name) this.eventConsumersService.startEventConsumer(name)
.subscribe(() => { .subscribe(() => {
this.eventConsumers = this.eventConsumers = this.eventConsumers.replaceBy('name', consumer.start());
this.eventConsumers.replaceAll(
e => e.name === name,
e => new EventConsumerDto(name, false, e.isResetting, e.error, e.position));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
} }
public stop(name: string) { public stop(consumer: EventConsumerDto) {
this.eventConsumersService.stopEventConsumer(name) this.eventConsumersService.stopEventConsumer(name)
.subscribe(() => { .subscribe(() => {
this.eventConsumers = this.eventConsumers = this.eventConsumers.replaceBy('name', consumer.stop());
this.eventConsumers.replaceAll(
e => e.name === name,
e => new EventConsumerDto(name, true, e.isResetting, e.error, e.position));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
} }
public reset(name: string) { public reset(consumer: EventConsumerDto) {
this.eventConsumersService.resetEventConsumer(name) this.eventConsumersService.resetEventConsumer(name)
.subscribe(() => { .subscribe(() => {
this.eventConsumers = this.eventConsumers = this.eventConsumers.replaceBy('name', consumer.reset());
this.eventConsumers.replaceAll(
e => e.name === name,
e => new EventConsumerDto(name, e.isStopped, true, e.error, e.position));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });

22
src/Squidex/app/features/administration/pages/messages.ts

@ -0,0 +1,22 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { UserDto } from 'shared';
export class UserCreated {
constructor(
public readonly user: UserDto
) {
}
}
export class UserUpdated {
constructor(
public readonly user: UserDto
) {
}
}

25
src/Squidex/app/features/administration/pages/users/messages.ts

@ -1,25 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export class UserCreated {
constructor(
public readonly id: string,
public readonly email: string,
public readonly displayName: string,
public readonly pictureUrl: string
) {
}
}
export class UserUpdated {
constructor(
public readonly id: string,
public readonly email: string,
public readonly displayName: string
) {
}
}

49
src/Squidex/app/features/administration/pages/users/user-page.component.ts

@ -6,7 +6,7 @@
*/ */
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { import {
@ -19,7 +19,7 @@ import {
ValidatorsEx ValidatorsEx
} from 'shared'; } from 'shared';
import { UserCreated, UserUpdated } from './messages'; import { UserCreated, UserUpdated } from './../messages';
@Component({ @Component({
selector: 'sqx-user-page', selector: 'sqx-user-page',
@ -27,6 +27,8 @@ import { UserCreated, UserUpdated } from './messages';
templateUrl: './user-page.component.html' templateUrl: './user-page.component.html'
}) })
export class UserPageComponent extends ComponentBase implements OnInit { export class UserPageComponent extends ComponentBase implements OnInit {
private user: UserDto;
public currentUserId: string; public currentUserId: string;
public userFormSubmitted = false; public userFormSubmitted = false;
public userForm: FormGroup; public userForm: FormGroup;
@ -52,7 +54,9 @@ export class UserPageComponent extends ComponentBase implements OnInit {
this.route.data.map(p => p['user']) this.route.data.map(p => p['user'])
.subscribe((user: UserDto) => { .subscribe((user: UserDto) => {
this.populateForm(user); this.user = user;
this.populateForm();
}); });
} }
@ -79,12 +83,14 @@ export class UserPageComponent extends ComponentBase implements OnInit {
if (this.isNewMode) { if (this.isNewMode) {
this.userManagementService.postUser(requestDto) this.userManagementService.postUser(requestDto)
.subscribe(created => { .subscribe(created => {
this.messageBus.publish( this.user =
new UserCreated( new UserDto(
created.id, created.id,
requestDto.email, requestDto.email,
requestDto.displayName, requestDto.displayName,
created.pictureUrl!)); created.pictureUrl!,
false);
this.messageBus.publish(new UserCreated(this.user));
this.notifyInfo('User created successfully.'); this.notifyInfo('User created successfully.');
back(); back();
@ -94,11 +100,12 @@ export class UserPageComponent extends ComponentBase implements OnInit {
} else { } else {
this.userManagementService.putUser(this.userId, requestDto) this.userManagementService.putUser(this.userId, requestDto)
.subscribe(() => { .subscribe(() => {
this.messageBus.publish( this.user =
new UserUpdated( this.user.update(
this.userId,
requestDto.email, requestDto.email,
requestDto.displayName)); requestDto.displayMessage);
this.messageBus.publish(new UserUpdated(this.user));
this.notifyInfo('User saved successfully.'); this.notifyInfo('User saved successfully.');
enable(); enable();
@ -109,10 +116,10 @@ export class UserPageComponent extends ComponentBase implements OnInit {
} }
} }
private populateForm(user: UserDto) { private populateForm() {
const input = user || {}; const input = this.user || {};
this.isNewMode = !user; this.isNewMode = !this.user;
this.userId = input['id']; this.userId = input['id'];
this.userFormError = ''; this.userFormError = '';
this.userFormSubmitted = false; this.userFormSubmitted = false;
@ -128,17 +135,17 @@ export class UserPageComponent extends ComponentBase implements OnInit {
[ [
Validators.required, Validators.required,
Validators.maxLength(100) Validators.maxLength(100)
]],
password: ['',
[
this.isNewMode ? Validators.required : Validators.nullValidator
]],
passwordConfirm: ['',
[
ValidatorsEx.match('password', 'Passwords must be the same.')
]] ]]
}); });
if (user) {
this.userForm.addControl('password', new FormControl(''));
} else {
this.userForm.addControl('password', new FormControl('', Validators.required));
}
this.userForm.addControl('passwordConfirm', new FormControl('', ValidatorsEx.match('password', 'Passwords must be the same.')));
this.isCurrentUser = this.userId === this.currentUserId; this.isCurrentUser = this.userId === this.currentUserId;
} }
} }

4
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -70,10 +70,10 @@
</td> </td>
<td class="text-right"> <td class="text-right">
<span *ngIf="user.id !== currentUserId"> <span *ngIf="user.id !== currentUserId">
<button class="btn btn-link" (click)="lock(user.id); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User"> <button class="btn btn-link" (click)="lock(user); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User">
<i class="icon icon-unlocked"></i> <i class="icon icon-unlocked"></i>
</button> </button>
<button class="btn btn-link" (click)="unlock(user.id); $event.stopPropagation();" *ngIf="user.isLocked" title="Unlock User"> <button class="btn btn-link" (click)="unlock(user); $event.stopPropagation();" *ngIf="user.isLocked" title="Unlock User">
<i class="icon icon-lock"></i> <i class="icon icon-lock"></i>
</button> </button>
</span> </span>

29
src/Squidex/app/features/administration/pages/users/users-page.component.ts

@ -20,7 +20,7 @@ import {
UserManagementService UserManagementService
} from 'shared'; } from 'shared';
import { UserCreated, UserUpdated } from './messages'; import { UserCreated, UserUpdated } from './../messages';
@Component({ @Component({
selector: 'sqx-users-page', selector: 'sqx-users-page',
@ -55,19 +55,14 @@ export class UsersPageComponent extends ComponentBase implements OnDestroy, OnIn
this.userCreatedSubscription = this.userCreatedSubscription =
this.messageBus.of(UserCreated) this.messageBus.of(UserCreated)
.subscribe(message => { .subscribe(message => {
const user = new UserDto(message.id, message.email, message.displayName, message.pictureUrl, false); this.usersItems = this.usersItems.pushFront(message.user);
this.usersItems = this.usersItems.pushFront(user);
this.usersPager = this.usersPager.incrementCount(); this.usersPager = this.usersPager.incrementCount();
}); });
this.userUpdatedSubscription = this.userUpdatedSubscription =
this.messageBus.of(UserUpdated) this.messageBus.of(UserUpdated)
.subscribe(message => { .subscribe(message => {
this.usersItems = this.usersItems = this.usersItems.replaceBy('id', message.user);
this.usersItems.replaceAll(
u => u.id === message.id,
u => new UserDto(u.id, message.email, message.displayName, u.pictureUrl, u.isLocked));
}); });
this.currentUserId = this.authService.user!.id; this.currentUserId = this.authService.user!.id;
@ -96,25 +91,19 @@ export class UsersPageComponent extends ComponentBase implements OnDestroy, OnIn
}); });
} }
public lock(id: string) { public lock(user: UserDto) {
this.userManagementService.lockUser(id) this.userManagementService.lockUser(user.id)
.subscribe(() => { .subscribe(() => {
this.usersItems = this.usersItems = this.usersItems.replaceBy('id', user.lock());
this.usersItems.replaceAll(
u => u.id === id,
u => new UserDto(u.id, u.email, u.displayName, u.pictureUrl, true));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
} }
public unlock(id: string) { public unlock(user: UserDto) {
this.userManagementService.unlockUser(id) this.userManagementService.unlockUser(user.id)
.subscribe(() => { .subscribe(() => {
this.usersItems = this.usersItems = this.usersItems.replaceBy('id', user.unlock());
this.usersItems.replaceAll(
u => u.id === id,
u => new UserDto(u.id, u.email, u.displayName, u.pictureUrl, false));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });

5
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -50,10 +50,7 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
this.messageBus.of(AssetUpdated) this.messageBus.of(AssetUpdated)
.subscribe(event => { .subscribe(event => {
if (event.sender !== this) { if (event.sender !== this) {
this.assetsItems = this.assetsItems = this.assetsItems.replaceBy('id', event.assetDto);
this.assetsItems.replaceAll(
a => a.id === event.assetDto.id,
a => event.assetDto);
} }
}); });

31
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -21,6 +21,7 @@ import {
AppLanguageDto, AppLanguageDto,
AppsStoreService, AppsStoreService,
allData, allData,
AuthService,
CanComponentDeactivate, CanComponentDeactivate,
ContentDto, ContentDto,
ContentsService, ContentsService,
@ -44,6 +45,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
private contentDeletedSubscription: Subscription; private contentDeletedSubscription: Subscription;
private version: Version = new Version(''); private version: Version = new Version('');
private cancelPromise: Subject<boolean> | null = null; private cancelPromise: Subject<boolean> | null = null;
private content: ContentDto;
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
@ -58,6 +60,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
public languages: AppLanguageDto[] = []; public languages: AppLanguageDto[] = [];
constructor(apps: AppsStoreService, notifications: NotificationService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly authService: AuthService,
private readonly contentsService: ContentsService, private readonly contentsService: ContentsService,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly router: Router, private readonly router: Router,
@ -78,7 +81,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
this.contentDeletedSubscription = this.contentDeletedSubscription =
this.messageBus.of(ContentDeleted) this.messageBus.of(ContentDeleted)
.subscribe(message => { .subscribe(message => {
if (message.id === this.contentId) { if (message.contentId === this.contentId) {
this.router.navigate(['../'], { relativeTo: this.route }); this.router.navigate(['../'], { relativeTo: this.route });
} }
}); });
@ -87,7 +90,9 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
this.route.data.map(p => p['content']) this.route.data.map(p => p['content'])
.subscribe((content: ContentDto) => { .subscribe((content: ContentDto) => {
this.populateForm(content); this.content = content;
this.populateForm();
}); });
} }
@ -142,8 +147,10 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
if (this.isNewMode) { if (this.isNewMode) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.contentsService.postContent(app, this.schema.name, requestDto, publish, this.version)) .switchMap(app => this.contentsService.postContent(app, this.schema.name, requestDto, publish, this.version))
.subscribe(created => { .subscribe(dto => {
this.messageBus.publish(new ContentCreated(created.id, created.data, this.version.value, publish)); this.content = dto;
this.messageBus.publish(new ContentCreated(dto));
this.notifyInfo('Content created successfully.'); this.notifyInfo('Content created successfully.');
back(); back();
@ -155,7 +162,9 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.contentsService.putContent(app, this.schema.name, this.contentId!, requestDto, this.version)) .switchMap(app => this.contentsService.putContent(app, this.schema.name, this.contentId!, requestDto, this.version))
.subscribe(() => { .subscribe(() => {
this.messageBus.publish(new ContentUpdated(this.contentId!, requestDto, this.version.value)); this.content = this.content.update(requestDto, this.authService.user.token);
this.messageBus.publish(new ContentUpdated(this.content));
this.notifyInfo('Content saved successfully.'); this.notifyInfo('Content saved successfully.');
this.enable(); this.enable();
@ -205,23 +214,23 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
this.contentForm = new FormGroup(controls); this.contentForm = new FormGroup(controls);
} }
private populateForm(content: ContentDto) { private populateForm() {
this.contentForm.markAsPristine(); this.contentForm.markAsPristine();
if (!content) { if (!this.content) {
this.contentData = null; this.contentData = null;
this.contentId = null; this.contentId = null;
this.isNewMode = true; this.isNewMode = true;
return; return;
} }
this.contentData = content.data; this.contentData = this.content.data;
this.contentId = content.id; this.contentId = this.content.id;
this.version = content.version; this.version = this.content.version;
this.isNewMode = false; this.isNewMode = false;
for (const field of this.schema.fields) { for (const field of this.schema.fields) {
const fieldValue = content.data[field.name] || {}; const fieldValue = this.content.data[field.name] || {};
const fieldForm = <FormGroup>this.contentForm.get(field.name); const fieldForm = <FormGroup>this.contentForm.get(field.name);
if (field.partitioning === 'language') { if (field.partitioning === 'language') {

47
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -24,14 +24,12 @@ import {
AuthService, AuthService,
ContentDto, ContentDto,
ContentsService, ContentsService,
DateTime,
FieldDto, FieldDto,
ImmutableArray, ImmutableArray,
MessageBus, MessageBus,
NotificationService, NotificationService,
Pager, Pager,
SchemaDetailsDto, SchemaDetailsDto
Version
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -81,14 +79,14 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.contentCreatedSubscription = this.contentCreatedSubscription =
this.messageBus.of(ContentCreated) this.messageBus.of(ContentCreated)
.subscribe(message => { .subscribe(message => {
this.contentItems = this.contentItems.pushFront(this.createContent(message.id, message.data, message.version, message.isPublished)); this.contentItems = this.contentItems.pushFront(message.content);
this.contentsPager = this.contentsPager.incrementCount(); this.contentsPager = this.contentsPager.incrementCount();
}); });
this.contentUpdatedSubscription = this.contentUpdatedSubscription =
this.messageBus.of(ContentUpdated) this.messageBus.of(ContentUpdated)
.subscribe(message => { .subscribe(message => {
this.updateContents(message.id, undefined, message.data, message.version); this.contentItems = this.contentItems.replaceBy('id', message.content, (o, n) => o.update(n.data, n.lastModifiedBy));
}); });
this.route.params.map(p => <string> p['language']) this.route.params.map(p => <string> p['language'])
@ -127,7 +125,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version)) .switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => { .subscribe(() => {
this.updateContents(content.id, true, content.data, content.version.value); this.contentItems = this.contentItems.replaceBy('id', content.publish(this.authService.user.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -137,7 +135,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version)) .switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => { .subscribe(() => {
this.updateContents(content.id, false, content.data, content.version.value); this.contentItems = this.contentItems.replaceBy('id', content.unpublish(this.authService.user.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -204,40 +202,5 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.load(); this.load();
} }
private updateContents(id: string, p: boolean | undefined, data: any, version: string) {
this.contentItems = this.contentItems.replaceAll(x => x.id === id, c => this.updateContent(c, p === undefined ? c.isPublished : p, data, version));
}
private createContent(id: string, data: any, version: string, isPublished: boolean): ContentDto {
const me = `subject:${this.authService.user!.id}`;
const newContent =
new ContentDto(
id,
isPublished,
me, me,
DateTime.now(),
DateTime.now(),
data,
new Version(version));
return newContent;
}
private updateContent(content: ContentDto, isPublished: boolean, data: any, version: string): ContentDto {
const me = `subject:${this.authService.user!.id}`;
const newContent =
new ContentDto(
content.id,
isPublished,
content.createdBy, me,
content.created, DateTime.now(),
data,
new Version(version));
return newContent;
}
} }

13
src/Squidex/app/features/content/pages/messages.ts

@ -5,28 +5,25 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { ContentDto } from 'shared';
export class ContentCreated { export class ContentCreated {
constructor( constructor(
public readonly id: string, public readonly content: ContentDto
public readonly data: any,
public readonly version: string,
public readonly isPublished: boolean
) { ) {
} }
} }
export class ContentUpdated { export class ContentUpdated {
constructor( constructor(
public readonly id: string, public readonly content: ContentDto
public readonly data: any,
public readonly version: string
) { ) {
} }
} }
export class ContentDeleted { export class ContentDeleted {
constructor( constructor(
public readonly id: string public readonly contentId: string
) { ) {
} }
} }

5
src/Squidex/app/features/content/shared/assets-editor.component.ts

@ -56,10 +56,7 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
this.messageBus.of(AssetUpdated) this.messageBus.of(AssetUpdated)
.subscribe(event => { .subscribe(event => {
if (event.sender !== this) { if (event.sender !== this) {
this.oldAssets = this.oldAssets = this.oldAssets.replaceBy('id', event.assetDto);
this.oldAssets.replaceAll(
a => a.id === event.assetDto.id,
a => event.assetDto);
} }
}); });
} }

9
src/Squidex/app/features/schemas/pages/messages.ts

@ -5,21 +5,18 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { SchemaPropertiesDto } from 'shared'; import { SchemaDto } from 'shared';
export class SchemaUpdated { export class SchemaUpdated {
constructor( constructor(
public readonly name: string, public readonly schema: SchemaDto
public readonly properties: SchemaPropertiesDto,
public readonly isPublished: boolean,
public readonly version: string
) { ) {
} }
} }
export class SchemaDeleted { export class SchemaDeleted {
constructor( constructor(
public readonly name: string public readonly schemaId: string
) { ) {
} }
} }

10
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="{app} | {schema}" parameter1="app" value1="{{appName() | async}}" parameter2="schema" value2="{{schemaName}}"></sqx-title> <sqx-title message="{app} | {schema}" parameter1="app" value1="{{appName() | async}}" parameter2="schema" value2="{{schema.name}}"></sqx-title>
<sqx-panel panelWidth="48rem"> <sqx-panel panelWidth="48rem">
<div class="panel-header"> <div class="panel-header">
@ -9,10 +9,10 @@
</button> </button>
<div class="btn-group btn-group-sm" data-toggle="buttons"> <div class="btn-group btn-group-sm" data-toggle="buttons">
<button type="button" class="btn btn-publishing btn-secondary" [class.btn-success]="isPublished" [disabled]="isPublished" (click)="publish()"> <button type="button" class="btn btn-publishing btn-secondary" [class.btn-success]="schema.isPublished" [disabled]="schema.isPublished" (click)="publish()">
Published Published
</button> </button>
<button type="button" class="btn btn-publishing btn-secondary" [class.btn-danger]="!isPublished" [disabled]="!isPublished" (click)="unpublish()"> <button type="button" class="btn btn-publishing btn-secondary" [class.btn-danger]="!schema.isPublished" [disabled]="!schema.isPublished" (click)="unpublish()">
Unpublished Unpublished
</button> </button>
</div> </div>
@ -48,7 +48,7 @@
(enabling)="enableField(field)" (enabling)="enableField(field)"
(showing)="showField(field)" (showing)="showField(field)"
(hiding)="hideField(field)" (hiding)="hideField(field)"
(saving)="saveField(field, $event)"></sqx-field> (saving)="saveField($event)"></sqx-field>
</div> </div>
<div class="table-items-footer"> <div class="table-items-footer">
@ -134,7 +134,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<sqx-schema-edit-form [appName]="appName() | async" [name]="schemaName" [properties]="schemaProperties" [version]="schemaVersion" (saved)="onSchemaSaved($event)" (cancelled)="editSchemaDialog.hide()"></sqx-schema-edit-form> <sqx-schema-edit-form [appName]="appName() | async" [name]="schema.name" [properties]="schema.properties" [version]="schema.version" (saved)="onSchemaSaved($event)" (cancelled)="editSchemaDialog.hide()"></sqx-schema-edit-form>
</div> </div>
</div> </div>
</div> </div>

126
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -13,12 +13,12 @@ import {
AddFieldDto, AddFieldDto,
AppComponentBase, AppComponentBase,
AppsStoreService, AppsStoreService,
AuthService,
createProperties, createProperties,
fadeAnimation, fadeAnimation,
FieldDto, FieldDto,
fieldTypes, fieldTypes,
HistoryChannelUpdated, HistoryChannelUpdated,
ImmutableArray,
MessageBus, MessageBus,
ModalView, ModalView,
NotificationService, NotificationService,
@ -27,8 +27,7 @@ import {
SchemaPropertiesDto, SchemaPropertiesDto,
SchemasService, SchemasService,
UpdateFieldDto, UpdateFieldDto,
ValidatorsEx, ValidatorsEx
Version
} from 'shared'; } from 'shared';
import { SchemaDeleted, SchemaUpdated } from './../messages'; import { SchemaDeleted, SchemaUpdated } from './../messages';
@ -45,11 +44,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public fieldTypes = fieldTypes; public fieldTypes = fieldTypes;
public schemaExport: any; public schemaExport: any;
public schemaName: string; public schema: SchemaDetailsDto;
public schemaFields = ImmutableArray.empty<FieldDto>();
public schemaVersion = new Version('');
public schemaProperties: SchemaPropertiesDto;
public schemaInformation: any;
public schemas: SchemaDto[]; public schemas: SchemaDto[];
public confirmDeleteDialog = new ModalView(); public confirmDeleteDialog = new ModalView();
@ -59,8 +54,6 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public editOptionsDropdown = new ModalView(); public editOptionsDropdown = new ModalView();
public editSchemaDialog = new ModalView(); public editSchemaDialog = new ModalView();
public isPublished: boolean;
public addFieldFormSubmitted = false; public addFieldFormSubmitted = false;
public addFieldForm: FormGroup = public addFieldForm: FormGroup =
this.formBuilder.group({ this.formBuilder.group({
@ -81,11 +74,12 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
} }
constructor(apps: AppsStoreService, notifications: NotificationService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly schemasService: SchemasService, private readonly authService: AuthService,
private readonly messageBus: MessageBus,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly router: Router private readonly router: Router,
private readonly schemasService: SchemasService
) { ) {
super(notifications, apps); super(notifications, apps);
} }
@ -93,13 +87,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public ngOnInit() { public ngOnInit() {
this.route.data.map(p => p['schema']) this.route.data.map(p => p['schema'])
.subscribe((schema: SchemaDetailsDto) => { .subscribe((schema: SchemaDetailsDto) => {
this.schemaName = schema.name; this.schema = schema;
this.schemaFields = ImmutableArray.of(schema.fields);
this.schemaVersion = schema.version;
this.schemaProperties = schema.properties;
this.schemaInformation = { properties: schema.properties, name: schema.name };
this.isPublished = schema.isPublished;
this.export(); this.export();
}); });
@ -119,10 +107,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public publish() { public publish() {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.publishSchema(app, this.schemaName, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.publishSchema(app, this.schema.name, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.isPublished = true; this.updateSchema(this.schema.publish(this.authService.user.token));
this.notify();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -130,10 +117,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public unpublish() { public unpublish() {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.unpublishSchema(app, this.schemaName, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.unpublishSchema(app, this.schema.name, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.isPublished = false; this.updateSchema(this.schema.unpublish(this.authService.user.token));
this.notify();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -141,9 +127,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public enableField(field: FieldDto) { public enableField(field: FieldDto) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.enableField(app, this.schemaName, field.fieldId, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.enableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, field.isHidden, false, field.partitioning, field.properties)); this.updateSchema(this.schema.updateField(this.authService.user.token, field.enable()));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -151,9 +137,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public disableField(field: FieldDto) { public disableField(field: FieldDto) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.disableField(app, this.schemaName, field.fieldId, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.disableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, field.isHidden, true, field.partitioning, field.properties)); this.updateSchema(this.schema.updateField(this.authService.user.token, field.disable()));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -161,9 +147,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public showField(field: FieldDto) { public showField(field: FieldDto) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.showField(app, this.schemaName, field.fieldId, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.showField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, false, field.isDisabled, field.partitioning, field.properties)); this.updateSchema(this.schema.updateField(this.authService.user.token, field.show()));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -171,9 +157,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public hideField(field: FieldDto) { public hideField(field: FieldDto) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.hideField(app, this.schemaName, field.fieldId, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.hideField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, true, field.isDisabled, field.partitioning, field.properties)); this.updateSchema(this.schema.updateField(this.authService.user.token, field.hide()));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -181,33 +167,31 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public deleteField(field: FieldDto) { public deleteField(field: FieldDto) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.deleteField(app, this.schemaName, field.fieldId, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.deleteField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateFields(this.schemaFields.remove(field)); this.updateSchema(this.schema.removeField(this.authService.user.token, field));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
} }
public sortFields(fields: FieldDto[]) { public sortFields(fields: FieldDto[]) {
this.updateFields(ImmutableArray.of(fields));
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.putFieldOrdering(app, this.schemaName, fields.map(t => t.fieldId), this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.putFieldOrdering(app, this.schema.name, fields.map(t => t.fieldId), this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateFields(ImmutableArray.of(fields)); this.updateSchema(this.schema.replaceFields(this.authService.user.token, fields));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
} }
public saveField(field: FieldDto, newField: FieldDto) { public saveField(field: FieldDto) {
const requestDto = new UpdateFieldDto(newField.properties); const requestDto = new UpdateFieldDto(field.properties);
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.putField(app, this.schemaName, field.fieldId, requestDto, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.putField(app, this.schema.name, field.fieldId, requestDto, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, newField.isHidden, field.isDisabled, field.partitioning, newField.properties)); this.updateSchema(this.schema.updateField(this.authService.user.token, field));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -215,16 +199,15 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
public deleteSchema() { public deleteSchema() {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.deleteSchema(app, this.schemaName, this.schemaVersion)).retry(2) .switchMap(app => this.schemasService.deleteSchema(app, this.schema.name, this.schema.version)).retry(2)
.finally(() => {
this.confirmDeleteDialog.hide();
})
.subscribe(() => { .subscribe(() => {
this.messageBus.publish(new SchemaDeleted(this.schemaName)); this.messageBus.publish(new SchemaDeleted(this.schema.id));
this.router.navigate(['../'], { relativeTo: this.route }); this.router.navigate(['../'], { relativeTo: this.route });
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}, () => {
this.confirmDeleteDialog.hide();
}); });
} }
@ -246,17 +229,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}; };
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.postField(app, this.schemaName, requestDto, this.schemaVersion)) .switchMap(app => this.schemasService.postField(app, this.schema.name, requestDto, this.schema.version))
.subscribe(dto => { .subscribe(dto => {
const newField = this.updateSchema(this.schema.addField(this.authService.user.token, dto));
new FieldDto(parseInt(dto.id, 10),
requestDto.name,
false,
false,
requestDto.partitioning,
requestDto.properties);
this.updateFields(this.schemaFields.push(newField));
reset(); reset();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
@ -271,28 +246,13 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
} }
public onSchemaSaved(properties: SchemaPropertiesDto) { public onSchemaSaved(properties: SchemaPropertiesDto) {
this.updateProperties(properties); this.updateSchema(this.schema.update(this.authService.user.token, properties));
this.editSchemaDialog.hide(); this.editSchemaDialog.hide();
} }
private updateProperties(properties: SchemaPropertiesDto) { private updateSchema(schema: SchemaDetailsDto) {
this.schemaProperties = properties; this.schema = schema;
this.schemaInformation = { properties: properties, name: this.schemaName };
this.notify();
this.export();
}
private updateField(field: FieldDto, newField: FieldDto) {
this.schemaFields = this.schemaFields.replace(field, newField);
this.notify();
this.export();
}
private updateFields(fields: ImmutableArray<FieldDto>) {
this.schemaFields = fields;
this.notify(); this.notify();
this.export(); this.export();
@ -300,7 +260,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
private export() { private export() {
const result: any = { const result: any = {
fields: this.schemaFields.values.map(field => { fields: this.schema.fields.map(field => {
const copy: any = Object.assign({}, field); const copy: any = Object.assign({}, field);
delete copy.fieldId; delete copy.fieldId;
@ -318,12 +278,12 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
properties: {} properties: {}
}; };
if (this.schemaProperties.label) { if (this.schema.properties.label) {
result.properties.label = this.schemaProperties.label; result.properties.label = this.schema.properties.label;
} }
if (this.schemaProperties.hints) { if (this.schema.properties.hints) {
result.properties.hints = this.schemaProperties.hints; result.properties.hints = this.schema.properties.hints;
} }
this.schemaExport = result; this.schemaExport = result;
@ -331,7 +291,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
private notify() { private notify() {
this.messageBus.publish(new HistoryChannelUpdated()); this.messageBus.publish(new HistoryChannelUpdated());
this.messageBus.publish(new SchemaUpdated(this.schemaName, this.schemaProperties, this.isPublished, this.schemaVersion.value)); this.messageBus.publish(new SchemaUpdated(this.schema));
} }
} }

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

@ -11,10 +11,8 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { import {
ApiUrlConfig, ApiUrlConfig,
AuthService, AuthService,
DateTime,
fadeAnimation, fadeAnimation,
SchemaDto, SchemaDto,
SchemaPropertiesDto,
SchemasService, SchemasService,
ValidatorsEx, ValidatorsEx,
Version Version
@ -74,7 +72,7 @@ export class SchemaFormComponent {
} }
public cancel() { public cancel() {
this.reset(); this.resetForm();
this.cancelled.emit(); this.cancelled.emit();
} }
@ -87,14 +85,14 @@ export class SchemaFormComponent {
const schemaVersion = new Version(); const schemaVersion = new Version();
const schemaName = this.createForm.controls['name'].value; const schemaName = this.createForm.controls['name'].value;
const requestDto = Object.assign(this.createForm.controls['import'].value || {}, {}); const requestDto = Object.assign(this.createForm.controls['import'].value || {}, { name: schemaName });
requestDto.name = schemaName; const me = this.authService.user!.token;
this.schemas.postSchema(this.appName, requestDto, schemaVersion) this.schemas.postSchema(this.appName, requestDto, me, undefined, schemaVersion)
.subscribe(dto => { .subscribe(dto => {
this.reset(); this.resetForm();
this.created.emit(this.createSchemaDto(dto.id, requestDto.properties || {}, schemaName, schemaVersion)); this.created.emit(dto);
}, error => { }, error => {
this.createForm.enable(); this.createForm.enable();
this.createFormError = error.displayMessage; this.createFormError = error.displayMessage;
@ -102,16 +100,10 @@ export class SchemaFormComponent {
} }
} }
private reset() { private resetForm() {
this.createFormError = ''; this.createFormError = '';
this.createForm.reset(); this.createForm.reset();
this.createFormSubmitted = false; this.createFormSubmitted = false;
} }
private createSchemaDto(id: string, properties: SchemaPropertiesDto, name: string, version: Version) {
const user = this.authService.user!.token;
const now = DateTime.now();
return new SchemaDto(id, name, properties, false, user, user, now, now, version);
}
} }

23
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -13,16 +13,13 @@ import { Subscription } from 'rxjs';
import { import {
AppComponentBase, AppComponentBase,
AppsStoreService, AppsStoreService,
AuthService,
DateTime,
fadeAnimation, fadeAnimation,
ImmutableArray, ImmutableArray,
MessageBus, MessageBus,
ModalView, ModalView,
NotificationService, NotificationService,
SchemaDto, SchemaDto,
SchemasService, SchemasService
Version
} from 'shared'; } from 'shared';
import { SchemaDeleted, SchemaUpdated } from './../messages'; import { SchemaDeleted, SchemaUpdated } from './../messages';
@ -48,7 +45,6 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
constructor(apps: AppsStoreService, notifications: NotificationService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,
private readonly authService: AuthService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute private readonly route: ActivatedRoute
) { ) {
@ -78,13 +74,13 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
this.schemaUpdatedSubscription = this.schemaUpdatedSubscription =
this.messageBus.of(SchemaUpdated) this.messageBus.of(SchemaUpdated)
.subscribe(m => { .subscribe(m => {
this.updateSchemas(this.schemas.replaceAll(s => s.name === m.name, s => updateSchema(s, this.authService, m))); this.updateSchemas(this.schemas.replaceBy('id', m.schema));
}); });
this.schemaDeletedSubscription = this.schemaDeletedSubscription =
this.messageBus.of(SchemaDeleted) this.messageBus.of(SchemaDeleted)
.subscribe(m => { .subscribe(m => {
this.updateSchemas(this.schemas.filter(s => s.name !== m.name)); this.updateSchemas(this.schemas.filter(s => s.id !== m.schemaId));
}); });
this.load(); this.load();
@ -130,16 +126,3 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
} }
} }
function updateSchema(schema: SchemaDto, authService: AuthService, message: SchemaUpdated): SchemaDto {
const me = `subject:${authService.user!.id}`;
return new SchemaDto(
schema.id,
schema.name,
message.properties,
message.isPublished,
schema.createdBy, me,
schema.created, DateTime.now(),
new Version(message.version));
}

14
src/Squidex/app/features/settings/pages/clients/clients-page.component.ts

@ -85,7 +85,7 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.appClientsService.updateClient(app, client.id, requestDto, this.version)) .switchMap(app => this.appClientsService.updateClient(app, client.id, requestDto, this.version))
.subscribe(() => { .subscribe(() => {
this.updateClients(this.appClients.replace(client, rename(client, name))); this.updateClients(this.appClients.replaceBy('id', client.rename(name)));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -97,7 +97,7 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.appClientsService.updateClient(app, client.id, requestDto, this.version)) .switchMap(app => this.appClientsService.updateClient(app, client.id, requestDto, this.version))
.subscribe(() => { .subscribe(() => {
this.updateClients(this.appClients.replace(client, change(client, isReader))); this.updateClients(this.appClients.replaceBy('id', client.change(isReader)));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -134,12 +134,4 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
this.messageBus.publish(new HistoryChannelUpdated()); this.messageBus.publish(new HistoryChannelUpdated());
} }
} }
function change(client: AppClientDto, isReader: boolean): AppClientDto {
return new AppClientDto(client.id, client.name, client.secret, isReader);
};
function rename(client: AppClientDto, name: string): AppClientDto {
return new AppClientDto(client.id, name, client.secret, client.isReader);
};

10
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -65,7 +65,8 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
public usersPermissions = [ public usersPermissions = [
'Owner', 'Owner',
'Developer', 'Developer',
'Editor' 'Editor',
'Reader'
]; ];
public get canAddContributor() { public get canAddContributor() {
@ -118,7 +119,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
} }
public changePermission(contributor: AppContributorDto, permission: string) { public changePermission(contributor: AppContributorDto, permission: string) {
const requestDto = changePermission(contributor, permission); const requestDto = contributor.changePermission(permission);
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.appContributorsService.postContributor(app, requestDto, this.version)) .switchMap(app => this.appContributorsService.postContributor(app, requestDto, this.version))
@ -155,8 +156,3 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
this.messageBus.publish(new HistoryChannelUpdated()); this.messageBus.publish(new HistoryChannelUpdated());
} }
} }
function changePermission(contributor: AppContributorDto, permission: string): AppContributorDto {
return new AppContributorDto(contributor.contributorId, permission);
}

4
src/Squidex/app/features/settings/pages/plans/plans-page.component.ts

@ -52,7 +52,6 @@ export class PlansPageComponent extends AppComponentBase implements OnInit {
.switchMap(app => this.plansService.getPlans(app, this.version).retry(2)) .switchMap(app => this.plansService.getPlans(app, this.version).retry(2))
.subscribe(dto => { .subscribe(dto => {
this.plans = dto; this.plans = dto;
this.planOwned = !dto.planOwner || (dto.planOwner === this.authService.user!.id); this.planOwned = !dto.planOwner || (dto.planOwner === this.authService.user!.id);
if (showInfo) { if (showInfo) {
@ -75,10 +74,9 @@ export class PlansPageComponent extends AppComponentBase implements OnInit {
this.plans.hasPortal, this.plans.hasPortal,
this.plans.hasConfigured, this.plans.hasConfigured,
this.plans.plans); this.plans.plans);
this.isDisabled = false;
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}, () => {
this.isDisabled = false; this.isDisabled = false;
}); });
} }

10
src/Squidex/app/features/webhooks/pages/webhooks-page.component.ts

@ -94,23 +94,23 @@ export class WebhooksPageComponent extends AppComponentBase implements OnInit {
} }
public addWebhook() { public addWebhook() {
this.addWebhookFormSubmitted = true;
if (this.addWebhookForm.valid) { if (this.addWebhookForm.valid) {
this.addWebhookFormSubmitted = true;
this.addWebhookForm.disable(); this.addWebhookForm.disable();
const requestDto = new CreateWebhookDto(this.addWebhookForm.controls['url'].value); const requestDto = new CreateWebhookDto(this.addWebhookForm.controls['url'].value);
const schemaId = this.addWebhookForm.controls['schemaId'].value; const schemaId = this.addWebhookForm.controls['schemaId'].value;
const schema = this.schemas.find(s => s.id === schemaId); const schema = this.schemas.find(s => s.id === schemaId);
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.webhooksService.postWebhook(app, schema.name, requestDto, this.version)) .switchMap(app => this.webhooksService.postWebhook(app, schema.name, requestDto, this.version))
.subscribe(dto => { .subscribe(dto => {
const webhook = new WebhookDto(dto.id, schemaId, dto.sharedSecret, requestDto.url, 0, 0, 0, 0, []); this.webhooks = this.webhooks.push({ webhook: dto, schema: schema, showDetails: false });
this.webhooks = this.webhooks.push({ schema, webhook, showDetails: false });
this.resetWebhookForm();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}, () => {
this.resetWebhookForm(); this.resetWebhookForm();
}); });
} }

7
src/Squidex/app/framework/angular/http-extensions-impl.ts

@ -10,13 +10,6 @@ import { Observable } from 'rxjs';
import { Version } from './../utils/version'; import { Version } from './../utils/version';
export class EntityCreatedDto {
constructor(
public readonly id: any
) {
}
}
export class ErrorDto { export class ErrorDto {
public get displayMessage(): string { public get displayMessage(): string {
let result = this.message; let result = this.message;

10
src/Squidex/app/framework/angular/validators.spec.ts

@ -243,3 +243,13 @@ describe('ValidatorsEx.pattern', () => {
expect(error).toEqual(expected); expect(error).toEqual(expected);
}); });
}); });
describe('ValidatorsEx.noop', () => {
it('should return null validator', () => {
const input = new FormControl(null);
const error = <any>ValidatorsEx.noop()(input);
expect(error).toBeNull();
});
});

6
src/Squidex/app/framework/angular/validators.ts

@ -128,4 +128,10 @@ export module ValidatorsEx {
return null; return null;
}; };
} }
export function noop() {
return (control: AbstractControl): { [key: string]: any } => {
return null;
};
}
} }

7
src/Squidex/app/framework/utils/immutable-array.spec.ts

@ -112,6 +112,13 @@ describe('ImmutableArray', () => {
expect(array_2.values).toEqual([1, 4, 3, 8]); expect(array_2.values).toEqual([1, 4, 3, 8]);
}); });
it('should replace by field', () => {
const array_1 = ImmutableArray.of([{ id: 1, v: 1 }, { id: 2, v: 2 }]);
const array_2 = array_1.replaceBy('id', { id: 1, v: 11 });
expect(array_2.values).toEqual([{ id: 1, v: 11 }, { id: 2, v: 2 }]);
});
it('should return original if nothing has been replace', () => { it('should return original if nothing has been replace', () => {
const array_1 = ImmutableArray.of([1, 2, 3, 4]); const array_1 = ImmutableArray.of([1, 2, 3, 4]);
const array_2 = array_1.replaceAll((i: number) => i % 200 === 0, i => i); const array_2 = array_1.replaceAll((i: number) => i % 200 === 0, i => i);

14
src/Squidex/app/framework/utils/immutable-array.ts

@ -1,3 +1,13 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export interface IdField {
id: string;
}
function freeze<T>(items: T[]): T[] { function freeze<T>(items: T[]): T[] {
for (let item of items) { for (let item of items) {
@ -152,4 +162,8 @@ export class ImmutableArray<T> implements Iterable<T> {
return hasChange ? new ImmutableArray<T>(copy) : this; return hasChange ? new ImmutableArray<T>(copy) : this;
} }
public replaceBy(field: string, newValue: T, replacer?: (o: T, n: T) => T) {
return this.replaceAll(x => x[field] === newValue[field], o => replacer ? replacer(o, newValue) : newValue);
}
} }

75
src/Squidex/app/shared/components/asset.component.ts

@ -13,12 +13,10 @@ import { AppComponentBase } from './app.component-base';
import { import {
ApiUrlConfig, ApiUrlConfig,
AppsStoreService, AppsStoreService,
AssetCreatedDto,
AssetDto, AssetDto,
AssetReplacedDto, AssetReplacedDto,
AssetsService, AssetsService,
AuthService, AuthService,
DateTime,
fadeAnimation, fadeAnimation,
FileHelper, FileHelper,
ModalView, ModalView,
@ -93,28 +91,15 @@ export class AssetComponent extends AppComponentBase implements OnInit {
const initFile = this.initFile; const initFile = this.initFile;
if (initFile) { if (initFile) {
const me = `subject:${this.authService.user!.id}`;
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.assetsService.uploadFile(app, initFile)) .switchMap(app => this.assetsService.uploadFile(app, initFile, me))
.subscribe(result => { .subscribe(dto => {
if (result instanceof AssetCreatedDto) { if (dto instanceof AssetDto) {
const me = `subject:${this.authService.user!.id}`; this.loaded.emit(dto);
const asset = new AssetDto(
result.id,
me, me,
DateTime.now(),
DateTime.now(),
result.fileName,
result.fileSize,
result.fileVersion,
result.mimeType,
result.isImage,
result.pixelWidth,
result.pixelHeight,
result.version);
this.loaded.emit(asset);
} else { } else {
this.progress = result; this.progress = dto;
} }
}, error => { }, error => {
this.failed.emit(); this.failed.emit();
@ -128,32 +113,20 @@ export class AssetComponent extends AppComponentBase implements OnInit {
public updateFile(files: FileList) { public updateFile(files: FileList) {
if (files.length === 1) { if (files.length === 1) {
const me = `subject:${this.authService.user!.id}`;
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.version)) .switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.version))
.subscribe(result => { .subscribe(dto => {
if (result instanceof AssetReplacedDto) { if (dto instanceof AssetReplacedDto) {
const me = `subject:${this.authService.user!.id}`; this.updateAsset(this.asset.update(dto, me), true);
const asset = new AssetDto(
this.asset.id,
this.asset.createdBy, me,
this.asset.created, DateTime.now(),
this.asset.fileName,
result.fileSize,
result.fileVersion,
result.mimeType,
result.isImage,
result.pixelWidth,
result.pixelHeight,
result.version);
this.updateAsset(asset, true);
} else { } else {
this.progress = result; this.progress = dto;
} }
}, error => { }, error => {
this.progress = 0;
this.notifyError(error); this.notifyError(error);
}, () => {
this.progress = 0;
}); });
} }
} }
@ -166,24 +139,12 @@ export class AssetComponent extends AppComponentBase implements OnInit {
const requestDto = new UpdateAssetDto(this.renameForm.controls['name'].value); const requestDto = new UpdateAssetDto(this.renameForm.controls['name'].value);
const me = `subject:${this.authService.user!.id}`;
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.assetsService.putAsset(app, this.asset.id, requestDto, this.version)) .switchMap(app => this.assetsService.putAsset(app, this.asset.id, requestDto, this.version))
.subscribe(() => { .subscribe(() => {
const me = `subject:${this.authService.user!.id}`; this.updateAsset(this.asset.rename(requestDto.fileName, me), true);
const asset = new AssetDto(
this.asset.id,
this.asset.createdBy, me,
this.asset.created, DateTime.now(), requestDto.fileName,
this.asset.fileSize,
this.asset.fileVersion,
this.asset.mimeType,
this.asset.isImage,
this.asset.pixelWidth,
this.asset.pixelHeight,
this.asset.version);
this.updateAsset(asset, true);
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}, () => { }, () => {

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

@ -25,6 +25,14 @@ export class AppClientDto {
public readonly isReader: boolean public readonly isReader: boolean
) { ) {
} }
public rename(name: string): AppClientDto {
return new AppClientDto(this.id, name, this.secret, this.isReader);
}
public change(isReader: boolean): AppClientDto {
return new AppClientDto(this.id, this.name, this.secret, isReader);
}
} }
export class CreateAppClientDto { export class CreateAppClientDto {

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

@ -23,6 +23,10 @@ export class AppContributorDto {
public readonly permission: string public readonly permission: string
) { ) {
} }
public changePermission(permission: string): AppContributorDto {
return new AppContributorDto(this.contributorId, permission);
}
} }
export class AppContributorsDto { export class AppContributorsDto {

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

@ -26,6 +26,10 @@ export class AppLanguageDto {
public readonly fallback: string[] public readonly fallback: string[]
) { ) {
} }
public update(isMaster: boolean, isOptional: boolean, fallback: string[]): AppLanguageDto {
return new AppLanguageDto(this.iso2Code, this.englishName, isMaster, isOptional, fallback);
}
} }
export class AddAppLanguageDto { export class AddAppLanguageDto {

11
src/Squidex/app/shared/services/apps.service.spec.ts

@ -13,11 +13,12 @@ import {
AppDto, AppDto,
AppsService, AppsService,
CreateAppDto, CreateAppDto,
DateTime, DateTime
EntityCreatedDto
} from './../'; } from './../';
describe('AppsService', () => { describe('AppsService', () => {
const now = DateTime.now();
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
@ -76,10 +77,10 @@ describe('AppsService', () => {
const dto = new CreateAppDto('new-app'); const dto = new CreateAppDto('new-app');
let created: EntityCreatedDto | null = null; let app: AppDto | null = null;
appsService.postApp(dto).subscribe(result => { appsService.postApp(dto).subscribe(result => {
created = result; app = result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps'); const req = httpMock.expectOne('http://service/p/api/apps');
@ -89,6 +90,6 @@ describe('AppsService', () => {
req.flush({ id: '123' }); req.flush({ id: '123' });
expect(created).toEqual(new EntityCreatedDto('123')); expect(app).toEqual(new AppDto('123', dto.name, 'Owner', now, now));
})); }));
}); });

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

@ -14,8 +14,7 @@ import 'framework/angular/http-extensions';
import { import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP, HTTP
EntityCreatedDto
} from 'framework'; } from 'framework';
export class AppDto { export class AppDto {
@ -63,12 +62,14 @@ export class AppsService {
.pretifyError('Failed to load apps. Please reload.'); .pretifyError('Failed to load apps. Please reload.');
} }
public postApp(dto: CreateAppDto): Observable<EntityCreatedDto> { public postApp(dto: CreateAppDto, now?: DateTime): Observable<AppDto> {
const url = this.apiUrl.buildUrl('api/apps'); const url = this.apiUrl.buildUrl('api/apps');
return HTTP.postVersioned(this.http, url, dto) return HTTP.postVersioned(this.http, url, dto)
.map(response => { .map(response => {
return new EntityCreatedDto(response.id); now = now || DateTime.now();
return new AppDto(response.id, dto.name, 'Owner', now, now);
}) })
.pretifyError('Failed to create app. Please reload.'); .pretifyError('Failed to create app. Please reload.');
} }

15
src/Squidex/app/shared/services/assets.service.spec.ts

@ -12,7 +12,6 @@ import {
ApiUrlConfig, ApiUrlConfig,
AssetsDto, AssetsDto,
AssetDto, AssetDto,
AssetCreatedDto,
AssetReplacedDto, AssetReplacedDto,
AssetsService, AssetsService,
DateTime, DateTime,
@ -21,6 +20,8 @@ import {
} from './../'; } from './../';
describe('AssetsService', () => { describe('AssetsService', () => {
let now = DateTime.now();
let user = 'me';
let version = new Version('1'); let version = new Version('1');
beforeEach(() => { beforeEach(() => {
@ -204,10 +205,10 @@ describe('AssetsService', () => {
it('should make post request to create asset', it('should make post request to create asset',
inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => { inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => {
let asset: AssetCreatedDto | null = null; let asset: AssetDto | null = null;
assetsService.uploadFile('my-app', null!).subscribe(result => { assetsService.uploadFile('my-app', null!, user).subscribe(result => {
asset = <AssetCreatedDto>result; asset = <AssetDto>result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets');
@ -228,8 +229,12 @@ describe('AssetsService', () => {
}); });
expect(asset).toEqual( expect(asset).toEqual(
new AssetCreatedDto( new AssetDto(
'id1', 'id1',
user,
user,
now,
now,
'my-asset1.png', 'my-asset1.png',
1024, 2, 1024, 2,
'text/plain', 'text/plain',

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

@ -41,26 +41,40 @@ export class AssetDto {
public readonly version: Version public readonly version: Version
) { ) {
} }
}
export class UpdateAssetDto { public update(result: AssetReplacedDto, user: string): AssetDto {
constructor( return new AssetDto(
public readonly fileName: string this.id,
) { this.createdBy, user,
this.created, DateTime.now(),
this.fileName,
result.fileSize,
result.fileVersion,
result.mimeType,
result.isImage,
result.pixelWidth,
result.pixelHeight,
result.version)
}
public rename(name: string, user: string): AssetDto {
return new AssetDto(
this.id,
this.createdBy, user,
this.created, DateTime.now(), name,
this.fileSize,
this.fileVersion,
this.mimeType,
this.isImage,
this.pixelWidth,
this.pixelHeight,
this.version);
} }
} }
export class AssetCreatedDto { export class UpdateAssetDto {
constructor( constructor(
public readonly id: string, public readonly fileName: string
public readonly fileName: string,
public readonly fileSize: number,
public readonly fileVersion: number,
public readonly mimeType: string,
public readonly isImage: boolean,
public readonly pixelWidth: number | null,
public readonly pixelHeight: number | null,
public readonly version: Version
) { ) {
} }
} }
@ -132,8 +146,8 @@ export class AssetsService {
.pretifyError('Failed to load assets. Please reload.'); .pretifyError('Failed to load assets. Please reload.');
} }
public uploadFile(appName: string, file: File): Observable<number | AssetCreatedDto> { public uploadFile(appName: string, file: File, user: string, now?: DateTime): Observable<number | AssetDto> {
return new Observable<number | AssetCreatedDto>(subscriber => { return new Observable<number | AssetDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets`);
const req = new HttpRequest('POST', url, getFormData(file), { const req = new HttpRequest('POST', url, getFormData(file), {
@ -150,8 +164,14 @@ export class AssetsService {
} else if (event instanceof HttpResponse) { } else if (event instanceof HttpResponse) {
const response = event.body; const response = event.body;
const dto = new AssetCreatedDto( now = now || DateTime.now();
const dto = new AssetDto(
response.id, response.id,
user,
user,
now,
now,
response.fileName, response.fileName,
response.fileSize, response.fileSize,
response.fileVersion, response.fileVersion,

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

@ -38,6 +38,36 @@ export class ContentDto {
public readonly version: Version public readonly version: Version
) { ) {
} }
public publish(user: string): ContentDto {
return new ContentDto(
this.id,
true,
this.createdBy, user,
this.created, DateTime.now(),
this.data,
this.version);
}
public unpublish(user: string): ContentDto {
return new ContentDto(
this.id,
false,
this.createdBy, user,
this.created, DateTime.now(),
this.data,
this.version);
}
public update(data: any, user: string): ContentDto {
return new ContentDto(
this.id,
this.isPublished,
this.createdBy, user,
this.created, DateTime.now(),
data,
this.version);
}
} }
@Injectable() @Injectable()

12
src/Squidex/app/shared/services/event-consumers.service.ts

@ -22,6 +22,18 @@ export class EventConsumerDto {
public readonly position: string public readonly position: string
) { ) {
} }
public start(): EventConsumerDto {
return new EventConsumerDto(this.name, false, false, this.error, this.position);
}
public stop(): EventConsumerDto {
return new EventConsumerDto(this.name, true, false, this.error, this.position);
}
public reset(): EventConsumerDto {
return new EventConsumerDto(this.name, this.isStopped, true, this.error, this.position);
}
} }
@Injectable() @Injectable()

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

@ -14,7 +14,6 @@ import {
CreateSchemaDto, CreateSchemaDto,
createProperties, createProperties,
DateTime, DateTime,
EntityCreatedDto,
FieldDto, FieldDto,
SchemaDetailsDto, SchemaDetailsDto,
SchemaDto, SchemaDto,
@ -26,6 +25,8 @@ import {
} from './../'; } from './../';
describe('SchemasService', () => { describe('SchemasService', () => {
let now = DateTime.now();
let user = 'me';
let version = new Version('1'); let version = new Version('1');
beforeEach(() => { beforeEach(() => {
@ -240,10 +241,10 @@ describe('SchemasService', () => {
const dto = new CreateSchemaDto('name'); const dto = new CreateSchemaDto('name');
let created: EntityCreatedDto | null = null; let schema: SchemaDto | null = null;
schemasService.postSchema('my-app', dto, version).subscribe(result => { schemasService.postSchema('my-app', dto, user, now, version).subscribe(result => {
created = result; schema = result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas');
@ -251,10 +252,10 @@ describe('SchemasService', () => {
expect(req.request.method).toEqual('POST'); 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({ id: 'my-schema' }); req.flush({ id: '1' });
expect(created).toEqual( expect(schema).toEqual(
new EntityCreatedDto('my-schema')); new SchemaDto('my-schema', dto.name, new SchemaPropertiesDto(null, null), false, user, user, now, now, version));
})); }));
it('should make post request to add field', it('should make post request to add field',
@ -262,10 +263,10 @@ describe('SchemasService', () => {
const dto = new AddFieldDto('name', 'invariant', createProperties('Number')); const dto = new AddFieldDto('name', 'invariant', createProperties('Number'));
let created: EntityCreatedDto | null = null; let field: FieldDto | null = null;
schemasService.postField('my-app', 'my-schema', dto, version).subscribe(result => { schemasService.postField('my-app', 'my-schema', dto, version).subscribe(result => {
created = result; field = result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields');
@ -275,8 +276,8 @@ describe('SchemasService', () => {
req.flush({ id: 123 }); req.flush({ id: 123 });
expect(created).toEqual( expect(field).toEqual(
new EntityCreatedDto(123)); new FieldDto(123, dto.name, false, false, dto.partitioning, dto.properties));
})); }));
it('should make put request to update schema', it('should make put request to update schema',

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

@ -15,7 +15,6 @@ import 'framework/angular/http-extensions';
import { import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
EntityCreatedDto,
HTTP, HTTP,
ValidatorsEx, ValidatorsEx,
Version Version
@ -84,21 +83,130 @@ export class SchemaDto {
public readonly version: Version public readonly version: Version
) { ) {
} }
public publish(user: string): SchemaDto {
return new SchemaDto(
this.id,
this.name,
this.properties,
true,
this.createdBy, user,
this.created, DateTime.now(),
this.version);
}
public unpublish(user: string): SchemaDto {
return new SchemaDto(
this.id,
this.name,
this.properties,
false,
this.createdBy, user,
this.created, DateTime.now(),
this.version);
}
public update(user: string, properties: SchemaPropertiesDto): SchemaDto {
return new SchemaDto(
this.id,
this.name,
properties,
this.isPublished,
this.createdBy, user,
this.created, DateTime.now(),
this.version);
}
} }
export class SchemaDetailsDto { export class SchemaDetailsDto extends SchemaDto {
constructor( constructor(id: string, name: string, properties: SchemaPropertiesDto, isPublished: boolean, createdBy: string, lastModifiedBy: string, created: DateTime, lastModified: DateTime, version: Version,
public readonly id: string,
public readonly name: string,
public readonly properties: SchemaPropertiesDto,
public readonly isPublished: boolean,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly version: Version,
public readonly fields: FieldDto[] public readonly fields: FieldDto[]
) { ) {
super(id, name, properties, isPublished, createdBy, lastModifiedBy, created, lastModified, version);
}
public publish(user: string): SchemaDetailsDto {
return new SchemaDetailsDto(
this.id,
this.name,
this.properties,
true,
this.createdBy, user,
this.created, DateTime.now(),
this.version,
this.fields);
}
public unpublish(user: string): SchemaDetailsDto {
return new SchemaDetailsDto(
this.id,
this.name,
this.properties,
false,
this.createdBy, user,
this.created, DateTime.now(),
this.version,
this.fields);
}
public update(user: string, properties: SchemaPropertiesDto): SchemaDetailsDto {
return new SchemaDetailsDto(
this.id,
this.name,
properties,
this.isPublished,
this.createdBy, user,
this.created, DateTime.now(),
this.version,
this.fields);
}
public addField(user: string, field: FieldDto): SchemaDetailsDto {
return new SchemaDetailsDto(
this.id,
this.name,
this.properties,
this.isPublished,
this.createdBy, user,
this.created, DateTime.now(),
this.version,
[...this.fields, field]);
}
public updateField(user: string, field: FieldDto): SchemaDetailsDto {
return new SchemaDetailsDto(
this.id,
this.name,
this.properties,
this.isPublished,
this.createdBy, user,
this.created, DateTime.now(),
this.version,
this.fields.map(f => f.fieldId === field.fieldId ? field : f));
}
public replaceFields(user: string, fields: FieldDto[]): SchemaDetailsDto {
return new SchemaDetailsDto(
this.id,
this.name,
this.properties,
this.isPublished,
this.createdBy, user,
this.created, DateTime.now(),
this.version,
fields);
}
public removeField(user: string, field: FieldDto): SchemaDetailsDto {
return new SchemaDetailsDto(
this.id,
this.name,
this.properties,
this.isPublished,
this.createdBy, user,
this.created, DateTime.now(),
this.version,
this.fields.filter(f => f.fieldId !== field.fieldId));
} }
} }
@ -113,6 +221,26 @@ export class FieldDto {
) { ) {
} }
public show(): FieldDto {
return new FieldDto(this.fieldId, this.name, false, this.isDisabled, this.partitioning, this.properties);
}
public hide(): FieldDto {
return new FieldDto(this.fieldId, this.name, false, this.isDisabled, this.partitioning, this.properties);
}
public enable(): FieldDto {
return new FieldDto(this.fieldId, this.name, this.isHidden, false, this.partitioning, this.properties);
}
public disable(): FieldDto {
return new FieldDto(this.fieldId, this.name, this.isHidden, true, this.partitioning, this.properties);
}
public update(properties: FieldPropertiesDto): FieldDto {
return new FieldDto(this.fieldId, this.name, this.isHidden, this.isDisabled, this.partitioning, properties);
}
public formatValue(value: any): string { public formatValue(value: any): string {
return this.properties.formatValue(value); return this.properties.formatValue(value);
} }
@ -475,7 +603,9 @@ 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 properties?: SchemaPropertiesDto
) { ) {
} }
} }
@ -548,22 +678,40 @@ export class SchemasService {
.pretifyError('Failed to load schema. Please reload.'); .pretifyError('Failed to load schema. Please reload.');
} }
public postSchema(appName: string, dto: CreateSchemaDto, version?: Version): Observable<EntityCreatedDto> { public postSchema(appName: string, dto: CreateSchemaDto, user: string, now?: DateTime, version?: Version): Observable<SchemaDetailsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => { .map(response => {
return new EntityCreatedDto(response.id); now = now || DateTime.now();
return new SchemaDetailsDto(
response.id,
dto.name,
dto.properties || new SchemaPropertiesDto(null, null),
false,
user,
user,
now,
now,
version,
dto.fields || []);
}) })
.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<EntityCreatedDto> { public postField(appName: string, schemaName: string, dto: AddFieldDto, version?: Version): Observable<FieldDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => { .map(response => {
return new EntityCreatedDto(response.id); return new FieldDto(
response.id,
dto.name,
false,
false,
dto.partitioning,
dto.properties);
}) })
.pretifyError('Failed to add field. Please reload.'); .pretifyError('Failed to add field. Please reload.');
} }

3
src/Squidex/app/shared/services/users.service.spec.ts

@ -12,7 +12,6 @@ import {
ApiUrlConfig, ApiUrlConfig,
CreateUserDto, CreateUserDto,
UpdateUserDto, UpdateUserDto,
UserCreatedDto,
UserDto, UserDto,
UserManagementService, UserManagementService,
UsersDto, UsersDto,
@ -280,7 +279,7 @@ describe('UserManagementService', () => {
req.flush({ id: '123', pictureUrl: 'path/to/image1' }); req.flush({ id: '123', pictureUrl: 'path/to/image1' });
expect(user).toEqual(new UserCreatedDto('123', 'path/to/image1')); expect(user).toEqual(new UserDto('123', dto.email, dto.displayName, 'path/to/image1', false));
})); }));
it('should make put request to update user', it('should make put request to update user',

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

@ -21,12 +21,27 @@ export class UsersDto {
} }
} }
export class UserCreatedDto { export class UserDto {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly pictureUrl: string public readonly email: string,
public readonly displayName: string,
public readonly pictureUrl: string | null,
public readonly isLocked: boolean
) { ) {
} }
public update(email: string, displayName: string): UserDto {
return new UserDto(this.id, email, displayName, this.pictureUrl, this.isLocked);
}
public lock(): UserDto {
return new UserDto(this.id, this.email, this.displayName, this.pictureUrl, true);
}
public unlock(): UserDto {
return new UserDto(this.id, this.email, this.displayName, this.pictureUrl, false);
}
} }
export class CreateUserDto { export class CreateUserDto {
@ -47,17 +62,6 @@ export class UpdateUserDto {
} }
} }
export class UserDto {
constructor(
public readonly id: string,
public readonly email: string,
public readonly displayName: string,
public readonly pictureUrl: string | null,
public readonly isLocked: boolean
) {
}
}
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor( constructor(
@ -150,7 +154,7 @@ export class UserManagementService {
return HTTP.postVersioned(this.http, url, dto) return HTTP.postVersioned(this.http, url, dto)
.map(response => { .map(response => {
return new UserCreatedDto(response.id, response.pictureUrl); return new UserDto(response.id, dto.email, dto.displayName, response.pictureUrl, false);
}) })
.pretifyError('Failed to create user. Please reload.'); .pretifyError('Failed to create user. Please reload.');
} }

7
src/Squidex/app/shared/services/webhooks.service.spec.ts

@ -12,7 +12,6 @@ import {
ApiUrlConfig, ApiUrlConfig,
CreateWebhookDto, CreateWebhookDto,
Version, Version,
WebhookCreatedDto,
WebhookDto, WebhookDto,
WebhooksService WebhooksService
} from './../'; } from './../';
@ -86,7 +85,7 @@ describe('WebhooksService', () => {
const dto = new CreateWebhookDto('http://squidex.io/hook'); const dto = new CreateWebhookDto('http://squidex.io/hook');
let webhook: WebhookCreatedDto | null = null; let webhook: WebhookDto | null = null;
webhooksService.postWebhook('my-app', 'my-schema', dto, version).subscribe(result => { webhooksService.postWebhook('my-app', 'my-schema', dto, version).subscribe(result => {
webhook = result; webhook = result;
@ -97,9 +96,9 @@ describe('WebhooksService', () => {
expect(req.request.method).toEqual('POST'); 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({ id: 'id1', sharedSecret: 'token1' }); req.flush({ id: 'id1', sharedSecret: 'token1', schemaId: 'schema1' });
expect(webhook).toEqual(new WebhookCreatedDto('id1', 'token1')); expect(webhook).toEqual(new WebhookDto('id1', 'schema1', 'token1', dto.url, 0, 0, 0, 0, []));
})); }));
it('should make delete request to delete webhook', it('should make delete request to delete webhook',

17
src/Squidex/app/shared/services/webhooks.service.ts

@ -32,14 +32,6 @@ export class WebhookDto {
} }
} }
export class WebhookCreatedDto {
constructor(
public readonly id: string,
public readonly sharedSecret: string
) {
}
}
export class CreateWebhookDto { export class CreateWebhookDto {
constructor( constructor(
public readonly url: string public readonly url: string
@ -78,14 +70,17 @@ export class WebhooksService {
.pretifyError('Failed to load webhooks. Please reload.'); .pretifyError('Failed to load webhooks. Please reload.');
} }
public postWebhook(appName: string, schemaName: string, dto: CreateWebhookDto, version?: Version): Observable<WebhookCreatedDto> { public postWebhook(appName: string, schemaName: string, dto: CreateWebhookDto, version?: Version): Observable<WebhookDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => { .map(response => {
return new WebhookCreatedDto( return new WebhookDto(
response.id, response.id,
response.sharedSecret); response.schemaId,
response.sharedSecret,
dto.url,
0, 0, 0, 0, []);
}) })
.pretifyError('Failed to create webhook. Please reload.'); .pretifyError('Failed to create webhook. Please reload.');
} }

Loading…
Cancel
Save