From 8d8dcea518f9ff6f280a714ff9c1e8c2b7312f94 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 1 Jun 2019 23:11:43 +0200 Subject: [PATCH 001/175] Ng8 --- src/Squidex/app/app.routes.ts | 18 +- .../pages/graphql/graphql-page.component.scss | 2 +- .../pages/graphql/graphql-page.component.ts | 2 +- .../apps/pages/apps-page.component.scss | 2 +- .../apps/pages/news-dialog.component.scss | 2 +- .../pages/onboarding-dialog.component.scss | 2 +- .../assets/pages/assets-page.component.scss | 2 +- .../content-history-page.component.scss | 2 +- .../pages/content/content-page.component.ts | 2 +- .../contents/contents-page.component.scss | 6 +- .../pages/contents/contents-page.component.ts | 2 +- .../content/shared/array-item.component.scss | 2 +- .../shared/contents-selector.component.scss | 12 +- .../pages/dashboard-page.component.scss | 4 +- .../pages/rules/rule-element.component.scss | 8 +- .../pages/schema/field-wizard.component.scss | 6 +- .../pages/schema/field-wizard.component.ts | 2 +- .../pages/schemas/schema-form.component.scss | 10 +- .../settings/pages/roles/role.component.ts | 2 +- .../angular/forms/autocomplete.component.ts | 4 +- .../angular/forms/code-editor.component.scss | 2 +- .../angular/forms/code-editor.component.ts | 2 +- .../angular/forms/color-picker.component.scss | 2 +- .../forms/date-time-editor.component.ts | 2 +- .../angular/forms/iframe-editor.component.ts | 2 +- .../angular/forms/json-editor.component.scss | 2 +- .../angular/forms/json-editor.component.ts | 2 +- .../angular/forms/tag-editor.component.ts | 4 +- .../angular/modals/modal-dialog.component.ts | 4 +- .../angular/modals/root-view.component.ts | 2 +- .../app/framework/angular/panel.component.ts | 2 +- .../shared/components/asset.component.scss | 5 +- .../components/assets-selector.component.scss | 2 +- .../geolocation-editor.component.ts | 4 +- .../components/history-list.component.scss | 2 +- .../components/markdown-editor.component.ts | 6 +- .../components/rich-editor.component.ts | 2 +- .../internal/internal-area.component.scss | 2 +- src/Squidex/app/shims.ts | 105 +- src/Squidex/app/theme/_bootstrap.scss | 2 +- src/Squidex/package-lock.json | 2657 ++++++----------- src/Squidex/package.json | 52 +- src/Squidex/tsconfig.json | 1 + 43 files changed, 1123 insertions(+), 1835 deletions(-) diff --git a/src/Squidex/app/app.routes.ts b/src/Squidex/app/app.routes.ts index d7598968f..8331b68a4 100644 --- a/src/Squidex/app/app.routes.ts +++ b/src/Squidex/app/app.routes.ts @@ -38,12 +38,12 @@ export const routes: Routes = [ children: [ { path: '', - loadChildren: './features/apps/module#SqxFeatureAppsModule', + loadChildren: () => import('./features/apps/module').then(m => m.SqxFeatureAppsModule), canActivate: [UnsetAppGuard] }, { path: 'administration', - loadChildren: './features/administration/module#SqxFeatureAdministrationModule', + loadChildren: () => import('./features/administration/module').then(m => m.SqxFeatureAdministrationModule), canActivate: [UnsetAppGuard] }, { @@ -53,31 +53,31 @@ export const routes: Routes = [ children: [ { path: '', - loadChildren: './features/dashboard/module#SqxFeatureDashboardModule' + loadChildren: () => import('./features/dashboard/module').then(m => m.SqxFeatureDashboardModule) }, { path: 'content', - loadChildren: './features/content/module#SqxFeatureContentModule' + loadChildren: () => import('./features/content/module').then(m => m.SqxFeatureContentModule) }, { path: 'schemas', - loadChildren: './features/schemas/module#SqxFeatureSchemasModule' + loadChildren: () => import('./features/schemas/module').then(m => m.SqxFeatureSchemasModule) }, { path: 'assets', - loadChildren: './features/assets/module#SqxFeatureAssetsModule' + loadChildren: () => import('./features/assets/module').then(m => m.SqxFeatureAssetsModule) }, { path: 'rules', - loadChildren: './features/rules/module#SqxFeatureRulesModule' + loadChildren: () => import('./features/rules/module').then(m => m.SqxFeatureRulesModule) }, { path: 'settings', - loadChildren: './features/settings/module#SqxFeatureSettingsModule' + loadChildren: () => import('./features/settings/module').then(m => m.SqxFeatureSettingsModule) }, { path: 'api', - loadChildren: './features/api/module#SqxFeatureApiModule' + loadChildren: () => import('./features/api/module').then(m => m.SqxFeatureApiModule) } ] } diff --git a/src/Squidex/app/features/api/pages/graphql/graphql-page.component.scss b/src/Squidex/app/features/api/pages/graphql/graphql-page.component.scss index 6d3c84b1c..e47eee080 100644 --- a/src/Squidex/app/features/api/pages/graphql/graphql-page.component.scss +++ b/src/Squidex/app/features/api/pages/graphql/graphql-page.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -:host /deep/ { +::ng-deep { @import '~graphiql/graphiql'; .graphiql-container { diff --git a/src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts b/src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts index 3ae06545c..3d32114d0 100644 --- a/src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts +++ b/src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts @@ -22,7 +22,7 @@ import { AppsState, GraphQlService } from '@app/shared'; templateUrl: './graphql-page.component.html' }) export class GraphQLPageComponent implements AfterViewInit { - @ViewChild('graphiQLContainer') + @ViewChild('graphiQLContainer', { static: false }) public graphiQLContainer: ElementRef; constructor( diff --git a/src/Squidex/app/features/apps/pages/apps-page.component.scss b/src/Squidex/app/features/apps/pages/apps-page.component.scss index e428d32c9..af1a2f69f 100644 --- a/src/Squidex/app/features/apps/pages/apps-page.component.scss +++ b/src/Squidex/app/features/apps/pages/apps-page.component.scss @@ -66,7 +66,7 @@ } &:hover { - @include box-shadow(0, 3px, 16px, .2px); + @include box-shadow(0, 3px, 16px, .2); } &:focus { diff --git a/src/Squidex/app/features/apps/pages/news-dialog.component.scss b/src/Squidex/app/features/apps/pages/news-dialog.component.scss index 787e1d95a..927e34891 100644 --- a/src/Squidex/app/features/apps/pages/news-dialog.component.scss +++ b/src/Squidex/app/features/apps/pages/news-dialog.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -:host /deep/ { +::ng-deep { img { @include box-shadow(0, 4px, 20px, .2); width: 80%; diff --git a/src/Squidex/app/features/apps/pages/onboarding-dialog.component.scss b/src/Squidex/app/features/apps/pages/onboarding-dialog.component.scss index a2dadddb3..01f6bf120 100644 --- a/src/Squidex/app/features/apps/pages/onboarding-dialog.component.scss +++ b/src/Squidex/app/features/apps/pages/onboarding-dialog.component.scss @@ -19,7 +19,7 @@ p { max-width: 489px; } -:host /deep/ .modal { +::ng-deep .modal { &-content, &-dialog { min-height: $size-height; diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.scss b/src/Squidex/app/features/assets/pages/assets-page.component.scss index 6bc6bf7dc..1c98c09d3 100644 --- a/src/Squidex/app/features/assets/pages/assets-page.component.scss +++ b/src/Squidex/app/features/assets/pages/assets-page.component.scss @@ -6,7 +6,7 @@ padding: 1rem; } -:host /deep/ { +::ng-deep { .search { .form-control { @include border-radius-right; diff --git a/src/Squidex/app/features/content/pages/content/content-history-page.component.scss b/src/Squidex/app/features/content/pages/content/content-history-page.component.scss index cf0dec62a..d7f4584de 100644 --- a/src/Squidex/app/features/content/pages/content/content-history-page.component.scss +++ b/src/Squidex/app/features/content/pages/content/content-history-page.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -:host /deep/ { +::ng-deep { .user-ref { color: $color-title; } diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index 228595a75..5704468b4 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -59,7 +59,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD public language: AppLanguageDto; public languages: ImmutableArray; - @ViewChild('dueTimeSelector') + @ViewChild('dueTimeSelector', { static: false }) public dueTimeSelector: DueTimeSelectorComponent; constructor(apiUrl: ApiUrlConfig, authService: AuthService, diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.scss b/src/Squidex/app/features/content/pages/contents/contents-page.component.scss index e9f528877..46a30425b 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.scss +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.scss @@ -1,8 +1,10 @@ @import '_vars'; @import '_mixins'; -:host /deep/ .search-form { - display: block; +::ng-deep { + .search-form { + display: block; + } } .content { diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts index 4531e3df8..73acdf1da 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts @@ -56,7 +56,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit { public isAllSelected = false; - @ViewChild('dueTimeSelector') + @ViewChild('dueTimeSelector', { static: false }) public dueTimeSelector: DueTimeSelectorComponent; constructor( diff --git a/src/Squidex/app/features/content/shared/array-item.component.scss b/src/Squidex/app/features/content/shared/array-item.component.scss index 7b54dfb82..976c37102 100644 --- a/src/Squidex/app/features/content/shared/array-item.component.scss +++ b/src/Squidex/app/features/content/shared/array-item.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -:host /deep/ { +::ng-deep { .ui-separator { border-color: $color-border !important; font-size: 1.2rem; diff --git a/src/Squidex/app/features/content/shared/contents-selector.component.scss b/src/Squidex/app/features/content/shared/contents-selector.component.scss index c9609948f..6daf3f73c 100644 --- a/src/Squidex/app/features/content/shared/contents-selector.component.scss +++ b/src/Squidex/app/features/content/shared/contents-selector.component.scss @@ -1,10 +1,12 @@ @import '_vars'; @import '_mixins'; -:host /deep/ .modal-body { - background: $color-background; -} +::ng-deep { + .modal-body { + background: $color-background; + } -:host /deep/ .modal-tabs { - background: $color-dark-foreground; + .modal-tabs { + background: $color-dark-foreground; + } } \ No newline at end of file diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss index 936b11b25..c8ff0d705 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss @@ -19,7 +19,7 @@ } } -:host /deep/ canvas { +::ng-deep canvas { height: 12rem !important; margin-top: -1rem; margin-bottom: 0; @@ -72,7 +72,7 @@ } &:hover { - @include box-shadow(0, 3px, 16px, .2px); + @include box-shadow(0, 3px, 16px, .2); } &:focus { diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss index aab579f44..7c0f4f750 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss @@ -82,7 +82,9 @@ display: inline-block; } -/deep/ svg { - fill: $color-dark-foreground; - display: block; +::ng-deep { + svg { + fill: $color-dark-foreground; + display: block; + } } \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.scss b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.scss index c828581c5..5a7bb98a7 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.scss @@ -3,8 +3,10 @@ $icon-size: 4.5rem; -:host /deep/ .table-items-row-details-tab { - padding: 1.5rem 1.75rem; +::ng-deep { + .table-items-row-details-tab { + padding: 1.5rem 1.75rem; + } } .form-group { diff --git a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts index 100635708..0ce144904 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.ts @@ -29,7 +29,7 @@ const DEFAULT_FIELD = { name: '', partitioning: 'invariant', properties: createP templateUrl: './field-wizard.component.html' }) export class FieldWizardComponent implements OnInit { - @ViewChild('nameInput') + @ViewChild('nameInput', { static: false }) public nameInput: ElementRef; @Input() diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss index 99b0c95da..5642dbe21 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss @@ -8,10 +8,12 @@ $icon-size: 4.5rem; margin-left: -.5rem; } -:host /deep/ .editor { - height: 15rem !important; - margin-bottom: .5rem; - margin-top: 0; +::ng-deep { + .editor { + height: 15rem !important; + margin-bottom: .5rem; + margin-top: 0; + } } .name-group { diff --git a/src/Squidex/app/features/settings/pages/roles/role.component.ts b/src/Squidex/app/features/settings/pages/roles/role.component.ts index 385fe6cf2..f72576839 100644 --- a/src/Squidex/app/features/settings/pages/roles/role.component.ts +++ b/src/Squidex/app/features/settings/pages/roles/role.component.ts @@ -40,7 +40,7 @@ export class RoleComponent implements OnChanges { @Input() public allPermissions: AutocompleteSource; - @ViewChild('addInput') + @ViewChild('addInput', { static: false }) public addPermissionInput: AutocompleteComponent; public isEditing = false; diff --git a/src/Squidex/app/framework/angular/forms/autocomplete.component.ts b/src/Squidex/app/framework/angular/forms/autocomplete.component.ts index 24115d8fa..5f90c45ef 100644 --- a/src/Squidex/app/framework/angular/forms/autocomplete.component.ts +++ b/src/Squidex/app/framework/angular/forms/autocomplete.component.ts @@ -52,10 +52,10 @@ export class AutocompleteComponent extends StatefulControlComponent; - @ViewChild('input') + @ViewChild('input', { static: false }) public inputControl: ElementRef; public queryInput = new FormControl(); diff --git a/src/Squidex/app/framework/angular/forms/code-editor.component.scss b/src/Squidex/app/framework/angular/forms/code-editor.component.scss index cb89af65e..0fb96d3fb 100644 --- a/src/Squidex/app/framework/angular/forms/code-editor.component.scss +++ b/src/Squidex/app/framework/angular/forms/code-editor.component.scss @@ -3,7 +3,7 @@ // sass-lint:disable class-name-format -:host /deep/ { +::ng-deep { .ace_editor { background: $color-dark-foreground; border: 1px solid $color-input; diff --git a/src/Squidex/app/framework/angular/forms/code-editor.component.ts b/src/Squidex/app/framework/angular/forms/code-editor.component.ts index 3bf6f7a04..caa716184 100644 --- a/src/Squidex/app/framework/angular/forms/code-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/code-editor.component.ts @@ -35,7 +35,7 @@ export class CodeEditorComponent extends ExternalControlComponent implem private value: string; private isDisabled = false; - @ViewChild('editor') + @ViewChild('editor', { static: false }) public editor: ElementRef; @Input() diff --git a/src/Squidex/app/framework/angular/forms/color-picker.component.scss b/src/Squidex/app/framework/angular/forms/color-picker.component.scss index 85b930ea9..8b35bf458 100644 --- a/src/Squidex/app/framework/angular/forms/color-picker.component.scss +++ b/src/Squidex/app/framework/angular/forms/color-picker.component.scss @@ -1,7 +1,7 @@ @import '_mixins'; @import '_vars'; -:host /deep/ { +::ng-deep { .color-picker { & { border-color: $color-border; diff --git a/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts b/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts index 8cffe33e9..9c0f06a54 100644 --- a/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts @@ -41,7 +41,7 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string @Input() public hideClear: boolean; - @ViewChild('dateInput') + @ViewChild('dateInput', { static: false }) public dateInput: ElementRef; public timeControl = new FormControl(); diff --git a/src/Squidex/app/framework/angular/forms/iframe-editor.component.ts b/src/Squidex/app/framework/angular/forms/iframe-editor.component.ts index 5eb1115cc..f4badb851 100644 --- a/src/Squidex/app/framework/angular/forms/iframe-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/iframe-editor.component.ts @@ -26,7 +26,7 @@ export class IFrameEditorComponent extends ExternalControlComponent impleme private isDisabled = false; private isInitialized = false; - @ViewChild('iframe') + @ViewChild('iframe', { static: false }) public iframe: ElementRef; @Input() diff --git a/src/Squidex/app/framework/angular/forms/json-editor.component.scss b/src/Squidex/app/framework/angular/forms/json-editor.component.scss index cb89af65e..0fb96d3fb 100644 --- a/src/Squidex/app/framework/angular/forms/json-editor.component.scss +++ b/src/Squidex/app/framework/angular/forms/json-editor.component.scss @@ -3,7 +3,7 @@ // sass-lint:disable class-name-format -:host /deep/ { +::ng-deep { .ace_editor { background: $color-dark-foreground; border: 1px solid $color-input; diff --git a/src/Squidex/app/framework/angular/forms/json-editor.component.ts b/src/Squidex/app/framework/angular/forms/json-editor.component.ts index e2baa6854..0adf10a0a 100644 --- a/src/Squidex/app/framework/angular/forms/json-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/json-editor.component.ts @@ -32,7 +32,7 @@ export class JsonEditorComponent extends ExternalControlComponent implem private valueString: string; private isDisabled = false; - @ViewChild('editor') + @ViewChild('editor', { static: false }) public editor: ElementRef; constructor(changeDetector: ChangeDetectorRef, diff --git a/src/Squidex/app/framework/angular/forms/tag-editor.component.ts b/src/Squidex/app/framework/angular/forms/tag-editor.component.ts index df703254b..3649ab638 100644 --- a/src/Squidex/app/framework/angular/forms/tag-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/tag-editor.component.ts @@ -93,10 +93,10 @@ interface State { changeDetection: ChangeDetectionStrategy.OnPush }) export class TagEditorComponent extends StatefulControlComponent implements AfterViewInit, OnInit { - @ViewChild('form') + @ViewChild('form', { static: false }) public formElement: ElementRef; - @ViewChild('input') + @ViewChild('input', { static: false }) public inputElement: ElementRef; @Input() diff --git a/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts b/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts index d549ae62c..5bc25ec37 100644 --- a/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts +++ b/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts @@ -51,10 +51,10 @@ export class ModalDialogComponent extends StatefulComponent implements Af @Output() public close = new EventEmitter(); - @ViewChild('tabsElement') + @ViewChild('tabsElement', { static: false }) public tabsElement: ElementRef; - @ViewChild('footerElement') + @ViewChild('footerElement', { static: false }) public footerElement: ElementRef; constructor(changeDetector: ChangeDetectorRef) { diff --git a/src/Squidex/app/framework/angular/modals/root-view.component.ts b/src/Squidex/app/framework/angular/modals/root-view.component.ts index 2f3ec887c..5a53a9d79 100644 --- a/src/Squidex/app/framework/angular/modals/root-view.component.ts +++ b/src/Squidex/app/framework/angular/modals/root-view.component.ts @@ -14,6 +14,6 @@ import { ChangeDetectionStrategy, Component, ViewChild, ViewContainerRef } from changeDetection: ChangeDetectionStrategy.OnPush }) export class RootViewComponent { - @ViewChild('element', { read: ViewContainerRef }) + @ViewChild('element', { read: ViewContainerRef, static: false }) public viewContainer: ViewContainerRef; } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/panel.component.ts b/src/Squidex/app/framework/angular/panel.component.ts index 54bb5023d..46f5cd838 100644 --- a/src/Squidex/app/framework/angular/panel.component.ts +++ b/src/Squidex/app/framework/angular/panel.component.ts @@ -64,7 +64,7 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { @Input() public sidebarClass = ''; - @ViewChild('panel') + @ViewChild('panel', { static: false }) public panel: ElementRef; constructor( diff --git a/src/Squidex/app/shared/components/asset.component.scss b/src/Squidex/app/shared/components/asset.component.scss index 58ab1e5d0..d155b89e4 100644 --- a/src/Squidex/app/shared/components/asset.component.scss +++ b/src/Squidex/app/shared/components/asset.component.scss @@ -44,7 +44,7 @@ $list-height: 2.375rem; padding: 0; } -:host /deep/ { +::ng-deep { .form-control { &.disabled, &:disabled { @@ -107,7 +107,8 @@ $list-height: 2.375rem; background: $color-dark-black; } - &-name, &-info { + &-name, + &-info { @include truncate; } diff --git a/src/Squidex/app/shared/components/assets-selector.component.scss b/src/Squidex/app/shared/components/assets-selector.component.scss index 4a020bd99..0438b80c5 100644 --- a/src/Squidex/app/shared/components/assets-selector.component.scss +++ b/src/Squidex/app/shared/components/assets-selector.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -:host /deep/ { +::ng-deep { .search { .form-control { @include border-radius-right; diff --git a/src/Squidex/app/shared/components/geolocation-editor.component.ts b/src/Squidex/app/shared/components/geolocation-editor.component.ts index 00de85805..6de6e97bf 100644 --- a/src/Squidex/app/shared/components/geolocation-editor.component.ts +++ b/src/Squidex/app/shared/components/geolocation-editor.component.ts @@ -64,10 +64,10 @@ export class GeolocationEditorComponent extends StatefulControlComponent; - @ViewChild('searchBox') + @ViewChild('searchBox', { static: false }) public searchBoxInput: ElementRef; constructor(changeDetector: ChangeDetectorRef, diff --git a/src/Squidex/app/shared/components/history-list.component.scss b/src/Squidex/app/shared/components/history-list.component.scss index 5c1e18804..5226994c0 100644 --- a/src/Squidex/app/shared/components/history-list.component.scss +++ b/src/Squidex/app/shared/components/history-list.component.scss @@ -1,7 +1,7 @@ @import '_vars'; @import '_mixins'; -:host /deep/ { +::ng-deep { .user-ref { color: $color-title; } diff --git a/src/Squidex/app/shared/components/markdown-editor.component.ts b/src/Squidex/app/shared/components/markdown-editor.component.ts index 237f33a6c..24a155f37 100644 --- a/src/Squidex/app/shared/components/markdown-editor.component.ts +++ b/src/Squidex/app/shared/components/markdown-editor.component.ts @@ -40,13 +40,13 @@ export class MarkdownEditorComponent extends StatefulControlComponent i private value: string; private isDisabled = false; - @ViewChild('editor') + @ViewChild('editor', { static: false }) public editor: ElementRef; @Output() diff --git a/src/Squidex/app/shell/pages/internal/internal-area.component.scss b/src/Squidex/app/shell/pages/internal/internal-area.component.scss index f2d93d283..d31100410 100644 --- a/src/Squidex/app/shell/pages/internal/internal-area.component.scss +++ b/src/Squidex/app/shell/pages/internal/internal-area.component.scss @@ -2,7 +2,7 @@ @import '_mixins'; .navbar { - @include box-shadow(0, 3px, 5px, .13px); + @include box-shadow(0, 3px, 5px, .13); display: block; } diff --git a/src/Squidex/app/shims.ts b/src/Squidex/app/shims.ts index 779923fd5..4c18a84a4 100644 --- a/src/Squidex/app/shims.ts +++ b/src/Squidex/app/shims.ts @@ -5,20 +5,93 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import 'core-js/es6/array'; -import 'core-js/es6/date'; -import 'core-js/es6/function'; -import 'core-js/es6/map'; -import 'core-js/es6/math'; -import 'core-js/es6/number'; -import 'core-js/es6/object'; -import 'core-js/es6/parse-float'; -import 'core-js/es6/parse-int'; -import 'core-js/es6/reflect'; -import 'core-js/es6/regexp'; -import 'core-js/es6/set'; -import 'core-js/es6/string'; -import 'core-js/es6/symbol'; - -import 'core-js/es7/reflect'; +// ES2015 symbol capabilities +import 'core-js/modules/es.symbol'; + +// ES2015 function capabilities +import 'core-js/modules/es.function.bind'; +import 'core-js/modules/es.function.has-instance'; +import 'core-js/modules/es.function.name'; + +// ES2015 object capabilities +import 'core-js/modules/es.object.assign'; +import 'core-js/modules/es.object.create'; +import 'core-js/modules/es.object.define-properties'; +import 'core-js/modules/es.object.define-property'; +import 'core-js/modules/es.object.freeze'; +import 'core-js/modules/es.object.get-own-property-descriptor'; +import 'core-js/modules/es.object.get-own-property-names'; +import 'core-js/modules/es.object.get-prototype-of'; +import 'core-js/modules/es.object.is'; +import 'core-js/modules/es.object.is-extensible'; +import 'core-js/modules/es.object.is-frozen'; +import 'core-js/modules/es.object.is-sealed'; +import 'core-js/modules/es.object.keys'; +import 'core-js/modules/es.object.prevent-extensions'; +import 'core-js/modules/es.object.seal'; +import 'core-js/modules/es.object.set-prototype-of'; +import 'core-js/modules/es.object.to-string'; + +// ES2015 array capabilities +import 'core-js/modules/es.array.copy-within'; +import 'core-js/modules/es.array.every'; +import 'core-js/modules/es.array.fill'; +import 'core-js/modules/es.array.filter'; +import 'core-js/modules/es.array.find'; +import 'core-js/modules/es.array.find-index'; +import 'core-js/modules/es.array.for-each'; +import 'core-js/modules/es.array.from'; +import 'core-js/modules/es.array.index-of'; +import 'core-js/modules/es.array.is-array'; +import 'core-js/modules/es.array.iterator'; +import 'core-js/modules/es.array.join'; +import 'core-js/modules/es.array.last-index-of'; +import 'core-js/modules/es.array.map'; +import 'core-js/modules/es.array.of'; +import 'core-js/modules/es.array.reduce'; +import 'core-js/modules/es.array.reduce-right'; +import 'core-js/modules/es.array.slice'; +import 'core-js/modules/es.array.some'; +import 'core-js/modules/es.array.sort'; + +// ES2015 string capabilities +import 'core-js/modules/es.string.anchor'; +import 'core-js/modules/es.string.big'; +import 'core-js/modules/es.string.blink'; +import 'core-js/modules/es.string.bold'; +import 'core-js/modules/es.string.code-point-at'; +import 'core-js/modules/es.string.ends-with'; +import 'core-js/modules/es.string.fixed'; +import 'core-js/modules/es.string.fontcolor'; +import 'core-js/modules/es.string.fontsize'; +import 'core-js/modules/es.string.from-code-point'; +import 'core-js/modules/es.string.includes'; +import 'core-js/modules/es.string.italics'; +import 'core-js/modules/es.string.iterator'; +import 'core-js/modules/es.string.link'; +import 'core-js/modules/es.string.raw'; +import 'core-js/modules/es.string.repeat'; +import 'core-js/modules/es.string.small'; +import 'core-js/modules/es.string.starts-with'; +import 'core-js/modules/es.string.strike'; +import 'core-js/modules/es.string.sub'; +import 'core-js/modules/es.string.sup'; +import 'core-js/modules/es.string.trim'; + +import 'core-js/modules/es.parse-float'; +import 'core-js/modules/es.parse-int'; + +import 'core-js/es/date'; +import 'core-js/es/math'; +import 'core-js/es/number'; +import 'core-js/es/regexp'; + +import 'core-js/modules/es.map'; +import 'core-js/modules/es.promise'; +import 'core-js/modules/es.set'; +import 'core-js/modules/es.weak-map'; +import 'core-js/modules/web.dom-collections.iterator'; + +import 'core-js/proposals/reflect-metadata'; + import 'zone.js/dist/zone'; \ No newline at end of file diff --git a/src/Squidex/app/theme/_bootstrap.scss b/src/Squidex/app/theme/_bootstrap.scss index a59dffcc0..20f9b0077 100644 --- a/src/Squidex/app/theme/_bootstrap.scss +++ b/src/Squidex/app/theme/_bootstrap.scss @@ -162,7 +162,7 @@ a { .dropdown-menu { // White dropdown menu without border and shadow. & { - @include box-shadow(0, 3px, 16px, .2px); + @include box-shadow(0, 3px, 16px, .2); border: 0; background: $panel-light-background; } diff --git a/src/Squidex/package-lock.json b/src/Squidex/package-lock.json index 3051cf82b..183d8b57a 100644 --- a/src/Squidex/package-lock.json +++ b/src/Squidex/package-lock.json @@ -5,22 +5,22 @@ "requires": true, "dependencies": { "@angular-devkit/core": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", - "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.0.1.tgz", + "integrity": "sha512-GQtspR2umZeuTUDTLy2dpUstvQfK7oAwE0o+rWSa8mx0DyhYssrfBbvfE4GzUzKp6OSz1sbZAatSO+iimZjbQw==", "dev": true, "requires": { - "ajv": "6.9.1", - "chokidar": "2.0.4", + "ajv": "6.10.0", "fast-json-stable-stringify": "2.0.0", - "rxjs": "6.3.3", + "magic-string": "0.25.2", + "rxjs": "6.4.0", "source-map": "0.7.3" }, "dependencies": { "ajv": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", - "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -29,6 +29,88 @@ "uri-js": "^4.2.2" } }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + } + } + }, + "@angular/animations": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.0.0.tgz", + "integrity": "sha512-hggSRi83rmocLwzrKZtmFcqPdivKSJqp2yiYaiNmJ2yQWJ1JW/Lurypv9H347RWxmwCCwC2kV8embTGbOXIFDQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/common": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.0.0.tgz", + "integrity": "sha512-iOAJZ0+1zTRHnHE/5G30+4Q66W1pfZkSkxZIXvgijZ+wtuNloYdWNy/IdZ/m7ayBI7A6FsYEhyMUoWz2HVEJNw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.0.0.tgz", + "integrity": "sha512-4rKsVFMNykF83tPL1VE1+j9kZ3cWHUsLOAB/VqmF64EcR/GsbjKog2v23rSso5kqUtPiVq/FWGYllW6qMdxtJA==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler-cli": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.0.0.tgz", + "integrity": "sha512-Z0U0Ih8A7V3J1gq7AXnXbrGAD2ERmz7JbREJJRHDWiUNxIqGQiV3Odo1V8FL5n/cKvLwSYM2Ubvk10gb0+3njA==", + "dev": true, + "requires": { + "canonical-path": "1.0.0", + "chokidar": "^2.1.1", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.7.2", + "magic-string": "^0.25.0", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "shelljs": "^0.8.1", + "source-map": "^0.6.1", + "tslib": "^1.9.0", + "yargs": "13.1.0" + }, + "dependencies": { "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -37,6 +119,17 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "arr-diff": { @@ -81,24 +174,23 @@ } }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "dev": true, "requires": { "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", - "inherits": "^2.0.1", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "readdirp": "^2.2.1", + "upath": "^1.1.1" } }, "expand-brackets": { @@ -229,12 +321,6 @@ } } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -258,486 +344,59 @@ } } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "dev": true, + "optional": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" }, "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, "dev": true, + "optional": true, "requires": { - "is-extglob": "^2.1.0" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, "dev": true, + "optional": true, "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - } - } - }, - "@angular/animations": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.14.tgz", - "integrity": "sha512-K+wdq7TslmvDhrbwy65x7owE8wezI0fDdO+8SO9RU4m/w6R6vo4QS3uSdc5I2pxwm4QSXSc5eKhoWJkq0muTbQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/common": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.14.tgz", - "integrity": "sha512-c2QBhVpbQhg1FDhOQkyVdFvU11mfvYHW5ZaXzxdCpq2rZXCureYiCSnlv++EsIAKqi22+2a6GACHF9Gh8kBmSg==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/compiler": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.14.tgz", - "integrity": "sha512-Idhs+5HIzx+1+hrXIDaRpSqobMB7UvSvPlvCvtb3EDYjmltTNG68TtwMzGM8W2jdayliYuFOjFrnw1wCTkK3Ag==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/compiler-cli": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.14.tgz", - "integrity": "sha512-w5qn1nIPjiCP3WdbqicofpKpiRlh6NMYjWhe6mJysSBnVd34aSuGisYW/gVPQrmD46E1gmfpWTnWPeABVnjj6w==", - "dev": true, - "requires": { - "canonical-path": "1.0.0", - "chokidar": "^2.1.1", - "convert-source-map": "^1.5.1", - "dependency-graph": "^0.7.2", - "magic-string": "^0.25.0", - "minimist": "^1.2.0", - "reflect-metadata": "^0.1.2", - "shelljs": "^0.8.1", - "source-map": "^0.6.1", - "tslib": "^1.9.0", - "yargs": "9.0.1" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", - "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", - "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "chownr": { @@ -1352,9 +1011,9 @@ } }, "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -1378,59 +1037,59 @@ } }, "@angular/core": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.14.tgz", - "integrity": "sha512-XeZZJCyBKSKo0E/7Ef0SfJejmn+E7uBXa5cR1QapafS0Hnrq/hZu/NI039IDU/51NoycMDH2vTV19SmKu9Mkow==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.0.0.tgz", + "integrity": "sha512-mrkP1PTzqCmZGLYll+TDyawLXHzi+FcRPqSuRxCmDMthUUE93SLXT2yISDkx9aMPtFKgFr6KfrIkKuCz16BP/g==", "requires": { "tslib": "^1.9.0" } }, "@angular/forms": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.14.tgz", - "integrity": "sha512-jL5YbTk7VZmz4l0++iFVUNa1vGM+nnALjHKi1Ub8VWioRDRboYUsHyxzlgWW9gZRbHpnLEXFiUz1td+v7TouJw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.0.0.tgz", + "integrity": "sha512-T6XdG3mALWzvnrN3fA1hAmfwvraiF1SPMWNXgPk2riuMf8CFdoro+tQZ4eo1islHrTTw5QzmqN8JJALfhAG6bg==", "requires": { "tslib": "^1.9.0" } }, "@angular/http": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.14.tgz", - "integrity": "sha512-rSdH2JojApDU83qVm7RabIlNo3Ni3yr2gwsmQWs4XZ7SC8jLNnDkzdUbQ6T0vfuVX3v/FtAuMJl0yaVcG3EUJg==", + "version": "7.2.15", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.15.tgz", + "integrity": "sha512-TR7PEdmLWNIre3Zn8lvyb4lSrvPUJhKLystLnp4hBMcWsJqq5iK8S3bnlR4viZ9HMlf7bW7+Hm4SI6aB3tdUtw==", "requires": { "tslib": "^1.9.0" } }, "@angular/platform-browser": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.14.tgz", - "integrity": "sha512-yAq2+3W4J4B48HEmZYQucdEb9AHwRnv72q9CC/SxU7g59vaLhl1nv7cAWGJ4XFaJTbB7aB4Y4rLffuR+Gxkn7A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.0.0.tgz", + "integrity": "sha512-fTD+pTMbq+On9Uv3VXiei2lfuX7GX31dngm/Y4yWTFeW6eXy0+7kkfflzpLOb0hykCZvcXzarqCuEBBYNLrrOg==", "requires": { "tslib": "^1.9.0" } }, "@angular/platform-browser-dynamic": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.14.tgz", - "integrity": "sha512-lmTCBiDRbOPtniIqBjm1n5jl1TdyQM0qWQdBcoCsKpMNS/6/RacRcQsJZApAMdWm6gIVuLgmRQzaCLkSoekfYA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.0.0.tgz", + "integrity": "sha512-dx7W7JoSFbsveexjZ/BPlsXbMDLWVLmRCo7IqLvibMrTbdpaaOCNJIXJk1X+f7JJrQ7SwlZaVkoLCMoDWw6fmA==", "requires": { "tslib": "^1.9.0" } }, "@angular/platform-server": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-7.2.14.tgz", - "integrity": "sha512-ijFqRrxpbPQiAlbRtw0uKX8Qf+wWZhY5vNwV8wfP5oVziJdO7Vdtcmmw2NQxHAIHUrK7FQ5yklUxfglKa2G7SQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-8.0.0.tgz", + "integrity": "sha512-pA6m1okOfyy2qH5A6jUxrhx6z7eAG+ne7IM+j/6JUBDjp4KO9BC84aa/xfpZq5dsskl8E8II9c4hUKocMyeRjA==", "requires": { - "domino": "^2.1.0", + "domino": "^2.1.2", "tslib": "^1.9.0", "xhr2": "^0.1.4" } }, "@angular/router": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.14.tgz", - "integrity": "sha512-uqg0SKy79voEOIOvzVbCzFDD9XOAfZWkYt01ca2qLFXMx+6jWeVQIDuXc8Dmz5udIXNK5Ae//9R+nt5UZUZrSA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.0.0.tgz", + "integrity": "sha512-DGUTb8qpndE5m716xh00GxuC8o7qamlqbUruGB+SQD6ynU7s5yLGxtKffxqb1BT63+YewpsVxc2Koruvb1qjDw==", "requires": { "tslib": "^1.9.0" } @@ -1606,22 +1265,22 @@ } }, "@ngtools/webpack": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.8.tgz", - "integrity": "sha512-gfjSKz+F/2T4tZHpnQ1XqelKP/CIfI87XdoHsOI53ceTUrAkVKsOb3ULmEfkcdsdQZ/HhmCiLivcutHcW8xkhQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.0.1.tgz", + "integrity": "sha512-ly+KyzD5S8dVbwDv4pWt8+NNevPskmdXYJVxa068nE1dQV+CCK4mi6aR0GqrRR9zPvhgRoQKK8tMbN6WDCJS7g==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.8", + "@angular-devkit/core": "8.0.1", "enhanced-resolve": "4.1.0", - "rxjs": "6.3.3", + "rxjs": "6.4.0", "tree-kill": "1.2.1", "webpack-sources": "1.3.0" }, "dependencies": { "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -1645,10 +1304,27 @@ "integrity": "sha512-qjkHL3wF0JMHMqgm/kmL8Pf8rIiqvueEiZ0g6NVTcBX1WN46GWDr+V5z+gsHUeL0n8TfAmXnYmF7ajsxmBp4PQ==", "dev": true }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/jasmine": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.12.tgz", - "integrity": "sha512-lXvr2xFQEVQLkIhuGaR3GC1L9lMU1IxeWnAF/wNY5ZWpC4p9dgxkKkzMp7pntpAdv9pZSnYqgsBkCg32MXSZMg==", + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.13.tgz", + "integrity": "sha512-iczmLoIiVymaD1TIr2UctxjFkNEslVE/QtNAUmpDsD71cZfZBAsPCUv1Y+8AwsfA8bLx2ccr7d95T9w/UAirlQ==", "dev": true }, "@types/json5": { @@ -1663,6 +1339,12 @@ "integrity": "sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA==", "dev": true }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/mousetrap": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.0.tgz", @@ -1670,9 +1352,9 @@ "dev": true }, "@types/node": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.0.tgz", - "integrity": "sha512-Jrb/x3HT4PTJp6a4avhmJCDEVrPdqLfl3e8GGMbpkGGdwAV5UGlIs4vVEfsHHfylZVOKZWpOqmqFH8CbfOZ6kg==", + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.4.tgz", + "integrity": "sha512-j8YL2C0fXq7IONwl/Ud5Kt0PeXw22zGERt+HSSnwbKOJVsAGkEz3sFCYwaF9IOuoG1HOtE0vKCj6sXF7Q0+Vaw==", "dev": true }, "@types/prop-types": { @@ -1688,9 +1370,9 @@ "dev": true }, "@types/react": { - "version": "16.8.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.16.tgz", - "integrity": "sha512-A0+6kS6zwPtvubOLiCJmZ8li5bm3wKIkoKV0h3RdMDOnCj9cYkUnj3bWbE03/lcICdQmwBmUfoFiHeNhbFiyHQ==", + "version": "16.8.19", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.19.tgz", + "integrity": "sha512-QzEzjrd1zFzY9cDlbIiFvdr+YUmefuuRYrPxmkwG0UQv5XF35gFIi7a95m1bNVcFU0VimxSZ5QVGSiBmlggQXQ==", "dev": true, "requires": { "@types/prop-types": "*", @@ -2235,11 +1917,12 @@ } }, "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "dev": true, "requires": { + "object-assign": "^4.1.1", "util": "0.10.3" }, "dependencies": { @@ -3298,9 +2981,9 @@ }, "dependencies": { "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3414,9 +3097,9 @@ } }, "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "camelcase-keys": { @@ -3528,9 +3211,9 @@ "dev": true }, "chrome-trace-event": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", - "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -3931,10 +3614,13 @@ "dev": true }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } }, "content-type": { "version": "1.0.4", @@ -3984,9 +3670,9 @@ "dev": true }, "core-js": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.3.tgz", - "integrity": "sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.3.tgz", + "integrity": "sha512-PWZ+ZfuaKf178BIAg+CRsljwjIMRV8MY00CbZczkR6Zk5LfkSkjGoaab3+bqRQWVITNZxQB7TFYz+CFcyuamvA==" }, "core-util-is": { "version": "1.0.2", @@ -4084,12 +3770,14 @@ } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -4461,9 +4149,9 @@ } }, "csstype": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.4.tgz", - "integrity": "sha512-lAJUJP3M6HxFXbqtGRc0iZrdyeN+WzOWeY0q/VnFzI+kqVrYIzC7bWlKqCW7oCIdzoPkvfp82EVvrTlQ8zsWQg==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.5.tgz", + "integrity": "sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA==", "dev": true }, "currently-unhandled": { @@ -4570,45 +4258,6 @@ "requires": { "execa": "^1.0.0", "ip-regex": "^2.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } } }, "default-require-extensions": { @@ -4683,11 +4332,12 @@ } }, "del": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.0.tgz", - "integrity": "sha512-C4kvKNlYrwXhKxz97BuohF8YoGgQ23Xm9lvoHmgT7JaPGprSEjk3+XFled74Yt/x0ZABUHg2D67covzAPUKx5Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", "dev": true, "requires": { + "@types/glob": "^7.1.1", "globby": "^6.1.0", "is-path-cwd": "^2.0.0", "is-path-in-cwd": "^2.0.0", @@ -4970,6 +4620,12 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -5396,13 +5052,13 @@ } }, "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -5444,137 +5100,90 @@ } }, "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "dev": true, "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", "dev": true }, "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", "dev": true }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true } } @@ -5760,12 +5369,12 @@ "dev": true }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" } }, "findup-sync": { @@ -6870,10 +6479,13 @@ "dev": true }, "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } }, "get-value": { "version": "2.0.6", @@ -7020,9 +6632,9 @@ } }, "graphql": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.2.1.tgz", - "integrity": "sha512-2PL1UbvKeSjy/lUeJqHk+eR9CvuErXoCNwJI4jm3oNFEeY+9ELqHNKO1ZuSxAkasPkpWbmT/iMRMFxd3cEL3tQ==", + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.3.1.tgz", + "integrity": "sha512-FZm7kAa3FqKdXy8YSSpAoTtyDFMIYSpCDOr+3EqlI1bxmtHu+Vv/I2vrSeT1sBOEnEniX3uo4wFhFdS/8XN6gA==", "requires": { "iterall": "^1.2.2" } @@ -8726,9 +8338,9 @@ "dev": true }, "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", "dev": true }, "json5": { @@ -9924,18 +9536,6 @@ "uc.micro": "^1.0.1" } }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -9954,12 +9554,12 @@ } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, @@ -9974,12 +9574,6 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, "lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", @@ -10044,9 +9638,9 @@ } }, "loglevel": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", - "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.2.tgz", + "integrity": "sha512-Jt2MHrCNdtIe1W6co3tF5KXGRkzF+TYffiQstfXa04mrss9IKXzAAXYWak8LbZseAQY03sH2GzMCMU0ZOUc9bg==", "dev": true }, "loglevelnext": { @@ -10214,12 +9808,14 @@ "dev": true }, "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" } }, "memory-fs": { @@ -10396,32 +9992,33 @@ } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "mini-css-extract-plugin": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz", - "integrity": "sha512-79q5P7YGI6rdnVyIAV4NXpBQJFWdkzJxCim3Kog4078fM0piAaFlwocqbejdWtLW1cEzCexPrh6EdyFsPgVdAw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz", + "integrity": "sha512-RQIw6+7utTYn8DBGsf/LpRgZCJMpZt+kuawJ/fju0KiOL6nAaTBNmCJwS7HtwSCXfS47gCkmtBFS7HdsquhdxQ==", "dev": true, "requires": { "loader-utils": "^1.1.0", - "normalize-url": "^2.0.1", + "normalize-url": "1.9.1", "schema-utils": "^1.0.0", "webpack-sources": "^1.1.0" }, "dependencies": { "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", "dev": true, "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" } } } @@ -11171,14 +10768,31 @@ "dev": true }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + } } }, "os-tmpdir": { @@ -11216,21 +10830,21 @@ "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-map": { @@ -11240,9 +10854,9 @@ "dev": true }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "pako": { @@ -11390,15 +11004,6 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -11418,6 +11023,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -11445,57 +11056,12 @@ } }, "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" } }, "pluralize": { @@ -12619,9 +12185,9 @@ "dev": true }, "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, "preserve": { @@ -12788,12 +12354,11 @@ "dev": true }, "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", "dev": true, "requires": { - "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", "strict-uri-encode": "^1.0.0" } @@ -12915,27 +12480,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -13341,9 +12885,9 @@ "dev": true }, "rxjs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.1.tgz", - "integrity": "sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", "requires": { "tslib": "^1.9.0" } @@ -13675,9 +13219,9 @@ } }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "dev": true, "requires": { "debug": "2.6.9", @@ -13687,42 +13231,30 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true } } @@ -13769,15 +13301,15 @@ } }, "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, "set-blocking": { @@ -14196,9 +13728,9 @@ } }, "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "dev": true, "requires": { "is-plain-obj": "^1.0.0" @@ -14348,9 +13880,9 @@ "dev": true }, "readable-stream": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", - "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -14788,525 +14320,330 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true - } - } - }, - "tapable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", - "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", - "dev": true - }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "dev": true, - "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" - } - }, - "terser": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", - "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", - "dev": true, - "requires": { - "commander": "^2.19.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.10" - }, - "dependencies": { - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true - }, - "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "terser-webpack-plugin": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz", - "integrity": "sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==", - "dev": true, - "requires": { - "cacache": "^11.0.2", - "find-cache-dir": "^2.0.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^1.4.0", - "source-map": "^0.6.1", - "terser": "^3.16.1", - "webpack-sources": "^1.1.0", - "worker-farm": "^1.5.2" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "thunky": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", - "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", - "dev": true - }, - "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", - "dev": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "toposort": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", - "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", - "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "tree-kill": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", - "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", - "dev": true - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + } + } + }, + "tapable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", + "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", "dev": true }, - "true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { - "glob": "^7.1.2" + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" } }, - "ts-loader": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz", - "integrity": "sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw==", + "terser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.0.tgz", + "integrity": "sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA==", "dev": true, "requires": { - "chalk": "^2.3.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.0.2", - "micromatch": "^3.1.4", - "semver": "^5.0.1" + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + } + } + }, + "terser-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "dev": true, + "requires": { + "cacache": "^11.3.2", + "find-cache-dir": "^2.0.0", + "is-wsl": "^1.1.0", + "loader-utils": "^1.2.3", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.7.0", + "source-map": "^0.6.1", + "terser": "^4.0.0", + "webpack-sources": "^1.3.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "minimist": "^1.2.0" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "^3.0.2" } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "requires": { + "glob": "^7.1.2" + } + }, + "ts-loader": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.0.2.tgz", + "integrity": "sha512-kkF3sGf3oBUehlvXI9fkbItbFTnNgGkYAz91vtWnsKAU4m+LAmQjuby7uTZNo3As+/zHLuyB052SkQDY6vLXtg==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^4.0.0", + "semver": "^6.0.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "fill-range": "^7.0.1" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "to-regex-range": "^5.0.1" } }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "is-number": "^7.0.0" } } } @@ -15358,9 +14695,9 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, "tslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", - "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", + "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -15369,7 +14706,7 @@ "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "resolve": "^1.3.2", @@ -15474,9 +14811,9 @@ } }, "typescript": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", - "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", + "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", "dev": true }, "uc.micro": { @@ -15495,19 +14832,48 @@ } }, "uglifyjs-webpack-plugin": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.1.2.tgz", - "integrity": "sha512-G1fJx2uOAAfvdZ77SVCzmFo6mv8uKaHoZBL9Qq/ciC8r6p0ANOL1uY85fIUiyWXKw5RzAaJYZfNSL58Or2hQ0A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.1.3.tgz", + "integrity": "sha512-/lRkCaFbI6pT3CxsQHDhBcqB6tocOnqba0vJqJ2DzSWFLRgOIiip8q0nVFydyXk+n8UtF7ZuS6hvWopcYH5FuA==", "dev": true, "requires": { - "cacache": "^11.2.0", - "find-cache-dir": "^2.0.0", + "cacache": "^11.3.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.4.0", + "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "uglify-js": "^3.0.0", - "webpack-sources": "^1.1.0", - "worker-farm": "^1.5.2" + "uglify-js": "^3.5.12", + "webpack-sources": "^1.3.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "uglify-js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", + "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "dev": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } } }, "ultron": { @@ -15781,9 +15147,9 @@ "dev": true }, "v8-compile-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", - "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "dev": true }, "validate-npm-package-license": { @@ -15908,9 +15274,9 @@ } }, "chokidar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", - "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -16079,9 +15445,9 @@ } }, "fsevents": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", - "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "dev": true, "optional": true, "requires": { @@ -16745,9 +16111,9 @@ } }, "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -16780,9 +16146,9 @@ } }, "webpack": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.30.0.tgz", - "integrity": "sha512-4hgvO2YbAFUhyTdlR4FNyt2+YaYBYHavyzjCMbZzgglo02rlKi/pcsEzwCuCpsn1ryzIl1cq/u8ArIKu8JBYMg==", + "version": "4.32.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.32.2.tgz", + "integrity": "sha512-F+H2Aa1TprTQrpodRAWUMJn7A8MgDx82yQiNvYMaj3d1nv3HetKU0oqEulL9huj8enirKi8KvEXQ3QtuHF89Zg==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -17110,9 +16476,9 @@ } }, "webpack-cli": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.1.tgz", - "integrity": "sha512-c2inFU7SM0IttEgF7fK6AaUsbBnORRzminvbyRKS+NlbQHVZdCtzKBlavRL5359bFsywXGRAItA5di/IruC8mg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.2.tgz", + "integrity": "sha512-FLkobnaJJ+03j5eplxlI0TUxhGCOdfewspIGuvDVtpOlrAuKMFC57K42Ukxqs1tn8947/PM6tP95gQc0DCzRYA==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -17134,12 +16500,6 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -17151,129 +16511,6 @@ "wrap-ansi": "^2.0.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -17340,31 +16577,31 @@ } }, "webpack-dev-server": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.3.1.tgz", - "integrity": "sha512-jY09LikOyGZrxVTXK0mgIq9y2IhCoJ05848dKZqX1gAGLU1YDqgpOT71+W53JH/wI4v6ky4hm+KvSyW14JEs5A==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.5.1.tgz", + "integrity": "sha512-0IdMGddJcnK9zesZOeHWl4uAOVfypn7DSrdNWtclROkVBXy/TcBN+6eEG1wNfLT9dXVfaRZZsLTJt0mJtgTQgw==", "dev": true, "requires": { "ansi-html": "0.0.7", "bonjour": "^3.5.0", - "chokidar": "^2.1.5", + "chokidar": "^2.1.6", "compression": "^1.7.4", "connect-history-api-fallback": "^1.6.0", "debug": "^4.1.1", - "del": "^4.1.0", - "express": "^4.16.4", + "del": "^4.1.1", + "express": "^4.17.1", "html-entities": "^1.2.1", "http-proxy-middleware": "^0.19.1", "import-local": "^2.0.0", - "internal-ip": "^4.2.0", + "internal-ip": "^4.3.0", "ip": "^1.1.5", "killable": "^1.0.1", - "loglevel": "^1.6.1", + "loglevel": "^1.6.2", "opn": "^5.5.0", "portfinder": "^1.0.20", "schema-utils": "^1.0.0", "selfsigned": "^1.10.4", - "semver": "^6.0.0", + "semver": "^6.1.1", "serve-index": "^1.9.1", "sockjs": "0.3.19", "sockjs-client": "1.3.0", @@ -17372,7 +16609,7 @@ "strip-ansi": "^3.0.1", "supports-color": "^6.1.0", "url": "^0.11.0", - "webpack-dev-middleware": "^3.6.2", + "webpack-dev-middleware": "^3.7.0", "webpack-log": "^2.0.0", "yargs": "12.0.5" }, @@ -17445,16 +16682,10 @@ } } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "chokidar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", - "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -17493,27 +16724,6 @@ } } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -17531,21 +16741,6 @@ } } }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -17706,19 +16901,10 @@ } } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, "fsevents": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", - "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "dev": true, "optional": true, "requires": { @@ -18263,15 +17449,6 @@ } } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -18293,12 +17470,6 @@ } } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", @@ -18375,36 +17546,6 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -18426,16 +17567,16 @@ "to-regex": "^3.0.2" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "mime": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", + "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==", "dev": true }, "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -18445,39 +17586,10 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true }, "readdirp": { @@ -18492,9 +17604,9 @@ } }, "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", "dev": true }, "supports-color": { @@ -18507,14 +17619,14 @@ } }, "webpack-dev-middleware": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.6.2.tgz", - "integrity": "sha512-A47I5SX60IkHrMmZUlB0ZKSWi29TZTcPz7cha1Z75yYOsgWh/1AcPmQEbC8ZIbU3A1ytSv1PMU0PyPz2Lmz2jg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", + "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", "dev": true, "requires": { "memory-fs": "^0.4.1", - "mime": "^2.3.1", - "range-parser": "^1.0.3", + "mime": "^2.4.2", + "range-parser": "^1.2.1", "webpack-log": "^2.0.0" } }, @@ -18643,9 +17755,9 @@ "dev": true }, "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "dev": true, "requires": { "errno": "~0.1.7" @@ -18736,33 +17848,124 @@ "dev": true }, "yargs": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", - "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.1.0.tgz", + "integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + } + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + } } }, "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } }, "yeast": { diff --git a/src/Squidex/package.json b/src/Squidex/package.json index 72f1ab671..cf9b1f8af 100644 --- a/src/Squidex/package.json +++ b/src/Squidex/package.json @@ -15,21 +15,21 @@ "build:clean": "rimraf wwwroot/build" }, "dependencies": { - "@angular/animations": "7.2.14", - "@angular/common": "7.2.14", - "@angular/core": "7.2.14", - "@angular/forms": "7.2.14", - "@angular/http": "7.2.14", - "@angular/platform-browser-dynamic": "7.2.14", - "@angular/platform-browser": "7.2.14", - "@angular/platform-server": "7.2.14", - "@angular/router": "7.2.14", + "@angular/animations": "8.0.0", + "@angular/common": "8.0.0", + "@angular/core": "8.0.0", + "@angular/forms": "8.0.0", + "@angular/http": "7.2.15", + "@angular/platform-browser-dynamic": "8.0.0", + "@angular/platform-browser": "8.0.0", + "@angular/platform-server": "8.0.0", + "@angular/router": "8.0.0", "angular2-chartjs": "0.5.1", "babel-polyfill": "6.26.0", "bootstrap": "4.3.1", - "core-js": "2.6.3", + "core-js": "3.1.3", "graphiql": "0.13.0", - "graphql": "14.2.1", + "graphql": "14.3.1", "marked": "0.6.2", "moment": "2.24.0", "mousetrap": "1.6.3", @@ -40,23 +40,23 @@ "progressbar.js": "1.0.1", "react-dom": "16.8.6", "react": "16.8.6", - "rxjs": "6.5.1", + "rxjs": "6.5.2", "slugify": "1.3.4", "sortablejs": "1.9.0", "tslib": "1.9.3", "zone.js": "0.9.1" }, "devDependencies": { - "@angular/compiler-cli": "7.2.14", - "@angular/compiler": "7.2.14", - "@ngtools/webpack": "7.3.8", + "@angular/compiler-cli": "8.0.0", + "@angular/compiler": "8.0.0", + "@ngtools/webpack": "8.0.1", "@types/core-js": "2.5.0", - "@types/jasmine": "3.3.12", + "@types/jasmine": "3.3.13", "@types/marked": "0.6.5", "@types/mousetrap": "1.6", - "@types/node": "12.0.0", + "@types/node": "12.0.4", "@types/react-dom": "16.8.4", - "@types/react": "16.8.16", + "@types/react": "16.8.19", "@types/sortablejs": "1.7.2", "angular-router-loader": "0.8.5", "angular2-template-loader": "0.6.2", @@ -82,7 +82,7 @@ "karma-sourcemap-loader": "0.3.7", "karma-webpack": "3.0.5", "karma": "4.1.0", - "mini-css-extract-plugin": "0.6.0", + "mini-css-extract-plugin": "0.7.0", "node-sass": "4.12.0", "optimize-css-assets-webpack-plugin": "5.0.1", "raw-loader": "1.0.0", @@ -91,17 +91,17 @@ "sass-lint": "1.13.1", "sass-loader": "7.1.0", "style-loader": "0.23.1", - "ts-loader": "5.4.5", + "ts-loader": "6.0.2", "tsconfig-paths-webpack-plugin": "3.2.0", "tslint-webpack-plugin": "2.0.4", - "tslint": "5.16.0", + "tslint": "5.17.0", "typemoq": "2.1.0", - "typescript": "3.2.4", - "uglifyjs-webpack-plugin": "2.1.2", + "typescript": "3.4.3", + "uglifyjs-webpack-plugin": "2.1.3", "underscore": "1.9.1", - "webpack-cli": "3.3.1", - "webpack-dev-server": "3.3.1", + "webpack-cli": "3.3.2", + "webpack-dev-server": "3.5.1", "webpack-merge": "4.2.1", - "webpack": "4.30.0" + "webpack": "4.32.2" } } diff --git a/src/Squidex/tsconfig.json b/src/Squidex/tsconfig.json index 9bb19bd05..38f1bc4dd 100644 --- a/src/Squidex/tsconfig.json +++ b/src/Squidex/tsconfig.json @@ -3,6 +3,7 @@ "baseUrl": ".", "emitDecoratorMetadata": true, "experimentalDecorators": true, + "incremental": true, "importHelpers": true, "lib": ["es6", "esnext", "dom"], "moduleResolution": "node", From 35d9640b856b5ce1bc2bb27d71d155659aed4b70 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 2 Jun 2019 11:11:44 +0200 Subject: [PATCH 002/175] Simplified build --- src/Squidex/.vscode/settings.json | 36 -- src/Squidex/app-config/helpers.js | 25 - src/Squidex/app-config/karma-test-shim.js | 3 +- src/Squidex/app-config/karma.conf.js | 6 +- src/Squidex/app-config/karma.coverage.conf.js | 6 +- src/Squidex/app-config/webpack.config.js | 446 +++++++++++++----- src/Squidex/app-config/webpack.run.base.js | 35 -- src/Squidex/app-config/webpack.run.dev.js | 68 --- src/Squidex/app-config/webpack.run.prod.js | 137 ------ .../app-config/webpack.test.coverage.js | 37 -- src/Squidex/app-config/webpack.test.js | 16 - .../app/shared/state/assets.state.spec.ts | 31 +- src/Squidex/app/shims.ts | 2 - src/Squidex/package-lock.json | 78 +-- src/Squidex/package.json | 13 +- 15 files changed, 377 insertions(+), 562 deletions(-) delete mode 100644 src/Squidex/.vscode/settings.json delete mode 100644 src/Squidex/app-config/helpers.js delete mode 100644 src/Squidex/app-config/webpack.run.base.js delete mode 100644 src/Squidex/app-config/webpack.run.dev.js delete mode 100644 src/Squidex/app-config/webpack.run.prod.js delete mode 100644 src/Squidex/app-config/webpack.test.coverage.js delete mode 100644 src/Squidex/app-config/webpack.test.js diff --git a/src/Squidex/.vscode/settings.json b/src/Squidex/.vscode/settings.json deleted file mode 100644 index 26a63fc30..000000000 --- a/src/Squidex/.vscode/settings.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - // When opening a file, `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents. - "editor.detectIndentation": false, - - // Typescript version from local package to be consistent - "typescript.tsdk": "node_modules/typescript/lib", - - // Configure glob patterns for excluding files and folders. - "files.exclude": { - "**/node_modules": true, - "**/Assets": true, - "**/artifacts": true, - "**/build": true, - "**/logs": true, - "**/out": true, - "**/obj": true, - "**/bin": true, - "**/*.lock.json": true, - "**/*.bat": true, - "**/*.sln": true, - "**/*.sln.DotSettings": true, - "**/*.user": true, - "**/*.xproj": true, - "**/*.gitattributes": true, - "appsetttings.Development.json": true, - "appsetttings.Production.json": true, - ".awcache": true, - ".vs:": true, - ".vscode:": true - }, - - "coverage-gutters.coverageFileNames": [ - "_test-output/coverage/lcov.info" - ], - "coverage-gutters.showLineCoverage": true -} \ No newline at end of file diff --git a/src/Squidex/app-config/helpers.js b/src/Squidex/app-config/helpers.js deleted file mode 100644 index acae3e4bb..000000000 --- a/src/Squidex/app-config/helpers.js +++ /dev/null @@ -1,25 +0,0 @@ -var path = require('path'); - -var appRoot = path.resolve(__dirname, '..'); - -exports.root = function () { - var newArgs = Array.prototype.slice.call(arguments, 0); - - return path.join.apply(path, [appRoot].concat(newArgs)); -}; - -exports.removeLoaders = function (config, extensions) { - var rules = config.module.rules; - - for (var i = 0; i < rules.length; i += 1) { - var rule = rules[i]; - - for (var j = 0; j < extensions.length; j += 1) { - if (rule.test.source.indexOf(extensions[j]) >= 0) { - rules.splice(i, 1); - i--; - break; - } - } - } -} \ No newline at end of file diff --git a/src/Squidex/app-config/karma-test-shim.js b/src/Squidex/app-config/karma-test-shim.js index 41a41e554..773d09e87 100644 --- a/src/Squidex/app-config/karma-test-shim.js +++ b/src/Squidex/app-config/karma-test-shim.js @@ -1,7 +1,6 @@ Error.stackTraceLimit = Infinity; -require('core-js/es6'); -require('core-js/es7/reflect'); +require('core-js/proposals/reflect-metadata'); require('zone.js/dist/zone'); require('zone.js/dist/long-stack-trace-zone'); diff --git a/src/Squidex/app-config/karma.conf.js b/src/Squidex/app-config/karma.conf.js index 8b64456f4..f4776e18a 100644 --- a/src/Squidex/app-config/karma.conf.js +++ b/src/Squidex/app-config/karma.conf.js @@ -1,4 +1,4 @@ -var webpackConfig = require('./webpack.test'); +const webpackConfig = require('./webpack.config'); module.exports = function (config) { var _config = { @@ -10,7 +10,7 @@ module.exports = function (config) { frameworks: ['jasmine'], /** - * Load additional test shim to setup angular2 for testing. + * Load additional test shim to setup angular for testing. */ files: [ { pattern: './app-config/karma-test-shim.js', watched: false } @@ -23,7 +23,7 @@ module.exports = function (config) { /** * Load the files with webpack and use test configuration for it. */ - webpack: webpackConfig, + webpack: webpackConfig({ target: 'tests', jit: true }), webpackMiddleware: { stats: 'errors-only' diff --git a/src/Squidex/app-config/karma.coverage.conf.js b/src/Squidex/app-config/karma.coverage.conf.js index 441b998b9..8a9b849bf 100644 --- a/src/Squidex/app-config/karma.coverage.conf.js +++ b/src/Squidex/app-config/karma.coverage.conf.js @@ -1,4 +1,4 @@ -var webpackConfig = require('./webpack.test.coverage'); +const webpackConfig = require('./webpack.config'); module.exports = function (config) { var _config = { @@ -10,7 +10,7 @@ module.exports = function (config) { frameworks: ['jasmine'], /** - * Load additional test shim to setup angular2 for testing. + * Load additional test shim to setup angular for testing. */ files: [ { pattern: './app-config/karma-test-shim.js', watched: false } @@ -23,7 +23,7 @@ module.exports = function (config) { /** * Load the files with webpack and use test configuration for it. */ - webpack: webpackConfig, + webpack: webpackConfig({ target: 'tests', coverage: true, jit: true }), webpackMiddleware: { stats: 'errors-only' diff --git a/src/Squidex/app-config/webpack.config.js b/src/Squidex/app-config/webpack.config.js index 5ff10d725..edaeaa5a8 100644 --- a/src/Squidex/app-config/webpack.config.js +++ b/src/Squidex/app-config/webpack.config.js @@ -1,6 +1,13 @@ const webpack = require('webpack'), - path = require('path'), - helpers = require('./helpers'); + path = require('path'); + +const appRoot = path.resolve(__dirname, '..'); + +function root() { + var newArgs = Array.prototype.slice.call(arguments, 0); + + return path.join.apply(path, [appRoot].concat(newArgs)); +}; const plugins = { // https://github.com/webpack-contrib/mini-css-extract-plugin @@ -8,147 +15,356 @@ const plugins = { // https://github.com/dividab/tsconfig-paths-webpack-plugin TsconfigPathsPlugin: require('tsconfig-paths-webpack-plugin'), // https://github.com/aackerman/circular-dependency-plugin - CircularDependencyPlugin: require('circular-dependency-plugin') + CircularDependencyPlugin: require('circular-dependency-plugin'), + // https://github.com/jantimon/html-webpack-plugin + HtmlWebpackPlugin: require('html-webpack-plugin'), + // https://github.com/mishoo/UglifyJS2/tree/harmony + UglifyJsPlugin: require('uglifyjs-webpack-plugin'), + // https://www.npmjs.com/package/@ngtools/webpack + NgToolsWebpack: require('@ngtools/webpack'), + // https://github.com/NMFR/optimize-css-assets-webpack-plugin + OptimizeCSSAssetsPlugin: require("optimize-css-assets-webpack-plugin"), + // https://github.com/jrparish/tslint-webpack-plugin + TsLintPlugin: require('tslint-webpack-plugin') }; -const isDevServer = path.basename(require.main.filename) === 'webpack-dev-server.js'; +module.exports = function(env) { + const isDevServer = path.basename(require.main.filename) === 'webpack-dev-server.js'; + const isProduction = env && env.production; + const isTesting = env && env.target === 'tests'; + const isCoverage = env && env.coverage; + const isJit = env && env.jit; + + const config = { + mode: isProduction ? 'production' : 'development', -module.exports = { - /** - * Options affecting the resolving of modules. - * - * See: https://webpack.js.org/configuration/resolve/ - */ - resolve: { /** - * An array of extensions that should be used to resolve modules. + * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack. * - * See: https://webpack.js.org/configuration/resolve/#resolve-extensions + * See: https://webpack.js.org/configuration/devtool/ */ - extensions: ['.js', '.mjs', '.ts', '.css', '.scss'], - modules: [ - helpers.root('app'), - helpers.root('app', 'theme'), - helpers.root('node_modules') - ], + devtool: isProduction ? undefined : (isTesting ? 'inline-source-map' : 'source-map'), + + /** + * Options affecting the resolving of modules. + * + * See: https://webpack.js.org/configuration/resolve/ + */ + resolve: { + /** + * An array of extensions that should be used to resolve modules. + * + * See: https://webpack.js.org/configuration/resolve/#resolve-extensions + */ + extensions: ['.js', '.mjs', '.ts', '.css', '.scss'], + modules: [ + root('app'), + root('app', 'theme'), + root('node_modules') + ], + + plugins: [ + new plugins.TsconfigPathsPlugin() + ] + }, + + /** + * Options affecting the normal modules. + * + * See: https://webpack.js.org/configuration/module/ + */ + module: { + /** + * An array of Rules which are matched to requests when modules are created. + * + * See: https://webpack.js.org/configuration/module/#module-rules + */ + rules: [{ + test: /\.mjs$/, + type: "javascript/auto", + include: [/node_modules/] + }, { + test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/, + parser: { system: true }, + include: [/node_modules/] + }, { + test: /\.js\.flow$/, + use: [{ + loader: 'ignore-loader' + }], + include: [/node_modules/] + }, { + test: /\.html$/, + use: [{ + loader: 'raw-loader' + }] + }, { + test: /\.(woff|woff2|ttf|eot)(\?.*$|$)/, + use: [{ + loader: 'file-loader?name=[name].[hash].[ext]', + options: { + outputPath: 'assets', + /* + * Use custom public path as ./ is not supported by fonts. + */ + publicPath: isDevServer ? undefined : 'assets' + } + }] + }, { + test: /\.(png|jpe?g|gif|svg|ico)(\?.*$|$)/, + use: [{ + loader: 'file-loader?name=[name].[hash].[ext]', + options: { + outputPath: 'assets' + } + }] + }, { + test: /\.css$/, + use: [ + plugins.MiniCssExtractPlugin.loader, + { + loader: 'css-loader' + }] + }, { + test: /\.scss$/, + use: [{ + loader: 'raw-loader' + }, { + loader: 'sass-loader', options: { includePaths: [root('app', 'theme')] } + }], + exclude: root('app', 'theme') + }] + }, plugins: [ - new plugins.TsconfigPathsPlugin() - ] - }, - - /** - * Options affecting the normal modules. - * - * See: https://webpack.js.org/configuration/module/ - */ - module: { + new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, root('./app'), {}), + new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), + + /** + * Puts each bundle into a file and appends the hash of the file to the path. + * + * See: https://github.com/webpack-contrib/mini-css-extract-plugin + */ + new plugins.MiniCssExtractPlugin('[name].css'), + + new webpack.LoaderOptionsPlugin({ + options: { + htmlLoader: { + /** + * Define the root for images, so that we can use absolute urls. + * + * See: https://github.com/webpack/html-loader#Advanced_Options + */ + root: root('app', 'images') + }, + context: '/' + } + }), + + /** + * Detect circular dependencies in app. + * + * See: https://github.com/aackerman/circular-dependency-plugin + */ + new plugins.CircularDependencyPlugin({ + exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/, + // Add errors to webpack instead of warnings + failOnError: true + }), + ], + + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*' + }, + historyApiFallback: true + } + }; + + if (!isTesting) { /** - * An array of Rules which are matched to requests when modules are created. + * The entry point for the bundle. Our Angular app. * - * See: https://webpack.js.org/configuration/module/#module-rules + * See: https://webpack.js.org/configuration/entry-context/ */ - rules: [{ - test: /\.mjs$/, - type: "javascript/auto", - include: [/node_modules/] - }, { - test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/, - parser: { system: true }, - include: [/node_modules/] - }, { + config.entry = { + 'shims': './app/shims.ts', + 'app': './app/app.ts' + }; + + if (isProduction) { + config.output = { + /** + * The output directory as absolute path (required). + * + * See: https://webpack.js.org/configuration/output/#output-path + */ + path: root('wwwroot/build/'), + + publicPath: './build/', + + /** + * Specifies the name of each output file on disk. + * + * See: https://webpack.js.org/configuration/output/#output-filename + */ + filename: '[name].js', + + /** + * The filename of non-entry chunks as relative path inside the output.path directory. + * + * See: https://webpack.js.org/configuration/output/#output-chunkfilename + */ + chunkFilename: '[id].[hash].chunk.js' + }; + } else { + config.output = { + filename: '[name].js', + + /** + * Set the public path, because we are running the website from another port (5000). + */ + publicPath: 'http://localhost:3000/' + }; + } + + config.plugins.push( + new plugins.HtmlWebpackPlugin({ + hash: true, + chunks: ['shims', 'app'], + chunksSortMode: 'manual', + template: 'wwwroot/index.html' + }) + ); + + config.plugins.push( + new plugins.HtmlWebpackPlugin({ + template: 'wwwroot/_theme.html', hash: true, chunksSortMode: 'none', filename: 'theme.html' + }) + ); + + config.plugins.push( + new plugins.TsLintPlugin({ + files: ['./app/**/*.ts'], + /** + * Path to a configuration file. + */ + config: root('tslint.json'), + /** + * Wait for linting and fail the build when linting error occur. + */ + waitForLinting: isProduction + }) + ); + } + + if (isProduction) { + config.optimization = { + minimizer: [ + new plugins.UglifyJsPlugin({ + uglifyOptions: { + compress: false, + ecma: 6, + mangle: true, + output: { + comments: false + } + }, + extractComments: true + }), + + new plugins.OptimizeCSSAssetsPlugin({}) + ] + }; + + config.performance = { + hints: false + }; + } + + if (!isCoverage) { + config.module.rules.push({ test: /\.ts$/, use: [{ loader: 'awesome-typescript-loader' - }, { - loader: 'angular-router-loader' - }, { - loader: 'angular2-template-loader' }], exclude: [/node_modules/] - }, { + }) + } else { + config.module.rules.push({ test: /\.ts$/, use: [{ - loader: 'awesome-typescript-loader' + loader: 'ts-loader' }], - include: [/node_modules/] - }, { - test: /\.js\.flow$/, + include: [/\.(e2e|spec)\.ts$/], + }); + + // Use instrument loader for all normal builds. + config.module.rules.push({ + test: /\.ts$/, use: [{ - loader: 'ignore-loader' + loader: 'istanbul-instrumenter-loader' + }, { + loader: 'ts-loader' }], - include: [/node_modules/] - }, { - test: /\.html$/, - use: [{ - loader: 'raw-loader' - }] - }, { - test: /\.(woff|woff2|ttf|eot)(\?.*$|$)/, - use: [{ - loader: 'file-loader?name=[name].[hash].[ext]', - options: { - outputPath: 'assets', - /* - * Use custom public path as ./ is not supported by fonts. - */ - publicPath: isDevServer ? undefined : 'assets' - } - }] - }, { - test: /\.(png|jpe?g|gif|svg|ico)(\?.*$|$)/, - use: [{ - loader: 'file-loader?name=[name].[hash].[ext]', - options: { - outputPath: 'assets' - } - }] - }, { - test: /\.css$/, + exclude: [/\.(e2e|spec)\.ts$/] + }); + } + + if (isProduction) { + config.module.rules.push({ + test: /\.scss$/, + /* + * Extract the content from a bundle to a file. + * + * See: https://github.com/webpack-contrib/extract-text-webpack-plugin + */ use: [ plugins.MiniCssExtractPlugin.loader, { loader: 'css-loader' - }] - }, { + }, { + loader: 'sass-loader' + }], + /* + * Do not include component styles. + */ + include: root('app', 'theme'), + }); + } else { + config.module.rules.push({ test: /\.scss$/, use: [{ - loader: 'raw-loader' + loader: 'style-loader' }, { - loader: 'sass-loader', options: { includePaths: [helpers.root('app', 'theme')] } + loader: 'css-loader' + }, { + loader: 'sass-loader?sourceMap' }], - exclude: helpers.root('app', 'theme') - }] - }, + /* + * Do not include component styles. + */ + include: root('app', 'theme') + }); + } - plugins: [ - /** - * Puts each bundle into a file and appends the hash of the file to the path. - * - * See: https://github.com/webpack-contrib/mini-css-extract-plugin - */ - new plugins.MiniCssExtractPlugin('[name].css'), - - new webpack.LoaderOptionsPlugin({ - options: { - htmlLoader: { - /** - * Define the root for images, so that we can use absolute urls. - * - * See: https://github.com/webpack/html-loader#Advanced_Options - */ - root: helpers.root('app', 'images') - }, - context: '/' - } - }), - - new plugins.CircularDependencyPlugin({ - exclude: /([\\\/]node_modules[\\\/])|(ngfactory\.js$)/, - // Add errors to webpack instead of warnings - failOnError: true - }), - - new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/) - ] + if (!isJit) { + config.module.rules.push({ + test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, + use: [{ + loader: '@ngtools/webpack' + }] + }); + + config.plugins.push( + new plugins.NgToolsWebpack.AngularCompilerPlugin({ + entryModule: 'app/app.module#AppModule', + sourceMap: !isProduction, + skipSourceGeneration: false, + tsConfigPath: './tsconfig.json' + }) + ); + } + + return config; }; \ No newline at end of file diff --git a/src/Squidex/app-config/webpack.run.base.js b/src/Squidex/app-config/webpack.run.base.js deleted file mode 100644 index 06012fa60..000000000 --- a/src/Squidex/app-config/webpack.run.base.js +++ /dev/null @@ -1,35 +0,0 @@ -const webpack = require('webpack'), - webpackMerge = require('webpack-merge'), - path = require('path'), - helpers = require('./helpers'), - commonConfig = require('./webpack.config.js'); - -const plugins = { - // https://github.com/jantimon/html-webpack-plugin - HtmlWebpackPlugin: require('html-webpack-plugin') -}; - -module.exports = webpackMerge(commonConfig, { - /** - * The entry point for the bundle. Our Angular app. - * - * See: https://webpack.js.org/configuration/entry-context/ - */ - entry: { - 'shims': './app/shims.ts', - 'app': './app/app.ts' - }, - - plugins: [ - new plugins.HtmlWebpackPlugin({ - hash: true, - chunks: ['shims', 'app'], - chunksSortMode: 'manual', - template: 'wwwroot/index.html' - }), - - new plugins.HtmlWebpackPlugin({ - template: 'wwwroot/_theme.html', hash: true, chunksSortMode: 'none', filename: 'theme.html' - }) - ] -}); \ No newline at end of file diff --git a/src/Squidex/app-config/webpack.run.dev.js b/src/Squidex/app-config/webpack.run.dev.js deleted file mode 100644 index 5a34d49d9..000000000 --- a/src/Squidex/app-config/webpack.run.dev.js +++ /dev/null @@ -1,68 +0,0 @@ -const webpack = require('webpack'), - webpackMerge = require('webpack-merge'), - path = require('path'), - helpers = require('./helpers'), - runConfig = require('./webpack.run.base.js'); - -const plugins = { - // https://github.com/jrparish/tslint-webpack-plugin - TsLintPlugin: require('tslint-webpack-plugin') -}; - -module.exports = webpackMerge(runConfig, { - mode: 'development', - - devtool: 'source-map', - - output: { - filename: '[name].js', - - /** - * Set the public path, because we are running the website from another port (5000). - */ - publicPath: 'http://localhost:3000/' - }, - - /* - * Options affecting the normal modules. - * - * See: https://webpack.js.org/configuration/module/ - */ - module: { - /** - * An array of Rules which are matched to requests when modules are created. - * - * See: https://webpack.js.org/configuration/module/#module-rules - */ - rules: [{ - test: /\.scss$/, - use: [{ - loader: 'style-loader' - }, { - loader: 'css-loader' - }, { - loader: 'sass-loader?sourceMap', options: { includePaths: [helpers.root('app', 'theme')] } - }], - include: helpers.root('app', 'theme') - }] - }, - - plugins: [ - new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, helpers.root('./src'), {}), - - new plugins.TsLintPlugin({ - files: ['./app/**/*.ts'], - /** - * Path to a configuration file. - */ - config: helpers.root('tslint.json') - }) - ], - - devServer: { - headers: { - 'Access-Control-Allow-Origin': '*' - }, - historyApiFallback: true - } -}); \ No newline at end of file diff --git a/src/Squidex/app-config/webpack.run.prod.js b/src/Squidex/app-config/webpack.run.prod.js deleted file mode 100644 index 07fbca634..000000000 --- a/src/Squidex/app-config/webpack.run.prod.js +++ /dev/null @@ -1,137 +0,0 @@ -const webpack = require('webpack'), - webpackMerge = require('webpack-merge'), - path = require('path'), - helpers = require('./helpers'), - runConfig = require('./webpack.run.base.js'); - -const plugins = { - // https://github.com/mishoo/UglifyJS2/tree/harmony - UglifyJsPlugin: require('uglifyjs-webpack-plugin'), - // https://www.npmjs.com/package/@ngtools/webpack - NgToolsWebpack: require('@ngtools/webpack'), - // https://github.com/webpack-contrib/mini-css-extract-plugin - MiniCssExtractPlugin: require('mini-css-extract-plugin'), - // https://github.com/NMFR/optimize-css-assets-webpack-plugin - OptimizeCSSAssetsPlugin: require("optimize-css-assets-webpack-plugin"), - // https://github.com/jrparish/tslint-webpack-plugin - TsLintPlugin: require('tslint-webpack-plugin') -}; - -helpers.removeLoaders(runConfig, ['scss', 'ts']); - -module.exports = webpackMerge(runConfig, { - mode: 'production', - - output: { - /** - * The output directory as absolute path (required). - * - * See: https://webpack.js.org/configuration/output/#output-path - */ - path: helpers.root('wwwroot/build/'), - - publicPath: './build/', - - /** - * Specifies the name of each output file on disk. - * - * See: https://webpack.js.org/configuration/output/#output-filename - */ - filename: '[name].js', - - /** - * The filename of non-entry chunks as relative path inside the output.path directory. - * - * See: https://webpack.js.org/configuration/output/#output-chunkfilename - */ - chunkFilename: '[id].[hash].chunk.js' - }, - - /* - * Options affecting the normal modules. - * - * See: https://webpack.js.org/configuration/module/ - */ - module: { - /** - * An array of Rules which are matched to requests when modules are created. - * - * See: https://webpack.js.org/configuration/module/#module-rules - */ - rules: [{ - test: /\.scss$/, - /* - * Extract the content from a bundle to a file. - * - * See: https://github.com/webpack-contrib/extract-text-webpack-plugin - */ - use: [ - plugins.MiniCssExtractPlugin.loader, - { - loader: 'css-loader' - }, { - loader: 'sass-loader' - }], - /* - * Do not include component styles. - */ - include: helpers.root('app', 'theme'), - }, { - test: /\.scss$/, - use: [{ - loader: 'raw-loader' - }, { - loader: 'sass-loader', options: { includePaths: [helpers.root('app', 'theme')] } - }], - exclude: helpers.root('app', 'theme'), - }, { - test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, - use: [{ - loader: '@ngtools/webpack' - }] - }] - }, - - plugins: [ - new plugins.NgToolsWebpack.AngularCompilerPlugin({ - entryModule: 'app/app.module#AppModule', - sourceMap: false, - skipSourceGeneration: false, - tsConfigPath: './tsconfig.json' - }), - - new plugins.TsLintPlugin({ - files: ['./app/**/*.ts'], - /** - * Path to a configuration file. - */ - config: helpers.root('tslint.json'), - /** - * Wait for linting and fail the build when linting error occur. - */ - waitForLinting: true - }) - ], - - optimization: { - minimizer: [ - new plugins.UglifyJsPlugin({ - uglifyOptions: { - compress: false, - ecma: 6, - mangle: true, - output: { - comments: false - } - }, - extractComments: true - }), - - new plugins.OptimizeCSSAssetsPlugin({}) - ] - }, - - performance: { - hints: false - } -}); \ No newline at end of file diff --git a/src/Squidex/app-config/webpack.test.coverage.js b/src/Squidex/app-config/webpack.test.coverage.js deleted file mode 100644 index 1989b80d4..000000000 --- a/src/Squidex/app-config/webpack.test.coverage.js +++ /dev/null @@ -1,37 +0,0 @@ -const webpack = require('webpack'), - webpackMerge = require('webpack-merge'), - path = require('path'), - helpers = require('./helpers'), - testConfig = require('./webpack.test.js'); - -helpers.removeLoaders(testConfig, ['ts']); - -module.exports = webpackMerge(testConfig, { - module: { - /** - * An array of Rules which are matched to requests when modules are created. - * - * See: https://webpack.js.org/configuration/module/#module-rules - */ - rules: [{ - test: /\.ts$/, - use: [{ - loader: 'ts-loader' - }], - include: [/\.(e2e|spec)\.ts$/], - - }, { - test: /\.ts$/, - use: [{ - loader: 'istanbul-instrumenter-loader' - }, { - loader: 'ts-loader' - }, { - loader: 'angular-router-loader' - }, { - loader: 'angular2-template-loader' - }], - exclude: [/\.(e2e|spec)\.ts$/] - }] - } -}); \ No newline at end of file diff --git a/src/Squidex/app-config/webpack.test.js b/src/Squidex/app-config/webpack.test.js deleted file mode 100644 index eacdb4361..000000000 --- a/src/Squidex/app-config/webpack.test.js +++ /dev/null @@ -1,16 +0,0 @@ -const webpack = require('webpack'), - webpackMerge = require('webpack-merge'), - path = require('path'), - helpers = require('./helpers'), - commonConfig = require('./webpack.config.js'); - -module.exports = webpackMerge(commonConfig, { - mode: 'development', - - /** - * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack. - * - * See: https://webpack.js.org/configuration/devtool/ - */ - devtool: 'inline-source-map' -}); \ No newline at end of file diff --git a/src/Squidex/app/shared/state/assets.state.spec.ts b/src/Squidex/app/shared/state/assets.state.spec.ts index 17f604219..cf9e52d59 100644 --- a/src/Squidex/app/shared/state/assets.state.spec.ts +++ b/src/Squidex/app/shared/state/assets.state.spec.ts @@ -54,13 +54,15 @@ describe('AssetsState', () => { }); describe('Loading', () => { + beforeEach(() => { + assetsService.setup(x => x.getTags(app)) + .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable(Times.atLeastOnce()); + }); + it('should load assets', () => { assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) .returns(() => of(new AssetsDto(200, oldAssets))).verifiable(); - assetsService.setup(x => x.getTags(app)) - .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable(); - assetsState.load().subscribe(); expect(assetsState.snapshot.assets.values).toEqual(oldAssets); @@ -72,10 +74,7 @@ describe('AssetsState', () => { it('should show notification on load when reload is true', () => { assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) - .returns(() => of(new AssetsDto(200, oldAssets))); - - assetsService.setup(x => x.getTags(app)) - .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable(); + .returns(() => of(new AssetsDto(200, oldAssets))).verifiable(); assetsState.load(true).subscribe(); @@ -86,7 +85,7 @@ describe('AssetsState', () => { it('should load with tags when tag toggled', () => { assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1'])) - .returns(() => of(new AssetsDto(0, []))); + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.toggleTag('tag1').subscribe(); @@ -95,10 +94,10 @@ describe('AssetsState', () => { it('should load without tags when tag toggled', () => { assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1'])) - .returns(() => of(new AssetsDto(0, []))); + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) - .returns(() => of(new AssetsDto(0, []))); + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.toggleTag('tag1').subscribe(); assetsState.toggleTag('tag1').subscribe(); @@ -108,7 +107,7 @@ describe('AssetsState', () => { it('should load with tags when tags selected', () => { assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1', 'tag2'])) - .returns(() => of(new AssetsDto(0, []))); + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.selectTags(['tag1', 'tag2']).subscribe(); @@ -117,7 +116,7 @@ describe('AssetsState', () => { it('should load without tags when tags reset', () => { assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) - .returns(() => of(new AssetsDto(0, []))); + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.resetTags().subscribe(); @@ -125,9 +124,13 @@ describe('AssetsState', () => { }); it('should load next page and prev page when paging', () => { + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) + .returns(() => of(new AssetsDto(200, []))).verifiable(Times.exactly(2)); + assetsService.setup(x => x.getAssets(app, 30, 30, undefined, [])) - .returns(() => of(new AssetsDto(200, []))); + .returns(() => of(new AssetsDto(200, []))).verifiable(); + assetsState.load().subscribe(); assetsState.goNext().subscribe(); assetsState.goPrev().subscribe(); @@ -136,7 +139,7 @@ describe('AssetsState', () => { it('should load with query when searching', () => { assetsService.setup(x => x.getAssets(app, 30, 0, 'my-query', [])) - .returns(() => of(new AssetsDto(0, []))); + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.search('my-query').subscribe(); diff --git a/src/Squidex/app/shims.ts b/src/Squidex/app/shims.ts index 4c18a84a4..f2b179e2a 100644 --- a/src/Squidex/app/shims.ts +++ b/src/Squidex/app/shims.ts @@ -92,6 +92,4 @@ import 'core-js/modules/es.set'; import 'core-js/modules/es.weak-map'; import 'core-js/modules/web.dom-collections.iterator'; -import 'core-js/proposals/reflect-metadata'; - import 'zone.js/dist/zone'; \ No newline at end of file diff --git a/src/Squidex/package-lock.json b/src/Squidex/package-lock.json index 183d8b57a..721adebda 100644 --- a/src/Squidex/package-lock.json +++ b/src/Squidex/package-lock.json @@ -1669,15 +1669,6 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, - "angular-router-loader": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/angular-router-loader/-/angular-router-loader-0.8.5.tgz", - "integrity": "sha512-8wggCTKGgzB1o8co3Wvj+p9pKN7T7q3C477lEz3NLjvPVzUti8rv9i45Di+4aO/k+HvzGh3s8QdNlXU2Bl4avQ==", - "dev": true, - "requires": { - "loader-utils": "^1.0.2" - } - }, "angular2-chartjs": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/angular2-chartjs/-/angular2-chartjs-0.5.1.tgz", @@ -1686,29 +1677,6 @@ "chart.js": "^2.3.0" } }, - "angular2-template-loader": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz", - "integrity": "sha1-wNROkP/w+sleiyPwQ6zaf9HFHXw=", - "dev": true, - "requires": { - "loader-utils": "^0.2.15" - }, - "dependencies": { - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - } - } - }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -2879,14 +2847,13 @@ } }, "browserslist": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.5.tgz", - "integrity": "sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w==", - "dev": true, + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.1.tgz", + "integrity": "sha512-1MC18ooMPRG2UuVFJTHFIAkk6mpByJfxCrnUyvSlu/hyQSFHMrlhM02SzNuCV+quTP4CKmqtOMAIjrifrpBJXQ==", "requires": { - "caniuse-lite": "^1.0.30000912", - "electron-to-chromium": "^1.3.86", - "node-releases": "^1.0.5" + "caniuse-lite": "^1.0.30000971", + "electron-to-chromium": "^1.3.137", + "node-releases": "^1.1.21" } }, "buffer": { @@ -3133,10 +3100,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000918", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000918.tgz", - "integrity": "sha512-CAZ9QXGViBvhHnmIHhsTPSWFBujDaelKnUj7wwImbyQRxmXynYqKGi3UaZTSz9MoVh+1EVxOS/DFIkrJYgR3aw==", - "dev": true + "version": "1.0.30000971", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz", + "integrity": "sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g==" }, "canonical-path": { "version": "1.0.0", @@ -4600,10 +4566,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.90", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.90.tgz", - "integrity": "sha512-IjJZKRhFbWSOX1w0sdIXgp4CMRguu6UYcTckyFF/Gjtemsu/25eZ+RXwFlV+UWcIueHyQA1UnRJxocTpH5NdGA==", - "dev": true + "version": "1.3.142", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.142.tgz", + "integrity": "sha512-GLOB/wAA2g9l5Hwg1XrPqd6br2WNOPIY8xl/q+g5zZdv3b5fB69oFOooxKxc0DfDfDS1RqaF6hKjwt6v4fuFUw==" }, "elliptic": { "version": "6.4.1", @@ -10352,10 +10317,9 @@ } }, "node-releases": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.1.tgz", - "integrity": "sha512-2UXrBr6gvaebo5TNF84C66qyJJ6r0kxBObgZIDX3D3/mt1ADKiHux3NJPWisq0wxvJJdkjECH+9IIKYViKj71Q==", - "dev": true, + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.22.tgz", + "integrity": "sha512-O6XpteBuntW1j86mw6LlovBIwTe+sO2+7vi9avQffNeIW4upgnaCVm6xrBWH+KATz7mNNRNNeEpuWB7dT6Cr3w==", "requires": { "semver": "^5.3.0" } @@ -13206,8 +13170,7 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "semver-dsl": { "version": "1.0.1", @@ -17684,15 +17647,6 @@ "uuid": "^3.1.0" } }, - "webpack-merge": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", - "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, "webpack-sources": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", diff --git a/src/Squidex/package.json b/src/Squidex/package.json index cf9b1f8af..7453f5829 100644 --- a/src/Squidex/package.json +++ b/src/Squidex/package.json @@ -6,12 +6,12 @@ "repository": "https://github.com/SebastianStehle/Squidex", "scripts": { "copy": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/", - "start": "npm run copy && webpack-dev-server --config app-config/webpack.run.dev.js --inline --port 3000 --hot", + "start": "npm run copy && webpack-dev-server --config app-config/webpack.config.js --inline --port 3000 --hot", "test": "karma start", "test:coverage": "karma start karma.coverage.conf.js", "test:clean": "rimraf _test-output", "tslint": "tslint -c tslint.json -p tsconfig.json app/**/*.ts", - "build": "npm run copy && webpack --config app-config/webpack.run.prod.js", + "build": "npm run copy && webpack --config app-config/webpack.config.js --env.production", "build:clean": "rimraf wwwroot/build" }, "dependencies": { @@ -20,13 +20,15 @@ "@angular/core": "8.0.0", "@angular/forms": "8.0.0", "@angular/http": "7.2.15", - "@angular/platform-browser-dynamic": "8.0.0", "@angular/platform-browser": "8.0.0", + "@angular/platform-browser-dynamic": "8.0.0", "@angular/platform-server": "8.0.0", "@angular/router": "8.0.0", "angular2-chartjs": "0.5.1", "babel-polyfill": "6.26.0", "bootstrap": "4.3.1", + "browserslist": "^4.6.1", + "caniuse-lite": "^1.0.30000971", "core-js": "3.1.3", "graphiql": "0.13.0", "graphql": "14.3.1", @@ -38,8 +40,8 @@ "oidc-client": "1.7.1", "pikaday": "1.8.0", "progressbar.js": "1.0.1", - "react-dom": "16.8.6", "react": "16.8.6", + "react-dom": "16.8.6", "rxjs": "6.5.2", "slugify": "1.3.4", "sortablejs": "1.9.0", @@ -58,8 +60,6 @@ "@types/react-dom": "16.8.4", "@types/react": "16.8.19", "@types/sortablejs": "1.7.2", - "angular-router-loader": "0.8.5", - "angular2-template-loader": "0.6.2", "awesome-typescript-loader": "5.2.1", "babel-core": "6.26.3", "circular-dependency-plugin": "5.0.2", @@ -101,7 +101,6 @@ "underscore": "1.9.1", "webpack-cli": "3.3.2", "webpack-dev-server": "3.5.1", - "webpack-merge": "4.2.1", "webpack": "4.32.2" } } From 6c6494c1369d6ef4f247b6309afedabe3257f971 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 7 Jun 2019 08:57:16 +0200 Subject: [PATCH 003/175] Test --- src/Squidex.Web/Extensions.cs | 8 ++ src/Squidex.Web/PermissionExtensions.cs | 77 +++++++++++++++++++ src/Squidex.Web/Resource.cs | 52 +++++++++++++ src/Squidex.Web/ResourceLink.cs | 23 ++++++ src/Squidex.Web/UrlHelperExtensions.cs | 46 +++++++++++ .../Api/Controllers/Users/Models/UserDto.cs | 53 ++++++++++++- .../Api/Controllers/Users/Models/UsersDto.cs | 34 +++++++- .../Users/UserManagementController.cs | 22 +----- .../Api/Controllers/Users/UsersController.cs | 4 +- .../pages/users/user-page.component.html | 2 +- .../pages/users/user-page.component.ts | 6 +- .../pages/users/users-page.component.html | 30 +++----- .../administration/services/users.service.ts | 31 +++++--- .../administration/state/users.state.spec.ts | 49 +++++------- .../administration/state/users.state.ts | 57 ++++---------- .../framework/angular/http/hateos.pipes.ts | 23 ++++++ src/Squidex/app/framework/declarations.ts | 1 + src/Squidex/app/framework/internal.ts | 1 + src/Squidex/app/framework/module.ts | 6 ++ src/Squidex/app/framework/utils/hateos.ts | 25 ++++++ 20 files changed, 419 insertions(+), 131 deletions(-) create mode 100644 src/Squidex.Web/PermissionExtensions.cs create mode 100644 src/Squidex.Web/Resource.cs create mode 100644 src/Squidex.Web/ResourceLink.cs create mode 100644 src/Squidex.Web/UrlHelperExtensions.cs create mode 100644 src/Squidex/app/framework/angular/http/hateos.pipes.ts create mode 100644 src/Squidex/app/framework/utils/hateos.ts diff --git a/src/Squidex.Web/Extensions.cs b/src/Squidex.Web/Extensions.cs index b7f7594bf..4ab57d830 100644 --- a/src/Squidex.Web/Extensions.cs +++ b/src/Squidex.Web/Extensions.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Security.Claims; using Squidex.Infrastructure.Security; @@ -40,5 +41,12 @@ namespace Squidex.Web return (null, null); } + + public static bool IsUser(this ApiController controller, string userId) + { + var subject = controller.User.OpenIdSubject(); + + return string.Equals(subject, userId, StringComparison.OrdinalIgnoreCase); + } } } diff --git a/src/Squidex.Web/PermissionExtensions.cs b/src/Squidex.Web/PermissionExtensions.cs new file mode 100644 index 000000000..6dc7d0610 --- /dev/null +++ b/src/Squidex.Web/PermissionExtensions.cs @@ -0,0 +1,77 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Http; +using Squidex.Infrastructure.Security; +using Squidex.Shared; +using Squidex.Shared.Identity; + +namespace Squidex.Web +{ + public static class PermissionExtensions + { + private sealed class PermissionFeature + { + public PermissionSet Permissions { get; } + + public PermissionFeature(PermissionSet permissions) + { + Permissions = permissions; + } + } + + public static PermissionSet GetPermissions(this HttpContext httpContext) + { + var feature = httpContext.Features.Get(); + + if (feature == null) + { + feature = new PermissionFeature(httpContext.User.Permissions()); + + httpContext.Features.Set(feature); + } + + return feature.Permissions; + } + + public static bool HasPermission(this HttpContext httpContext, Permission permission) + { + return httpContext.GetPermissions().Includes(permission); + } + + public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*") + { + return httpContext.GetPermissions().Includes(Permissions.ForApp(id, app, schema)); + } + + public static bool HasPermission(this ApiController controller, Permission permission) + { + return controller.HttpContext.GetPermissions().Includes(permission); + } + + public static bool HasPermission(this ApiController controller, string id, string app = "*", string schema = "*") + { + if (app == "*") + { + if (controller.RouteData.Values.TryGetValue("app", out var value) && value is string s) + { + app = s; + } + } + + if (schema == "*") + { + if (controller.RouteData.Values.TryGetValue("name", out var value) && value is string s) + { + schema = s; + } + } + + return controller.HttpContext.GetPermissions().Includes(Permissions.ForApp(id, app, schema)); + } + } +} diff --git a/src/Squidex.Web/Resource.cs b/src/Squidex.Web/Resource.cs new file mode 100644 index 000000000..b62a5f361 --- /dev/null +++ b/src/Squidex.Web/Resource.cs @@ -0,0 +1,52 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Newtonsoft.Json; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Net.Http; + +namespace Squidex.Web +{ + public abstract class Resource + { + [JsonProperty("_links")] + [Required] + [Display(Description = "The links.")] + public Dictionary Links { get; } = new Dictionary(); + + public void AddSelfLink(string href) + { + AddGetLink("self", href); + } + + public void AddGetLink(string rel, string href) + { + AddLink(rel, HttpMethod.Get, href); + } + + public void AddPostLink(string rel, string href) + { + AddLink(rel, HttpMethod.Post, href); + } + + public void AddPutLink(string rel, string href) + { + AddLink(rel, HttpMethod.Put, href); + } + + public void AddDeleteLink(string rel, string href) + { + AddLink(rel, HttpMethod.Delete, href); + } + + public void AddLink(string rel, HttpMethod method, string href) + { + Links[rel] = new ResourceLink { Href = href, Method = method }; + } + } +} diff --git a/src/Squidex.Web/ResourceLink.cs b/src/Squidex.Web/ResourceLink.cs new file mode 100644 index 000000000..48627ac3a --- /dev/null +++ b/src/Squidex.Web/ResourceLink.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; +using System.Net.Http; + +namespace Squidex.Web +{ + public class ResourceLink + { + [Required] + [Display(Description = "The link url.")] + public string Href { get; set; } + + [Required] + [Display(Description = "The link method.")] + public HttpMethod Method { get; set; } + } +} diff --git a/src/Squidex.Web/UrlHelperExtensions.cs b/src/Squidex.Web/UrlHelperExtensions.cs new file mode 100644 index 000000000..486d48a76 --- /dev/null +++ b/src/Squidex.Web/UrlHelperExtensions.cs @@ -0,0 +1,46 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Mvc; +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace Squidex.Web +{ + public static class UrlHelperExtensions + { + private static class NameOf + { + public static readonly string Controller; + + static NameOf() + { + const string suffix = "Controller"; + + var name = typeof(T).Name; + + if (name.EndsWith(suffix)) + { + name = name.Substring(0, name.Length - suffix.Length); + } + + Controller = name; + } + } + + public static string Url(this IUrlHelper urlHelper, Func action, object values = null) where T : Controller + { + return urlHelper.Action(action(null), NameOf.Controller, values); + } + + public static string Url(this Controller controller, Func action, object values = null) where T : Controller + { + return controller.Url.Url(action, values); + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs index 8a2a5a2d4..2481f73c8 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs @@ -8,12 +8,18 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Security; using Squidex.Shared.Users; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Users.Models { - public sealed class UserDto + public sealed class UserDto : Resource { + private static readonly Permission LockPermission = new Permission(Shared.Permissions.AdminUsersLock); + private static readonly Permission UnlockPermission = new Permission(Shared.Permissions.AdminUsersUnlock); + private static readonly Permission UpdatePermission = new Permission(Shared.Permissions.AdminUsersUpdate); + /// /// The id of the user. /// @@ -44,11 +50,50 @@ namespace Squidex.Areas.Api.Controllers.Users.Models [Required] public IEnumerable Permissions { get; set; } - public static UserDto FromUser(IUser user) + public static UserDto FromUser(IUser user, ApiController controller) + { + var userPermssions = user.Permissions().ToIds(); + var userName = user.DisplayName(); + + var result = SimpleMapper.Map(user, new UserDto { DisplayName = userName, Permissions = userPermssions }); + + return CreateLinks(result, controller); + } + + private static UserDto CreateLinks(UserDto result, ApiController controller) { - var permissions = user.Permissions().ToIds(); + var values = new { id = result.Id }; + + if (controller is UserManagementController) + { + result.AddSelfLink(controller.Url(c => nameof(c.GetUser), values)); + } + else + { + result.AddSelfLink(controller.Url(c => nameof(c.GetUser), values)); + } + + if (!controller.IsUser(result.Id)) + { + if (controller.HasPermission(LockPermission) && !result.IsLocked) + { + result.AddPutLink("lock", controller.Url(c => nameof(c.LockUser), values)); + } + + if (controller.HasPermission(UnlockPermission) && result.IsLocked) + { + result.AddPutLink("unlock", controller.Url(c => nameof(c.UnlockUser), values)); + } + } + + if (controller.HasPermission(UpdatePermission)) + { + result.AddPutLink("update", controller.Url(c => nameof(c.PutUser), values)); + } + + result.AddGetLink("picture", controller.Url(c => nameof(c.GetUserPicture), values)); - return SimpleMapper.Map(user, new UserDto { DisplayName = user.DisplayName(), Permissions = permissions }); + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs index 2d83ff47d..e866c8af4 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs @@ -5,10 +5,19 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; +using System.Linq; +using Squidex.Domain.Users; +using Squidex.Infrastructure.Security; +using Squidex.Shared; +using Squidex.Web; + namespace Squidex.Areas.Api.Controllers.Users.Models { - public sealed class UsersDto + public sealed class UsersDto : Resource { + private static readonly Permission CreatePermissions = new Permission(Permissions.AdminUsersCreate); + /// /// The total number of users. /// @@ -18,5 +27,28 @@ namespace Squidex.Areas.Api.Controllers.Users.Models /// The users. /// public UserDto[] Items { get; set; } + + public static UsersDto FromResults(IEnumerable items, long total, ApiController controller) + { + var result = new UsersDto + { + Total = total, + Items = items.Select(x => UserDto.FromUser(x, controller)).ToArray() + }; + + return CreateLinks(result, controller); + } + + private static UsersDto CreateLinks(UsersDto result, ApiController controller) + { + result.AddSelfLink(controller.Url(c => nameof(c.GetUsers))); + + if (controller.HasPermission(CreatePermissions)) + { + result.AddPostLink("create", controller.Url(c => nameof(c.PostUser))); + } + + return result; + } } } diff --git a/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs b/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs index 7d547be14..16862daab 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs @@ -5,8 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -14,7 +12,6 @@ using Squidex.Areas.Api.Controllers.Users.Models; using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Security; using Squidex.Shared; using Squidex.Web; @@ -43,11 +40,7 @@ namespace Squidex.Areas.Api.Controllers.Users await Task.WhenAll(taskForItems, taskForCount); - var response = new UsersDto - { - Total = taskForCount.Result, - Items = taskForItems.Result.Select(UserDto.FromUser).ToArray() - }; + var response = UsersDto.FromResults(taskForItems.Result, taskForCount.Result, this); return Ok(response); } @@ -64,7 +57,7 @@ namespace Squidex.Areas.Api.Controllers.Users return NotFound(); } - var response = UserDto.FromUser(entity); + var response = UserDto.FromUser(entity, this); return Ok(response); } @@ -96,7 +89,7 @@ namespace Squidex.Areas.Api.Controllers.Users [ApiPermission(Permissions.AdminUsersLock)] public async Task LockUser(string id) { - if (IsSelf(id)) + if (this.IsUser(id)) { throw new ValidationException("Locking user failed.", new ValidationError("You cannot lock yourself.")); } @@ -111,7 +104,7 @@ namespace Squidex.Areas.Api.Controllers.Users [ApiPermission(Permissions.AdminUsersUnlock)] public async Task UnlockUser(string id) { - if (IsSelf(id)) + if (this.IsUser(id)) { throw new ValidationException("Unlocking user failed.", new ValidationError("You cannot unlock yourself.")); } @@ -120,12 +113,5 @@ namespace Squidex.Areas.Api.Controllers.Users return NoContent(); } - - private bool IsSelf(string id) - { - var subject = User.OpenIdSubject(); - - return string.Equals(subject, id, StringComparison.OrdinalIgnoreCase); - } } } diff --git a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs index 36c556adb..5311ff642 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs @@ -76,7 +76,7 @@ namespace Squidex.Areas.Api.Controllers.Users { var entities = await userResolver.QueryByEmailAsync(query); - var models = entities.Where(x => !x.IsHidden()).Select(UserDto.FromUser).ToArray(); + var models = entities.Where(x => !x.IsHidden()).Select(x => UserDto.FromUser(x, this)).ToArray(); return Ok(models); } @@ -110,7 +110,7 @@ namespace Squidex.Areas.Api.Controllers.Users if (entity != null) { - var response = UserDto.FromUser(entity); + var response = UserDto.FromUser(entity, this); return Ok(response); } diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.html b/src/Squidex/app/features/administration/pages/users/user-page.component.html index b9d1bdb51..d23721240 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.html @@ -50,7 +50,7 @@ -
+
diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 0c7e1efc2..9a7e9b71a 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -26,7 +26,7 @@ import { export class UserPageComponent extends ResourceOwner implements OnInit { public canUpdate = false; - public user?: { user: UserDto, isCurrentUser: boolean }; + public user?: UserDto; public userForm = new UserForm(this.formBuilder); constructor( @@ -45,7 +45,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit { this.user = selectedUser!; if (selectedUser) { - this.userForm.load(selectedUser.user); + this.userForm.load(selectedUser); } })); } @@ -55,7 +55,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit { if (value) { if (this.user) { - this.usersState.update(this.user.user, value) + this.usersState.update(this.user, value) .subscribe(() => { this.userForm.submitCompleted(); }, error => { diff --git a/src/Squidex/app/features/administration/pages/users/users-page.component.html b/src/Squidex/app/features/administration/pages/users/users-page.component.html index 223f584bf..167ee3eb9 100644 --- a/src/Squidex/app/features/administration/pages/users/users-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/users-page.component.html @@ -48,32 +48,24 @@
- - + + diff --git a/src/Squidex/app/features/administration/services/users.service.ts b/src/Squidex/app/features/administration/services/users.service.ts index a657eee38..b42fe46a2 100644 --- a/src/Squidex/app/features/administration/services/users.service.ts +++ b/src/Squidex/app/features/administration/services/users.service.ts @@ -14,12 +14,19 @@ import { ApiUrlConfig, Model, pretifyError, - ResultSet + Resource, + ResourceLinks, + ResultSet, + withLinks } from '@app/shared'; -export class UsersDto extends ResultSet {} +export class UsersDto extends ResultSet { + public _links: ResourceLinks; +} export class UserDto extends Model { + public _links: ResourceLinks; + constructor( public readonly id: string, public readonly email: string, @@ -60,17 +67,19 @@ export class UsersService { public getUsers(take: number, skip: number, query?: string): Observable { const url = this.apiUrl.buildUrl(`api/user-management?take=${take}&skip=${skip}&query=${query || ''}`); - return this.http.get<{ total: number, items: any[] }>(url).pipe( + return this.http.get<{ total: number, items: any[] } & Resource>(url).pipe( map(body => { const users = body.items.map(item => - new UserDto( - item.id, - item.email, - item.displayName, - item.permissions, - item.isLocked)); - - return new UsersDto(body.total, users); + withLinks( + new UserDto( + item.id, + item.email, + item.displayName, + item.permissions, + item.isLocked), + item)); + + return withLinks(new UsersDto(body.total, users), body); }), pretifyError('Failed to load users. Please reload.')); } diff --git a/src/Squidex/app/features/administration/state/users.state.spec.ts b/src/Squidex/app/features/administration/state/users.state.spec.ts index c2a912ecd..e735870a5 100644 --- a/src/Squidex/app/features/administration/state/users.state.spec.ts +++ b/src/Squidex/app/features/administration/state/users.state.spec.ts @@ -16,7 +16,7 @@ import { UsersService } from '@app/features/administration/internal'; -import { SnapshotUser, UsersState } from './users.state'; +import { UsersState } from './users.state'; describe('UsersState', () => { const oldUsers = [ @@ -26,21 +26,15 @@ describe('UsersState', () => { const newUser = new UserDto('id3', 'mail3@mail.de', 'name3', ['Permission3'], false); - let authService: IMock; let dialogs: IMock; let usersService: IMock; let usersState: UsersState; beforeEach(() => { - authService = Mock.ofType(); - - authService.setup(x => x.user) - .returns(() => { id: 'id2' }); - dialogs = Mock.ofType(); usersService = Mock.ofType(); - usersState = new UsersState(authService.object, dialogs.object, usersService.object); + usersState = new UsersState(dialogs.object, usersService.object); }); afterEach(() => { @@ -54,10 +48,7 @@ describe('UsersState', () => { usersState.load().subscribe(); - expect(usersState.snapshot.users.values).toEqual([ - { isCurrentUser: false, user: oldUsers[0] }, - { isCurrentUser: true, user: oldUsers[1] } - ]); + expect(usersState.snapshot.users.values).toEqual(oldUsers); expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200); expect(usersState.snapshot.isLoaded).toBeTruthy(); @@ -91,7 +82,7 @@ describe('UsersState', () => { usersState.select('id1').subscribe(); usersState.load().subscribe(); - expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: newUsers[0] }); + expect(usersState.snapshot.selectedUser).toEqual(newUsers[0]); }); it('should load next page and prev page when paging', () => { @@ -127,32 +118,32 @@ describe('UsersState', () => { }); it('should return user on select and not load when already loaded', () => { - let selectedUser: SnapshotUser; + let selectedUser: UserDto; usersState.select('id1').subscribe(x => { selectedUser = x!; }); - expect(selectedUser!.user).toEqual(oldUsers[0]); - expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: oldUsers[0] }); + expect(selectedUser!).toEqual(oldUsers[0]); + expect(usersState.snapshot.selectedUser).toEqual(oldUsers[0]); }); it('should return user on select and load when not loaded', () => { usersService.setup(x => x.getUser('id3')) .returns(() => of(newUser)); - let selectedUser: SnapshotUser; + let selectedUser: UserDto; usersState.select('id3').subscribe(x => { selectedUser = x!; }); - expect(selectedUser!.user).toEqual(newUser); - expect(usersState.snapshot.selectedUser).toEqual({ isCurrentUser: false, user: newUser }); + expect(selectedUser!).toEqual(newUser); + expect(usersState.snapshot.selectedUser).toEqual(newUser); }); it('should return null on select when unselecting user', () => { - let selectedUser: SnapshotUser; + let selectedUser: UserDto; usersState.select(null).subscribe(x => { selectedUser = x!; @@ -166,7 +157,7 @@ describe('UsersState', () => { usersService.setup(x => x.getUser('unknown')) .returns(() => throwError({})).verifiable(); - let selectedUser: SnapshotUser; + let selectedUser: UserDto; usersState.select('unknown').subscribe(x => { selectedUser = x!; @@ -185,7 +176,7 @@ describe('UsersState', () => { const user_1 = usersState.snapshot.users.at(0); - expect(user_1.user.isLocked).toBeTruthy(); + expect(user_1.isLocked).toBeTruthy(); expect(user_1).toBe(usersState.snapshot.selectedUser!); }); @@ -198,7 +189,7 @@ describe('UsersState', () => { const user_1 = usersState.snapshot.users.at(1); - expect(user_1.user.isLocked).toBeFalsy(); + expect(user_1.isLocked).toBeFalsy(); expect(user_1).toBe(usersState.snapshot.selectedUser!); }); @@ -213,9 +204,9 @@ describe('UsersState', () => { const user_1 = usersState.snapshot.users.at(0); - expect(user_1.user.email).toEqual(request.email); - expect(user_1.user.displayName).toEqual(request.displayName); - expect(user_1.user.permissions).toEqual(request.permissions); + expect(user_1.email).toEqual(request.email); + expect(user_1.displayName).toEqual(request.displayName); + expect(user_1.permissions).toEqual(request.permissions); expect(user_1).toBe(usersState.snapshot.selectedUser!); }); @@ -227,11 +218,7 @@ describe('UsersState', () => { usersState.create(request).subscribe(); - expect(usersState.snapshot.users.values).toEqual([ - { isCurrentUser: false, user: newUser }, - { isCurrentUser: false, user: oldUsers[0] }, - { isCurrentUser: true, user: oldUsers[1] } - ]); + expect(usersState.snapshot.users.values).toEqual([newUser, ...oldUsers]); expect(usersState.snapshot.usersPager.numberOfItems).toBe(201); }); }); diff --git a/src/Squidex/app/features/administration/state/users.state.ts b/src/Squidex/app/features/administration/state/users.state.ts index 7de907b24..49c5163cc 100644 --- a/src/Squidex/app/features/administration/state/users.state.ts +++ b/src/Squidex/app/features/administration/state/users.state.ts @@ -7,12 +7,11 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; -import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators'; +import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; import '@app/framework/utils/rxjs-extensions'; import { - AuthService, DialogService, ImmutableArray, Pager, @@ -27,14 +26,6 @@ import { UsersService } from './../services/users.service'; -export interface SnapshotUser { - // The user. - user: UserDto; - - // Indicates if the user is the current user. - isCurrentUser: boolean; -} - interface Snapshot { // The current users. users: UsersList; @@ -49,10 +40,10 @@ interface Snapshot { isLoaded?: boolean; // The selected user. - selectedUser?: SnapshotUser | null; + selectedUser?: UserDto | null; } -export type UsersList = ImmutableArray; +export type UsersList = ImmutableArray; export type UsersResult = { total: number, users: UsersList }; @Injectable() @@ -74,14 +65,13 @@ export class UsersState extends State { distinctUntilChanged()); constructor( - private readonly authState: AuthService, private readonly dialogs: DialogService, private readonly usersService: UsersService ) { super({ users: ImmutableArray.empty(), usersPager: new Pager(0) }); } - public select(id: string | null): Observable { + public select(id: string | null): Observable { return this.loadUser(id).pipe( tap(selectedUser => { this.next(s => ({ ...s, selectedUser })); @@ -94,13 +84,13 @@ export class UsersState extends State { return of(null); } - const found = this.snapshot.users.find(x => x.user.id === id); + const found = this.snapshot.users.find(x => x.id === id); if (found) { return of(found); } - return this.usersService.getUser(id).pipe(map(x => this.createUser(x)), catchError(() => of(null))); + return this.usersService.getUser(id).pipe(catchError(() => of(null))); } public load(isReload = false): Observable { @@ -125,12 +115,12 @@ export class UsersState extends State { this.next(s => { const usersPager = s.usersPager.setCount(total); - const users = ImmutableArray.of(items.map(x => this.createUser(x))); + const users = ImmutableArray.of(items); let selectedUser = s.selectedUser; if (selectedUser) { - selectedUser = users.find(x => x.user.id === selectedUser!.user.id) || selectedUser; + selectedUser = users.find(x => x.id === selectedUser!.id) || selectedUser; } return { ...s, users, usersPager, selectedUser, isLoaded: true }; @@ -143,7 +133,7 @@ export class UsersState extends State { return this.usersService.postUser(request).pipe( tap(created => { this.next(s => { - const users = s.users.pushFront(this.createUser(created)); + const users = s.users.pushFront(created); const usersPager = s.usersPager.incrementCount(); return { ...s, users, usersPager }; @@ -154,7 +144,7 @@ export class UsersState extends State { public update(user: UserDto, request: UpdateUserDto): Observable { return this.usersService.putUser(user.id, request).pipe( - map(() => update(user, request)), + switchMap(() => this.usersService.getUser(user.id)), tap(updated => { this.replaceUser(updated); }), @@ -163,7 +153,7 @@ export class UsersState extends State { public lock(user: UserDto): Observable { return this.usersService.lockUser(user.id).pipe( - map(() => setLocked(user, true)), + switchMap(() => this.usersService.getUser(user.id)), tap(updated => { this.replaceUser(updated); }), @@ -172,7 +162,7 @@ export class UsersState extends State { public unlock(user: UserDto): Observable { return this.usersService.unlockUser(user.id).pipe( - map(() => setLocked(user, false)), + switchMap(() => this.usersService.getUser(user.id)), tap(updated => { this.replaceUser(updated); }), @@ -199,30 +189,15 @@ export class UsersState extends State { private replaceUser(user: UserDto) { return this.next(s => { - const users = s.users.map(u => u.user.id === user.id ? this.createUser(user) : u); + const users = s.users.map(u => u.id === user.id ? user : u); const selectedUser = s.selectedUser && - s.selectedUser.user.id !== user.id ? + s.selectedUser.id !== user.id ? s.selectedUser : - users.find(x => x.user.id === user.id); + users.find(x => x.id === user.id); return { ...s, users, selectedUser }; }); } - - private get userId() { - return this.authState.user!.id; - } - - private createUser(user: UserDto): SnapshotUser { - return { user, isCurrentUser: user.id === this.userId }; - } -} - - -const update = (user: UserDto, request: UpdateUserDto) => - user.with(request); - -const setLocked = (user: UserDto, isLocked: boolean) => - user.with({ isLocked }); \ No newline at end of file +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/http/hateos.pipes.ts b/src/Squidex/app/framework/angular/http/hateos.pipes.ts new file mode 100644 index 000000000..979c95205 --- /dev/null +++ b/src/Squidex/app/framework/angular/http/hateos.pipes.ts @@ -0,0 +1,23 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import { Resource } from '@app/framework/internal'; + +@Pipe({ + name: 'sqxHasLink', + pure: true +}) +export class HasLinkPipe implements PipeTransform { + public transform(value: Resource, rel: string) { + return value._links && !!value._links[rel]; + } +} + +@Pipe({ + name: 'sqxHasNoLink', + pure: true +}) +export class HasNoLinkPipe implements PipeTransform { + public transform(value: Resource, rel: string) { + return !value._links || !value._links[rel]; + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 7111dde33..5cf90253c 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -31,6 +31,7 @@ export * from './angular/forms/validators'; export * from './angular/http/caching.interceptor'; export * from './angular/http/loading.interceptor'; +export * from './angular/http/hateos.pipes'; export * from './angular/http/http-extensions'; export * from './angular/modals/dialog-renderer.component'; diff --git a/src/Squidex/app/framework/internal.ts b/src/Squidex/app/framework/internal.ts index 776e7df49..73b658619 100644 --- a/src/Squidex/app/framework/internal.ts +++ b/src/Squidex/app/framework/internal.ts @@ -23,6 +23,7 @@ export * from './utils/date-helper'; export * from './utils/date-time'; export * from './utils/duration'; export * from './utils/error'; +export * from './utils/hateos'; export * from './utils/interpolator'; export * from './utils/immutable-array'; export * from './utils/math-helper'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 5921bf736..b18aad34b 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -42,6 +42,8 @@ import { FormHintComponent, FromNowPipe, FullDateTimePipe, + HasLinkPipe, + HasNoLinkPipe, HoverBackgroundDirective, IFrameEditorComponent, IgnoreScrollbarDirective, @@ -122,6 +124,8 @@ import { FormHintComponent, FromNowPipe, FullDateTimePipe, + HasLinkPipe, + HasNoLinkPipe, HoverBackgroundDirective, IFrameEditorComponent, IgnoreScrollbarDirective, @@ -188,6 +192,8 @@ import { FormsModule, FromNowPipe, FullDateTimePipe, + HasLinkPipe, + HasNoLinkPipe, HoverBackgroundDirective, IFrameEditorComponent, IgnoreScrollbarDirective, diff --git a/src/Squidex/app/framework/utils/hateos.ts b/src/Squidex/app/framework/utils/hateos.ts new file mode 100644 index 000000000..14e5357d8 --- /dev/null +++ b/src/Squidex/app/framework/utils/hateos.ts @@ -0,0 +1,25 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + + export interface Resource { + _links?: { [rel: string]: ResourceLink }; + } + + export type ResourceLinks = { [rel: string]: ResourceLink }; + export type ResourceLink = { href: string; method: ResourceMethod; }; + + export function withLinks(value: T, source: Resource) { + value._links = source._links; + + return value; + } + + export type ResourceMethod = + 'get' | + 'post' | + 'put' | + 'delete'; \ No newline at end of file From ca77b20d6ad0029089bf250dad73b91cb89a156b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 7 Jun 2019 15:56:49 +0200 Subject: [PATCH 004/175] HATEOS for users. --- .../UserManagerExtensions.cs | 16 +- src/Squidex.Web/Resource.cs | 15 +- src/Squidex.Web/ResourceLink.cs | 3 +- .../Contents/ContentsController.cs | 4 +- .../Controllers/Users/Models/CreateUserDto.cs | 8 +- .../Controllers/Users/Models/PublicUserDto.cs | 26 ---- .../Controllers/Users/Models/UpdateUserDto.cs | 8 +- .../Users/Models/UserCreatedDto.cs | 26 ---- .../Users/UserManagementController.cs | 28 +++- .../Api/Controllers/Users/UsersController.cs | 8 +- .../guards/user-must-exist.guard.spec.ts | 4 +- .../pages/users/user-page.component.html | 14 +- .../pages/users/user-page.component.ts | 10 +- .../pages/users/users-page.component.html | 11 +- .../pages/users/users-page.component.ts | 4 +- .../services/users.service.spec.ts | 139 +++++++++++------- .../administration/services/users.service.ts | 84 ++++++----- .../administration/state/users.state.spec.ts | 88 +++++------ .../administration/state/users.state.ts | 25 ++-- .../framework/angular/http/hateos.pipes.ts | 23 ++- src/Squidex/app/framework/utils/hateos.ts | 47 ++++-- 21 files changed, 330 insertions(+), 261 deletions(-) delete mode 100644 src/Squidex/Areas/Api/Controllers/Users/Models/PublicUserDto.cs delete mode 100644 src/Squidex/Areas/Api/Controllers/Users/Models/UserCreatedDto.cs diff --git a/src/Squidex.Domain.Users/UserManagerExtensions.cs b/src/Squidex.Domain.Users/UserManagerExtensions.cs index 8fa0115a5..2db94237f 100644 --- a/src/Squidex.Domain.Users/UserManagerExtensions.cs +++ b/src/Squidex.Domain.Users/UserManagerExtensions.cs @@ -115,7 +115,7 @@ namespace Squidex.Domain.Users return result; } - public static async Task CreateAsync(this UserManager userManager, IUserFactory factory, UserValues values) + public static async Task CreateAsync(this UserManager userManager, IUserFactory factory, UserValues values) { var user = factory.Create(values.Email); @@ -142,10 +142,10 @@ namespace Squidex.Domain.Users throw; } - return user; + return await userManager.ResolveUserAsync(user); } - public static async Task UpdateAsync(this UserManager userManager, string id, UserValues values) + public static async Task UpdateAsync(this UserManager userManager, string id, UserValues values) { var user = await userManager.FindByIdAsync(id); @@ -155,6 +155,8 @@ namespace Squidex.Domain.Users } await UpdateAsync(userManager, user, values); + + return await userManager.ResolveUserAsync(user); } public static async Task UpdateSafeAsync(this UserManager userManager, IdentityUser user, UserValues values) @@ -193,7 +195,7 @@ namespace Squidex.Domain.Users } } - public static async Task LockAsync(this UserManager userManager, string id) + public static async Task LockAsync(this UserManager userManager, string id) { var user = await userManager.FindByIdAsync(id); @@ -203,9 +205,11 @@ namespace Squidex.Domain.Users } await DoChecked(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100)), "Cannot lock user."); + + return await userManager.ResolveUserAsync(user); } - public static async Task UnlockAsync(this UserManager userManager, string id) + public static async Task UnlockAsync(this UserManager userManager, string id) { var user = await userManager.FindByIdAsync(id); @@ -215,6 +219,8 @@ namespace Squidex.Domain.Users } await DoChecked(() => userManager.SetLockoutEndDateAsync(user, null), "Cannot unlock user."); + + return await userManager.ResolveUserAsync(user); } private static async Task DoChecked(Func> action, string message) diff --git a/src/Squidex.Web/Resource.cs b/src/Squidex.Web/Resource.cs index b62a5f361..a0e68197c 100644 --- a/src/Squidex.Web/Resource.cs +++ b/src/Squidex.Web/Resource.cs @@ -26,25 +26,30 @@ namespace Squidex.Web public void AddGetLink(string rel, string href) { - AddLink(rel, HttpMethod.Get, href); + AddLink(rel, "GET", href); + } + + public void AddPatchLink(string rel, string href) + { + AddLink(rel, "PATCH", href); } public void AddPostLink(string rel, string href) { - AddLink(rel, HttpMethod.Post, href); + AddLink(rel, "POST", href); } public void AddPutLink(string rel, string href) { - AddLink(rel, HttpMethod.Put, href); + AddLink(rel, "PUT", href); } public void AddDeleteLink(string rel, string href) { - AddLink(rel, HttpMethod.Delete, href); + AddLink(rel, "DELETE", href); } - public void AddLink(string rel, HttpMethod method, string href) + public void AddLink(string rel, string method, string href) { Links[rel] = new ResourceLink { Href = href, Method = method }; } diff --git a/src/Squidex.Web/ResourceLink.cs b/src/Squidex.Web/ResourceLink.cs index 48627ac3a..964610e7d 100644 --- a/src/Squidex.Web/ResourceLink.cs +++ b/src/Squidex.Web/ResourceLink.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; -using System.Net.Http; namespace Squidex.Web { @@ -18,6 +17,6 @@ namespace Squidex.Web [Required] [Display(Description = "The link method.")] - public HttpMethod Method { get; set; } + public string Method { get; set; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index fc1e9e63f..bd374a986 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -278,9 +278,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.ThrowIfSchemaNotExistsAsync(Context(), name); - var publishPermission = Permissions.ForApp(Permissions.AppContentsPublish, app, name); - - if (publish && !User.Permissions().Includes(publishPermission)) + if (publish && !this.HasPermission(Permissions.AppContentsPublish, app, name)) { return new StatusCodeResult(123); } diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/CreateUserDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/CreateUserDto.cs index b59f83b53..9dbd2feac 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/CreateUserDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/CreateUserDto.cs @@ -40,7 +40,13 @@ namespace Squidex.Areas.Api.Controllers.Users.Models public UserValues ToValues() { - return new UserValues { Email = Email, DisplayName = DisplayName, Password = Password, Permissions = new PermissionSet(Permissions) }; + return new UserValues + { + Email = Email, + DisplayName = DisplayName, + Password = Password, + Permissions = new PermissionSet(Permissions) + }; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/PublicUserDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/PublicUserDto.cs deleted file mode 100644 index e398c88be..000000000 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/PublicUserDto.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.ComponentModel.DataAnnotations; - -namespace Squidex.Areas.Api.Controllers.Users.Models -{ - public sealed class PublicUserDto - { - /// - /// The id of the user. - /// - [Required] - public string Id { get; set; } - - /// - /// The display name (usually first name and last name) of the user. - /// - [Required] - public string DisplayName { get; set; } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/UpdateUserDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/UpdateUserDto.cs index 77b41f567..d6391da83 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/UpdateUserDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/UpdateUserDto.cs @@ -39,7 +39,13 @@ namespace Squidex.Areas.Api.Controllers.Users.Models public UserValues ToValues() { - return new UserValues { Email = Email, DisplayName = DisplayName, Password = Password, Permissions = new PermissionSet(Permissions) }; + return new UserValues + { + Email = Email, + DisplayName = DisplayName, + Password = Password, + Permissions = new PermissionSet(Permissions) + }; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/UserCreatedDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/UserCreatedDto.cs deleted file mode 100644 index 090d81023..000000000 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/UserCreatedDto.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.ComponentModel.DataAnnotations; - -namespace Squidex.Areas.Api.Controllers.Users.Models -{ - public sealed class UserCreatedDto - { - /// - /// The id of the user. - /// - [Required] - public string Id { get; set; } - - /// - /// Additional permissions for the user. - /// - [Required] - public string[] Permissions { get; set; } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs b/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs index 16862daab..6157b120b 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs @@ -32,6 +32,7 @@ namespace Squidex.Areas.Api.Controllers.Users [HttpGet] [Route("user-management/")] + [ProducesResponseType(typeof(UsersDto), 200)] [ApiPermission(Permissions.AdminUsersRead)] public async Task GetUsers([FromQuery] string query = null, [FromQuery] int skip = 0, [FromQuery] int take = 10) { @@ -47,6 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Users [HttpGet] [Route("user-management/{id}/")] + [ProducesResponseType(typeof(UserDto), 201)] [ApiPermission(Permissions.AdminUsersRead)] public async Task GetUser(string id) { @@ -64,28 +66,33 @@ namespace Squidex.Areas.Api.Controllers.Users [HttpPost] [Route("user-management/")] + [ProducesResponseType(typeof(UserDto), 201)] [ApiPermission(Permissions.AdminUsersCreate)] public async Task PostUser([FromBody] CreateUserDto request) { - var user = await userManager.CreateAsync(userFactory, request.ToValues()); + var entity = await userManager.CreateAsync(userFactory, request.ToValues()); - var response = new UserCreatedDto { Id = user.Id }; + var response = UserDto.FromUser(entity, this); return Ok(response); } [HttpPut] [Route("user-management/{id}/")] + [ProducesResponseType(typeof(UserDto), 201)] [ApiPermission(Permissions.AdminUsersUpdate)] public async Task PutUser(string id, [FromBody] UpdateUserDto request) { - await userManager.UpdateAsync(id, request.ToValues()); + var entity = await userManager.UpdateAsync(id, request.ToValues()); + + var response = UserDto.FromUser(entity, this); - return NoContent(); + return Ok(response); } [HttpPut] [Route("user-management/{id}/lock/")] + [ProducesResponseType(typeof(UserDto), 201)] [ApiPermission(Permissions.AdminUsersLock)] public async Task LockUser(string id) { @@ -94,13 +101,16 @@ namespace Squidex.Areas.Api.Controllers.Users throw new ValidationException("Locking user failed.", new ValidationError("You cannot lock yourself.")); } - await userManager.LockAsync(id); + var entity = await userManager.LockAsync(id); + + var response = UserDto.FromUser(entity, this); - return NoContent(); + return Ok(response); } [HttpPut] [Route("user-management/{id}/unlock/")] + [ProducesResponseType(typeof(UserDto), 201)] [ApiPermission(Permissions.AdminUsersUnlock)] public async Task UnlockUser(string id) { @@ -109,9 +119,11 @@ namespace Squidex.Areas.Api.Controllers.Users throw new ValidationException("Unlocking user failed.", new ValidationError("You cannot unlock yourself.")); } - await userManager.UnlockAsync(id); + var entity = await userManager.UnlockAsync(id); - return NoContent(); + var response = UserDto.FromUser(entity, this); + + return Ok(response); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs index 5311ff642..a5cf31c74 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs @@ -68,7 +68,7 @@ namespace Squidex.Areas.Api.Controllers.Users /// [HttpGet] [Route("users/")] - [ProducesResponseType(typeof(PublicUserDto[]), 200)] + [ProducesResponseType(typeof(UserDto[]), 200)] [ApiPermission] public async Task GetUsers(string query) { @@ -76,9 +76,9 @@ namespace Squidex.Areas.Api.Controllers.Users { var entities = await userResolver.QueryByEmailAsync(query); - var models = entities.Where(x => !x.IsHidden()).Select(x => UserDto.FromUser(x, this)).ToArray(); + var response = entities.Where(x => !x.IsHidden()).Select(x => UserDto.FromUser(x, this)).ToArray(); - return Ok(models); + return Ok(response); } catch (Exception ex) { @@ -100,7 +100,7 @@ namespace Squidex.Areas.Api.Controllers.Users /// [HttpGet] [Route("users/{id}/")] - [ProducesResponseType(typeof(PublicUserDto), 200)] + [ProducesResponseType(typeof(UserDto), 200)] [ApiPermission] public async Task GetUser(string id) { diff --git a/src/Squidex/app/features/administration/guards/user-must-exist.guard.spec.ts b/src/Squidex/app/features/administration/guards/user-must-exist.guard.spec.ts index a5c03338f..a12487077 100644 --- a/src/Squidex/app/features/administration/guards/user-must-exist.guard.spec.ts +++ b/src/Squidex/app/features/administration/guards/user-must-exist.guard.spec.ts @@ -9,7 +9,7 @@ import { Router } from '@angular/router'; import { of } from 'rxjs'; import { IMock, Mock, Times } from 'typemoq'; -import { SnapshotUser, UsersState } from '@app/features/administration/internal'; +import { UserDto, UsersState } from '@app/features/administration/internal'; import { UserMustExistGuard } from './user-must-exist.guard'; @@ -32,7 +32,7 @@ describe('UserMustExistGuard', () => { it('should load user and return true when found', () => { usersState.setup(x => x.select('123')) - .returns(() => of({})); + .returns(() => of({})); let result: boolean; diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.html b/src/Squidex/app/features/administration/pages/users/user-page.component.html index d23721240..44a638173 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.html @@ -15,12 +15,14 @@ - - - - + + + + + + diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 9a7e9b71a..53a9ac15e 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -9,7 +9,7 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { ResourceOwner } from '@app/shared'; +import { hasLink, ResourceOwner } from '@app/shared'; import { CreateUserDto, @@ -46,11 +46,19 @@ export class UserPageComponent extends ResourceOwner implements OnInit { if (selectedUser) { this.userForm.load(selectedUser); + + if (!hasLink(selectedUser, 'update')) { + this.userForm.form.disable(); + } } })); } public save() { + if (this.userForm.form.disabled) { + return; + } + const value = this.userForm.submit(); if (value) { diff --git a/src/Squidex/app/features/administration/pages/users/users-page.component.html b/src/Squidex/app/features/administration/pages/users/users-page.component.html index 167ee3eb9..f71733085 100644 --- a/src/Squidex/app/features/administration/pages/users/users-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/users-page.component.html @@ -12,15 +12,18 @@ -
- + + + + +
diff --git a/src/Squidex/app/features/administration/pages/users/users-page.component.ts b/src/Squidex/app/features/administration/pages/users/users-page.component.ts index 065b20858..f029712b4 100644 --- a/src/Squidex/app/features/administration/pages/users/users-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/users-page.component.ts @@ -51,8 +51,8 @@ export class UsersPageComponent implements OnInit { this.usersState.unlock(user); } - public trackByUser(index: number, userInfo: { user: UserDto }) { - return userInfo.user.id; + public trackByUser(index: number, user: UserDto) { + return user.id; } } diff --git a/src/Squidex/app/features/administration/services/users.service.spec.ts b/src/Squidex/app/features/administration/services/users.service.spec.ts index ee74d079f..03b9b68fb 100644 --- a/src/Squidex/app/features/administration/services/users.service.spec.ts +++ b/src/Squidex/app/features/administration/services/users.service.spec.ts @@ -8,7 +8,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; -import { ApiUrlConfig } from '@app/framework'; +import { ApiUrlConfig, Resource } from '@app/framework'; import { UserDto, @@ -50,27 +50,15 @@ describe('UsersService', () => { req.flush({ total: 100, items: [ - { - id: '123', - email: 'mail1@domain.com', - displayName: 'User1', - permissions: ['Permission1'], - isLocked: true - }, - { - id: '456', - email: 'mail2@domain.com', - displayName: 'User2', - permissions: ['Permission2'], - isLocked: true - } + userResponse(12), + userResponse(13) ] }); expect(users!).toEqual( new UsersDto(100, [ - new UserDto('123', 'mail1@domain.com', 'User1', ['Permission1'], true), - new UserDto('456', 'mail2@domain.com', 'User2', ['Permission2'], true) + createUser(12), + createUser(13) ])); })); @@ -91,27 +79,15 @@ describe('UsersService', () => { req.flush({ total: 100, items: [ - { - id: '123', - email: 'mail1@domain.com', - displayName: 'User1', - permissions: ['Permission1'], - isLocked: true - }, - { - id: '456', - email: 'mail2@domain.com', - displayName: 'User2', - permissions: ['Permission2'], - isLocked: true - } + userResponse(12), + userResponse(13) ] }); expect(users!).toEqual( new UsersDto(100, [ - new UserDto('123', 'mail1@domain.com', 'User1', ['Permission1'], true), - new UserDto('456', 'mail2@domain.com', 'User2', ['Permission2'], true) + createUser(12), + createUser(13) ])); })); @@ -129,15 +105,9 @@ describe('UsersService', () => { expect(req.request.method).toEqual('GET'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({ - id: '123', - email: 'mail1@domain.com', - displayName: 'User1', - permissions: ['Permission1'], - isLocked: true - }); + req.flush(userResponse(12)); - expect(user!).toEqual(new UserDto('123', 'mail1@domain.com', 'User1', ['Permission1'], true)); + expect(user!).toEqual(createUser(12)); })); it('should make post request to create user', @@ -156,9 +126,9 @@ describe('UsersService', () => { expect(req.request.method).toEqual('POST'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({ id: '123', pictureUrl: 'path/to/image1' }); + req.flush(userResponse(12)); - expect(user!).toEqual(new UserDto('123', dto.email, dto.displayName, dto.permissions, false)); + expect(user!).toEqual(createUser(12)); })); it('should make put request to update user', @@ -166,39 +136,108 @@ describe('UsersService', () => { const dto = { email: 'mail@squidex.io', displayName: 'Squidex User', permissions: ['Permission1'], password: 'password' }; - userManagementService.putUser('123', dto).subscribe(); + const resource: Resource = { + _links: { + update: { method: 'PUT', href: 'api/user-management/123' } + } + }; + + let user: UserDto; + + userManagementService.putUser(resource, dto).subscribe(result => { + user = result; + }); const req = httpMock.expectOne('http://service/p/api/user-management/123'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({}); + req.flush(userResponse(12)); + + expect(user!).toEqual(createUser(12)); })); it('should make put request to lock user', inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { - userManagementService.lockUser('123').subscribe(); + const resource: Resource = { + _links: { + lock: { method: 'PUT', href: 'api/user-management/123/lock' } + } + }; + + let user: UserDto; + + userManagementService.lockUser(resource).subscribe(result => { + user = result; + }); const req = httpMock.expectOne('http://service/p/api/user-management/123/lock'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({}); + req.flush(userResponse(12)); + + expect(user!).toEqual(createUser(12)); })); it('should make put request to unlock user', inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { - userManagementService.unlockUser('123').subscribe(); + const resource: Resource = { + _links: { + unlock: { method: 'PUT', href: 'api/user-management/123/unlock' } + } + }; + + let user: UserDto; + + userManagementService.unlockUser(resource).subscribe(result => { + user = result; + }); const req = httpMock.expectOne('http://service/p/api/user-management/123/unlock'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({}); + req.flush(userResponse(12)); + + expect(user!).toEqual(createUser(12)); })); -}); \ No newline at end of file + + function userResponse(id: number) { + return { + id: `${id}`, + email: `user${id}@domain.com`, + displayName: `user${id}`, + permissions: [ + `Permission${id}` + ], + isLocked: true, + _links: { + update: { + method: 'PUT', href: `/users/${id}` + } + } + }; + } +}); + +export function createUser(id: number, suffix = '') { + const result = new UserDto(`${id}`, + `user${id}${suffix}@domain.com`, + `user${id}${suffix}`, + [ + `Permission${id}${suffix}` + ], + true); + + result._links['update'] = { + method: 'PUT', href: `/users/${id}` + }; + + return result; +} \ No newline at end of file diff --git a/src/Squidex/app/features/administration/services/users.service.ts b/src/Squidex/app/features/administration/services/users.service.ts index b42fe46a2..a1eb3e9c8 100644 --- a/src/Squidex/app/features/administration/services/users.service.ts +++ b/src/Squidex/app/features/administration/services/users.service.ts @@ -12,7 +12,6 @@ import { map } from 'rxjs/operators'; import { ApiUrlConfig, - Model, pretifyError, Resource, ResourceLinks, @@ -21,11 +20,11 @@ import { } from '@app/shared'; export class UsersDto extends ResultSet { - public _links: ResourceLinks; + public readonly _links: ResourceLinks = {}; } -export class UserDto extends Model { - public _links: ResourceLinks; +export class UserDto { + public readonly _links: ResourceLinks = {}; constructor( public readonly id: string, @@ -34,11 +33,6 @@ export class UserDto extends Model { public readonly permissions: string[] = [], public readonly isLocked?: boolean ) { - super(); - } - - public with(value: Partial): UserDto { - return this.clone(value); } } @@ -69,15 +63,7 @@ export class UsersService { return this.http.get<{ total: number, items: any[] } & Resource>(url).pipe( map(body => { - const users = body.items.map(item => - withLinks( - new UserDto( - item.id, - item.email, - item.displayName, - item.permissions, - item.isLocked), - item)); + const users = body.items.map(item => parseUser(item)); return withLinks(new UsersDto(body.total, users), body); }), @@ -89,14 +75,7 @@ export class UsersService { return this.http.get(url).pipe( map(body => { - const user = new UserDto( - body.id, - body.email, - body.displayName, - body.permissions, - body.isLocked); - - return user; + return parseUser(body); }), pretifyError('Failed to load user. Please reload.')); } @@ -106,36 +85,55 @@ export class UsersService { return this.http.post(url, dto).pipe( map(body => { - const user = new UserDto( - body.id, - dto.email, - dto.displayName, - dto.permissions, - false); - - return user; + return parseUser(body); }), pretifyError('Failed to create user. Please reload.')); } - public putUser(id: string, dto: UpdateUserDto): Observable { - const url = this.apiUrl.buildUrl(`api/user-management/${id}`); + public putUser(user: Resource, dto: UpdateUserDto): Observable { + const link = user._links['update']; + + const url = this.apiUrl.buildUrl(link.href); - return this.http.put(url, dto).pipe( + return this.http.request(link.method, url, { body: dto }).pipe( + map(body => { + return parseUser(body); + }), pretifyError('Failed to update user. Please reload.')); } - public lockUser(id: string): Observable { - const url = this.apiUrl.buildUrl(`api/user-management/${id}/lock`); + public lockUser(user: Resource): Observable { + const link = user._links['lock']; + + const url = this.apiUrl.buildUrl(link.href); - return this.http.put(url, {}).pipe( + return this.http.request(link.method, url).pipe( + map(body => { + return parseUser(body); + }), pretifyError('Failed to load users. Please retry.')); } - public unlockUser(id: string): Observable { - const url = this.apiUrl.buildUrl(`api/user-management/${id}/unlock`); + public unlockUser(user: Resource): Observable { + const link = user._links['unlock']; - return this.http.put(url, {}).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return this.http.request(link.method, url).pipe( + map(body => { + return parseUser(body); + }), pretifyError('Failed to load users. Please retry.')); } +} + +function parseUser(response: any) { + return withLinks( + new UserDto( + response.id, + response.email, + response.displayName, + response.permissions, + response.isLocked), + response); } \ No newline at end of file diff --git a/src/Squidex/app/features/administration/state/users.state.spec.ts b/src/Squidex/app/features/administration/state/users.state.spec.ts index e735870a5..b9e4971db 100644 --- a/src/Squidex/app/features/administration/state/users.state.spec.ts +++ b/src/Squidex/app/features/administration/state/users.state.spec.ts @@ -8,7 +8,7 @@ import { of, throwError } from 'rxjs'; import { IMock, It, Mock, Times } from 'typemoq'; -import { AuthService, DialogService } from '@app/shared'; +import { DialogService } from '@app/shared'; import { UserDto, @@ -16,15 +16,16 @@ import { UsersService } from '@app/features/administration/internal'; + import { UsersState } from './users.state'; +import { createUser } from './../services/users.service.spec'; + describe('UsersState', () => { - const oldUsers = [ - new UserDto('id1', 'mail1@mail.de', 'name1', ['Permission1'], false), - new UserDto('id2', 'mail2@mail.de', 'name2', ['Permission2'], true) - ]; + const user1 = createUser(1); + const user2 = createUser(2); - const newUser = new UserDto('id3', 'mail3@mail.de', 'name3', ['Permission3'], false); + const newUser = createUser(3); let dialogs: IMock; let usersService: IMock; @@ -44,11 +45,11 @@ describe('UsersState', () => { describe('Loading', () => { it('should load users', () => { usersService.setup(x => x.getUsers(10, 0, undefined)) - .returns(() => of(new UsersDto(200, oldUsers))).verifiable(); + .returns(() => of(new UsersDto(200, [user1, user2]))).verifiable(); usersState.load().subscribe(); - expect(usersState.snapshot.users.values).toEqual(oldUsers); + expect(usersState.snapshot.users.values).toEqual([user1, user2]); expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200); expect(usersState.snapshot.isLoaded).toBeTruthy(); @@ -57,7 +58,7 @@ describe('UsersState', () => { it('should show notification on load when reload is true', () => { usersService.setup(x => x.getUsers(10, 0, undefined)) - .returns(() => of(new UsersDto(200, oldUsers))).verifiable(); + .returns(() => of(new UsersDto(200, [user1, user2]))).verifiable(); usersState.load(true).subscribe(); @@ -68,18 +69,18 @@ describe('UsersState', () => { it('should replace selected user when reloading', () => { const newUsers = [ - new UserDto('id1', 'mail1@mail.de_new', 'name1_new', ['Permission1_New'], false), - new UserDto('id2', 'mail2@mail.de_new', 'name2_new', ['Permission2_New'], true) + createUser(1, '_new'), + createUser(2, '_new') ]; usersService.setup(x => x.getUsers(10, 0, undefined)) - .returns(() => of(new UsersDto(200, oldUsers))).verifiable(Times.exactly(2)); + .returns(() => of(new UsersDto(200, [user1, user2]))).verifiable(Times.exactly(2)); usersService.setup(x => x.getUsers(10, 0, undefined)) .returns(() => of(new UsersDto(200, newUsers))); usersState.load().subscribe(); - usersState.select('id1').subscribe(); + usersState.select(user1.id).subscribe(); usersState.load().subscribe(); expect(usersState.snapshot.selectedUser).toEqual(newUsers[0]); @@ -87,7 +88,7 @@ describe('UsersState', () => { it('should load next page and prev page when paging', () => { usersService.setup(x => x.getUsers(10, 0, undefined)) - .returns(() => of(new UsersDto(200, oldUsers))).verifiable(Times.exactly(2)); + .returns(() => of(new UsersDto(200, [user1, user2]))).verifiable(Times.exactly(2)); usersService.setup(x => x.getUsers(10, 10, undefined)) .returns(() => of(new UsersDto(200, []))).verifiable(); @@ -112,7 +113,7 @@ describe('UsersState', () => { describe('Updates', () => { beforeEach(() => { usersService.setup(x => x.getUsers(10, 0, undefined)) - .returns(() => of(new UsersDto(200, oldUsers))).verifiable(); + .returns(() => of(new UsersDto(200, [user1, user2]))).verifiable(); usersState.load().subscribe(); }); @@ -120,12 +121,12 @@ describe('UsersState', () => { it('should return user on select and not load when already loaded', () => { let selectedUser: UserDto; - usersState.select('id1').subscribe(x => { + usersState.select(user1.id).subscribe(x => { selectedUser = x!; }); - expect(selectedUser!).toEqual(oldUsers[0]); - expect(usersState.snapshot.selectedUser).toEqual(oldUsers[0]); + expect(selectedUser!).toEqual(user1); + expect(usersState.snapshot.selectedUser).toEqual(user1); }); it('should return user on select and load when not loaded', () => { @@ -168,46 +169,49 @@ describe('UsersState', () => { }); it('should mark as locked when locked', () => { - usersService.setup(x => x.lockUser('id1')) - .returns(() => of({})).verifiable(); + const updated = createUser(2, '_new'); + + usersService.setup(x => x.lockUser(user2)) + .returns(() => of(updated)).verifiable(); - usersState.select('id1').subscribe(); - usersState.lock(oldUsers[0]).subscribe(); + usersState.select(user2.id).subscribe(); + usersState.lock(user2).subscribe(); - const user_1 = usersState.snapshot.users.at(0); + const userUser2 = usersState.snapshot.users.at(1); - expect(user_1.isLocked).toBeTruthy(); - expect(user_1).toBe(usersState.snapshot.selectedUser!); + expect(userUser2).toBe(usersState.snapshot.selectedUser!); }); it('should unmark as locked when unlocked', () => { - usersService.setup(x => x.unlockUser('id2')) - .returns(() => of({})).verifiable(); + const updated = createUser(2, '_new'); - usersState.select('id2').subscribe(); - usersState.unlock(oldUsers[1]).subscribe(); + usersService.setup(x => x.unlockUser(user2)) + .returns(() => of(updated)).verifiable(); - const user_1 = usersState.snapshot.users.at(1); + usersState.select(user2.id).subscribe(); + usersState.unlock(user2).subscribe(); - expect(user_1.isLocked).toBeFalsy(); - expect(user_1).toBe(usersState.snapshot.selectedUser!); + const newUser2 = usersState.snapshot.users.at(1); + + expect(newUser2).toEqual(updated); + expect(newUser2).toBe(usersState.snapshot.selectedUser!); }); it('should update user properties when updated', () => { const request = { email: 'new@mail.com', displayName: 'New', permissions: ['Permission1'] }; - usersService.setup(x => x.putUser('id1', request)) - .returns(() => of({})).verifiable(); + const updated = createUser(2, '_new'); + + usersService.setup(x => x.putUser(user2, request)) + .returns(() => of(updated)).verifiable(); - usersState.select('id1').subscribe(); - usersState.update(oldUsers[0], request).subscribe(); + usersState.select(user2.id).subscribe(); + usersState.update(user2, request).subscribe(); - const user_1 = usersState.snapshot.users.at(0); + const newUser2 = usersState.snapshot.users.at(1); - expect(user_1.email).toEqual(request.email); - expect(user_1.displayName).toEqual(request.displayName); - expect(user_1.permissions).toEqual(request.permissions); - expect(user_1).toBe(usersState.snapshot.selectedUser!); + expect(newUser2).toEqual(updated); + expect(newUser2).toBe(usersState.snapshot.selectedUser!); }); it('should add user to snapshot when created', () => { @@ -218,7 +222,7 @@ describe('UsersState', () => { usersState.create(request).subscribe(); - expect(usersState.snapshot.users.values).toEqual([newUser, ...oldUsers]); + expect(usersState.snapshot.users.values).toEqual([newUser, user1, user2]); expect(usersState.snapshot.usersPager.numberOfItems).toBe(201); }); }); diff --git a/src/Squidex/app/features/administration/state/users.state.ts b/src/Squidex/app/features/administration/state/users.state.ts index 49c5163cc..04a4bc80a 100644 --- a/src/Squidex/app/features/administration/state/users.state.ts +++ b/src/Squidex/app/features/administration/state/users.state.ts @@ -7,7 +7,7 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; -import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; +import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators'; import '@app/framework/utils/rxjs-extensions'; @@ -15,6 +15,7 @@ import { DialogService, ImmutableArray, Pager, + ResourceLinks, shareSubscribed, State } from '@app/shared'; @@ -36,6 +37,9 @@ interface Snapshot { // The query to filter users. usersQuery?: string; + // The resource links. + links: ResourceLinks; + // Indicates if the users are loaded. isLoaded?: boolean; @@ -56,6 +60,10 @@ export class UsersState extends State { this.changes.pipe(map(x => x.usersPager), distinctUntilChanged()); + public links = + this.changes.pipe(map(x => x.links), + distinctUntilChanged()); + public selectedUser = this.changes.pipe(map(x => x.selectedUser), distinctUntilChanged()); @@ -68,7 +76,7 @@ export class UsersState extends State { private readonly dialogs: DialogService, private readonly usersService: UsersService ) { - super({ users: ImmutableArray.empty(), usersPager: new Pager(0) }); + super({ users: ImmutableArray.empty(), usersPager: new Pager(0), links: {} }); } public select(id: string | null): Observable { @@ -108,7 +116,7 @@ export class UsersState extends State { this.snapshot.usersPager.pageSize, this.snapshot.usersPager.skip, this.snapshot.usersQuery).pipe( - tap(({ total, items }) => { + tap(({ total, items, _links: links }) => { if (isReload) { this.dialogs.notifyInfo('Users reloaded.'); } @@ -123,7 +131,7 @@ export class UsersState extends State { selectedUser = users.find(x => x.id === selectedUser!.id) || selectedUser; } - return { ...s, users, usersPager, selectedUser, isLoaded: true }; + return { ...s, users, usersPager, links, selectedUser, isLoaded: true }; }); }), shareSubscribed(this.dialogs)); @@ -143,8 +151,7 @@ export class UsersState extends State { } public update(user: UserDto, request: UpdateUserDto): Observable { - return this.usersService.putUser(user.id, request).pipe( - switchMap(() => this.usersService.getUser(user.id)), + return this.usersService.putUser(user, request).pipe( tap(updated => { this.replaceUser(updated); }), @@ -152,8 +159,7 @@ export class UsersState extends State { } public lock(user: UserDto): Observable { - return this.usersService.lockUser(user.id).pipe( - switchMap(() => this.usersService.getUser(user.id)), + return this.usersService.lockUser(user).pipe( tap(updated => { this.replaceUser(updated); }), @@ -161,8 +167,7 @@ export class UsersState extends State { } public unlock(user: UserDto): Observable { - return this.usersService.unlockUser(user.id).pipe( - switchMap(() => this.usersService.getUser(user.id)), + return this.usersService.unlockUser(user).pipe( tap(updated => { this.replaceUser(updated); }), diff --git a/src/Squidex/app/framework/angular/http/hateos.pipes.ts b/src/Squidex/app/framework/angular/http/hateos.pipes.ts index 979c95205..f580b5bb0 100644 --- a/src/Squidex/app/framework/angular/http/hateos.pipes.ts +++ b/src/Squidex/app/framework/angular/http/hateos.pipes.ts @@ -1,14 +1,25 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + import { Pipe, PipeTransform } from '@angular/core'; -import { Resource } from '@app/framework/internal'; +import { + hasLink, + Resource, + ResourceLinks +} from '@app/framework/internal'; @Pipe({ name: 'sqxHasLink', pure: true }) export class HasLinkPipe implements PipeTransform { - public transform(value: Resource, rel: string) { - return value._links && !!value._links[rel]; + public transform(value: Resource | ResourceLinks, rel: string) { + return hasLink(value, rel); } } @@ -17,7 +28,7 @@ export class HasLinkPipe implements PipeTransform { pure: true }) export class HasNoLinkPipe implements PipeTransform { - public transform(value: Resource, rel: string) { - return !value._links || !value._links[rel]; + public transform(value: Resource | ResourceLinks, rel: string) { + return !hasLink(value, rel); } -} \ No newline at end of file +} diff --git a/src/Squidex/app/framework/utils/hateos.ts b/src/Squidex/app/framework/utils/hateos.ts index 14e5357d8..c1c2099c7 100644 --- a/src/Squidex/app/framework/utils/hateos.ts +++ b/src/Squidex/app/framework/utils/hateos.ts @@ -5,21 +5,40 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ - export interface Resource { - _links?: { [rel: string]: ResourceLink }; - } +export interface Resource { + readonly _links: { [rel: string]: ResourceLink }; +} - export type ResourceLinks = { [rel: string]: ResourceLink }; - export type ResourceLink = { href: string; method: ResourceMethod; }; +export type ResourceLinks = { [rel: string]: ResourceLink }; +export type ResourceLink = { href: string; method: ResourceMethod; }; - export function withLinks(value: T, source: Resource) { - value._links = source._links; +export function withLinks(value: T, source: Resource) { + if (value._links && source._links) { + for (let key in source._links) { + if (source._links.hasOwnProperty(key)) { + value._links[key] = source._links[key]; + } + } - return value; - } + Object.freeze(value._links); + } - export type ResourceMethod = - 'get' | - 'post' | - 'put' | - 'delete'; \ No newline at end of file + return value; +} + +export function hasLink(value: Resource | ResourceLinks, rel: string): boolean { + const link = getLink(value, rel); + + return !!(link && link.method && link.href); +} + +export function getLink(value: Resource | ResourceLinks, rel: string): ResourceLink { + return value ? (value._links ? value._links[rel] : value[rel]) : undefined; +} + +export type ResourceMethod = + 'GET' | + 'DELETE' | + 'PATCH' | + 'POST' | + 'PUT'; \ No newline at end of file From 368cfc7b828fe4464c3992abb76b7241fc70b1b7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 8 Jun 2019 14:20:34 +0200 Subject: [PATCH 005/175] HATEOS for event consumers. --- .../Grains/EventConsumerGrain.cs | 29 ++-- .../Grains/EventConsumerManagerGrain.cs | 12 +- .../Grains/IEventConsumerGrain.cs | 6 +- .../Grains/IEventConsumerManagerGrain.cs | 10 +- src/Squidex.Web/Resource.cs | 1 - src/Squidex.Web/UrlHelperExtensions.cs | 2 - .../Contents/ContentsController.cs | 1 - .../EventConsumersController.cs | 31 +++-- .../EventConsumers/Models/EventConsumerDto.cs | 37 +++++- .../Models/EventConsumersDto.cs | 39 ++++++ .../event-consumers-page.component.html | 6 +- .../services/event-consumers.service.spec.ts | 124 +++++++++++++----- .../services/event-consumers.service.ts | 82 ++++++++---- .../services/users.service.spec.ts | 3 +- .../state/event-consumers.state.spec.ts | 60 +++++---- .../state/event-consumers.state.ts | 21 +-- .../administration/state/users.state.spec.ts | 10 +- 17 files changed, 323 insertions(+), 151 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs index f37a9200f..bc5ada68d 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs @@ -58,7 +58,12 @@ namespace Squidex.Infrastructure.EventSourcing.Grains public Task> GetStateAsync() { - return Task.FromResult(State.ToInfo(eventConsumer.Name).AsImmutable()); + return Task.FromResult(CreateInfo()); + } + + private Immutable CreateInfo() + { + return State.ToInfo(eventConsumer.Name).AsImmutable(); } public Task OnEventAsync(Immutable subscription, Immutable storedEvent) @@ -109,39 +114,43 @@ namespace Squidex.Infrastructure.EventSourcing.Grains return TaskHelper.Done; } - public Task StartAsync() + public async Task> StartAsync() { if (!State.IsStopped) { - return TaskHelper.Done; + return CreateInfo(); } - return DoAndUpdateStateAsync(() => + await DoAndUpdateStateAsync(() => { Subscribe(State.Position); State = State.Started(); }); + + return CreateInfo(); } - public Task StopAsync() + public async Task> StopAsync() { if (State.IsStopped) { - return TaskHelper.Done; + return CreateInfo(); } - return DoAndUpdateStateAsync(() => + await DoAndUpdateStateAsync(() => { Unsubscribe(); State = State.Stopped(); }); + + return CreateInfo(); } - public Task ResetAsync() + public async Task> ResetAsync() { - return DoAndUpdateStateAsync(async () => + await DoAndUpdateStateAsync(async () => { Unsubscribe(); @@ -151,6 +160,8 @@ namespace Squidex.Infrastructure.EventSourcing.Grains State = State.Reset(); }); + + return CreateInfo(); } private Task DoAndUpdateStateAsync(Action action, [CallerMemberName] string caller = null) diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs index ca9097142..4952088c0 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs @@ -74,33 +74,31 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { return Task.WhenAll( eventConsumers - .Select(c => GrainFactory.GetGrain(c.Name)) - .Select(c => c.StartAsync())); + .Select(c => StartAsync(c.Name))); } public Task StopAllAsync() { return Task.WhenAll( eventConsumers - .Select(c => GrainFactory.GetGrain(c.Name)) - .Select(c => c.StopAsync())); + .Select(c => StopAsync(c.Name))); } - public Task ResetAsync(string consumerName) + public Task> ResetAsync(string consumerName) { var eventConsumer = GrainFactory.GetGrain(consumerName); return eventConsumer.ResetAsync(); } - public Task StartAsync(string consumerName) + public Task> StartAsync(string consumerName) { var eventConsumer = GrainFactory.GetGrain(consumerName); return eventConsumer.StartAsync(); } - public Task StopAsync(string consumerName) + public Task> StopAsync(string consumerName) { var eventConsumer = GrainFactory.GetGrain(consumerName); diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs index 58b7bf2fb..fb7d82811 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs @@ -16,11 +16,11 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { Task> GetStateAsync(); - Task StopAsync(); + Task> StopAsync(); - Task StartAsync(); + Task> StartAsync(); - Task ResetAsync(); + Task> ResetAsync(); Task OnEventAsync(Immutable subscription, Immutable storedEvent); diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs index c0b53d403..397db21f4 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs @@ -16,15 +16,15 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { Task ActivateAsync(string streamName); - Task StopAllAsync(); + Task StartAllAsync(); - Task StopAsync(string consumerName); + Task StopAllAsync(); - Task StartAllAsync(); + Task> StopAsync(string consumerName); - Task StartAsync(string consumerName); + Task> StartAsync(string consumerName); - Task ResetAsync(string consumerName); + Task> ResetAsync(string consumerName); Task>> GetConsumersAsync(); } diff --git a/src/Squidex.Web/Resource.cs b/src/Squidex.Web/Resource.cs index a0e68197c..59c4d10f9 100644 --- a/src/Squidex.Web/Resource.cs +++ b/src/Squidex.Web/Resource.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Net.Http; namespace Squidex.Web { diff --git a/src/Squidex.Web/UrlHelperExtensions.cs b/src/Squidex.Web/UrlHelperExtensions.cs index 486d48a76..27f00a1d9 100644 --- a/src/Squidex.Web/UrlHelperExtensions.cs +++ b/src/Squidex.Web/UrlHelperExtensions.cs @@ -7,8 +7,6 @@ using Microsoft.AspNetCore.Mvc; using System; -using System.Linq.Expressions; -using System.Reflection; namespace Squidex.Web { diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index bd374a986..61c74d203 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -21,7 +21,6 @@ using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Infrastructure.Commands; using Squidex.Shared; -using Squidex.Shared.Identity; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Contents diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs index 1369eb9c7..9e17d5f09 100644 --- a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs +++ b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Orleans; @@ -30,44 +29,54 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers [HttpGet] [Route("event-consumers/")] + [ProducesResponseType(typeof(EventConsumersDto), 200)] [ApiPermission(Permissions.AdminEventsRead)] public async Task GetEventConsumers() { var entities = await GetGrain().GetConsumersAsync(); - var response = entities.Value.OrderBy(x => x.Name).Select(EventConsumerDto.FromEventConsumerInfo).ToArray(); + var response = EventConsumersDto.FromResults(entities.Value, this); return Ok(response); } [HttpPut] [Route("event-consumers/{name}/start/")] + [ProducesResponseType(typeof(EventConsumerDto), 200)] [ApiPermission(Permissions.AdminEventsManage)] - public async Task Start(string name) + public async Task StartEventConsumer(string name) { - await GetGrain().StartAsync(name); + var entity = await GetGrain().StartAsync(name); - return NoContent(); + var response = EventConsumerDto.FromEventConsumerInfo(entity.Value, this); + + return Ok(response); } [HttpPut] [Route("event-consumers/{name}/stop/")] + [ProducesResponseType(typeof(EventConsumerDto), 200)] [ApiPermission(Permissions.AdminEventsManage)] - public async Task Stop(string name) + public async Task StopEventConsumer(string name) { - await GetGrain().StopAsync(name); + var entity = await GetGrain().StopAsync(name); + + var response = EventConsumerDto.FromEventConsumerInfo(entity.Value, this); - return NoContent(); + return Ok(response); } [HttpPut] [Route("event-consumers/{name}/reset/")] + [ProducesResponseType(typeof(EventConsumerDto), 200)] [ApiPermission(Permissions.AdminEventsManage)] - public async Task Reset(string name) + public async Task ResetEventConsumer(string name) { - await GetGrain().ResetAsync(name); + var entity = await GetGrain().ResetAsync(name); + + var response = EventConsumerDto.FromEventConsumerInfo(entity.Value, this); - return NoContent(); + return Ok(response); } private IEventConsumerManagerGrain GetGrain() diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs index 3ef6535c3..df0846cbf 100644 --- a/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs +++ b/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs @@ -7,11 +7,16 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Security; +using Squidex.Shared; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.EventConsumers.Models { - public sealed class EventConsumerDto + public sealed class EventConsumerDto : Resource { + private static readonly Permission EventsManagePermission = new Permission(Permissions.AdminEventsManage); + public bool IsStopped { get; set; } public bool IsResetting { get; set; } @@ -22,9 +27,35 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers.Models public string Position { get; set; } - public static EventConsumerDto FromEventConsumerInfo(EventConsumerInfo eventConsumerInfo) + public static EventConsumerDto FromEventConsumerInfo(EventConsumerInfo eventConsumerInfo, ApiController controller) + { + var result = SimpleMapper.Map(eventConsumerInfo, new EventConsumerDto()); + + return CreateLinks(result, controller); + } + + private static EventConsumerDto CreateLinks(EventConsumerDto result, ApiController controller) { - return SimpleMapper.Map(eventConsumerInfo, new EventConsumerDto()); + if (controller.HasPermission(EventsManagePermission)) + { + var values = new { name = result.Name }; + + if (!result.IsResetting) + { + result.AddPutLink("reset", controller.Url(x => nameof(x.ResetEventConsumer), values)); + } + + if (result.IsStopped) + { + result.AddPutLink("start", controller.Url(x => nameof(x.StartEventConsumer), values)); + } + else + { + result.AddPutLink("stop", controller.Url(x => nameof(x.StopEventConsumer), values)); + } + } + + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs new file mode 100644 index 000000000..8f9a20766 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.EventConsumers.Models +{ + public sealed class EventConsumersDto : Resource + { + /// + /// The event consumers. + /// + public EventConsumerDto[] Items { get; set; } + + public static EventConsumersDto FromResults(IEnumerable items, ApiController controller) + { + var result = new EventConsumersDto + { + Items = items.Select(x => EventConsumerDto.FromEventConsumerInfo(x, controller)).ToArray() + }; + + return CreateLinks(result, controller); + } + + private static EventConsumersDto CreateLinks(EventConsumersDto result, ApiController controller) + { + result.AddSelfLink(controller.Url(c => nameof(c.GetEventConsumers))); + + return result; + } + } +} diff --git a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html index 6664745ac..8c31c3da2 100644 --- a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html +++ b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html @@ -42,13 +42,13 @@ {{eventConsumer.position}}
diff --git a/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts b/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts index 5eacb6d62..33bb41306 100644 --- a/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts +++ b/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts @@ -8,9 +8,13 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; -import { ApiUrlConfig } from '@app/framework'; +import { ApiUrlConfig, Resource } from '@app/framework'; -import { EventConsumerDto, EventConsumersService } from './event-consumers.service'; +import { + EventConsumerDto, + EventConsumersDto, + EventConsumersService +} from './event-consumers.service'; describe('EventConsumersService', () => { beforeEach(() => { @@ -32,7 +36,7 @@ describe('EventConsumersService', () => { it('should make get request to get event consumers', inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { - let eventConsumers: EventConsumerDto[]; + let eventConsumers: EventConsumersDto; eventConsumersService.getEventConsumers().subscribe(result => { eventConsumers = result; @@ -43,66 +47,120 @@ describe('EventConsumersService', () => { expect(req.request.method).toEqual('GET'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush([ - { - name: 'event-consumer1', - position: '13', - isStopped: true, - isResetting: true, - error: 'an error 1' - }, - { - name: 'event-consumer2', - position: '29', - isStopped: true, - isResetting: true, - error: 'an error 2' - } - ]); + req.flush({ + items: [ + eventConsumerResponse(12), + eventConsumerResponse(13) + ] + }); expect(eventConsumers!).toEqual( - [ - new EventConsumerDto('event-consumer1', true, true, 'an error 1', '13'), - new EventConsumerDto('event-consumer2', true, true, 'an error 2', '29') - ]); + new EventConsumersDto([ + createEventConsumer(12), + createEventConsumer(13) + ])); })); it('should make put request to start event consumer', inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { - eventConsumersService.putStart('event-consumer1').subscribe(); + const resource: Resource = { + _links: { + start: { method: 'PUT', href: 'api/event-consumers/event-consumer123/start' } + } + }; + + let eventConsumer: EventConsumerDto; - const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/start'); + eventConsumersService.putStart(resource).subscribe(response => { + eventConsumer = response; + }); + + const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer123/start'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({}); + req.flush(eventConsumerResponse(123)); + + expect(eventConsumer!).toEqual(createEventConsumer(123)); })); it('should make put request to stop event consumer', inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { - eventConsumersService.putStop('event-consumer1').subscribe(); + const resource: Resource = { + _links: { + stop: { method: 'PUT', href: 'api/event-consumers/event-consumer123/stop' } + } + }; + + let eventConsumer: EventConsumerDto; - const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/stop'); + eventConsumersService.putStop(resource).subscribe(response => { + eventConsumer = response; + }); + + const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer123/stop'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({}); + req.flush(eventConsumerResponse(12)); + + expect(eventConsumer!).toEqual(createEventConsumer(12)); })); it('should make put request to reset event consumer', inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { - eventConsumersService.putReset('event-consumer1').subscribe(); + const resource: Resource = { + _links: { + reset: { method: 'PUT', href: 'api/event-consumers/event-consumer123/reset' } + } + }; + + let eventConsumer: EventConsumerDto; - const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/reset'); + eventConsumersService.putReset(resource).subscribe(response => { + eventConsumer = response; + }); + + const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer123/reset'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({}); + req.flush(eventConsumerResponse(12)); + + expect(eventConsumer!).toEqual(createEventConsumer(12)); })); -}); \ No newline at end of file + + function eventConsumerResponse(id: number) { + return { + name: `event-consumer${id}`, + position: `position-${id}`, + isStopped: true, + isResetting: true, + error: `failure-${id}`, + _links: { + reset: { method: 'PUT', href: `/event-consumers/${id}/reset` } + } + }; + } +}); + +export function createEventConsumer(id: number, suffix = '') { + const result = new EventConsumerDto( + `event-consumer${id}`, + true, + true, + `failure-${id}${suffix}`, + `position-${id}${suffix}`); + + result._links['reset'] = { + method: 'PUT', href: `/event-consumers/${id}/reset` + }; + + return result; +} \ No newline at end of file diff --git a/src/Squidex/app/features/administration/services/event-consumers.service.ts b/src/Squidex/app/features/administration/services/event-consumers.service.ts index 05b2cd0fd..5c49b1980 100644 --- a/src/Squidex/app/features/administration/services/event-consumers.service.ts +++ b/src/Squidex/app/features/administration/services/event-consumers.service.ts @@ -12,11 +12,24 @@ import { map } from 'rxjs/operators'; import { ApiUrlConfig, - Model, - pretifyError + pretifyError, + Resource, + ResourceLinks, + withLinks } from '@app/shared'; -export class EventConsumerDto extends Model { +export class EventConsumersDto { + public readonly _links: ResourceLinks = {}; + + constructor( + public readonly items: EventConsumerDto[] + ) { + } +} + +export class EventConsumerDto { + public readonly _links: ResourceLinks = {}; + constructor( public readonly name: string, public readonly isStopped?: boolean, @@ -24,7 +37,6 @@ export class EventConsumerDto extends Model { public readonly error?: string, public readonly position?: string ) { - super(); } } @@ -36,42 +48,62 @@ export class EventConsumersService { ) { } - public getEventConsumers(): Observable { + public getEventConsumers(): Observable { const url = this.apiUrl.buildUrl('/api/event-consumers'); - return this.http.get(url).pipe( + return this.http.get<{ items: any[] } & Resource>(url).pipe( map(body => { - const eventConsumers = body.map(item => - new EventConsumerDto( - item.name, - item.isStopped, - item.isResetting, - item.error, - item.position)); - - return eventConsumers; + const eventConsumers = body.items.map(item => parseEventConsumer(item)); + + return withLinks(new EventConsumersDto(eventConsumers), body); }), pretifyError('Failed to load event consumers. Please reload.')); } - public putStart(name: string): Observable { - const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/start`); + public putStart(eventConsumer: Resource): Observable { + const link = eventConsumer._links['start']; - return this.http.put(url, {}).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return this.http.request(link.method, url).pipe( + map(body => { + return parseEventConsumer(body); + }), pretifyError('Failed to start event consumer. Please reload.')); } - public putStop(name: string): Observable { - const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/stop`); + public putStop(eventConsumer: Resource): Observable { + const link = eventConsumer._links['stop']; + + const url = this.apiUrl.buildUrl(link.href); - return this.http.put(url, {}).pipe( + return this.http.request(link.method, url).pipe( + map(body => { + return parseEventConsumer(body); + }), pretifyError('Failed to stop event consumer. Please reload.')); } - public putReset(name: string): Observable { - const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/reset`); + public putReset(eventConsumer: Resource): Observable { + const link = eventConsumer._links['reset']; + + const url = this.apiUrl.buildUrl(link.href); - return this.http.put(url, {}).pipe( + return this.http.request(link.method, url).pipe( + map(body => { + return parseEventConsumer(body); + }), pretifyError('Failed to reset event consumer. Please reload.')); } -} \ No newline at end of file +} + +function parseEventConsumer(response: any): EventConsumerDto { + return withLinks( + new EventConsumerDto( + response.name, + response.isStopped, + response.isResetting, + response.error, + response.position), + response); +} diff --git a/src/Squidex/app/features/administration/services/users.service.spec.ts b/src/Squidex/app/features/administration/services/users.service.spec.ts index 03b9b68fb..825f962f6 100644 --- a/src/Squidex/app/features/administration/services/users.service.spec.ts +++ b/src/Squidex/app/features/administration/services/users.service.spec.ts @@ -227,7 +227,8 @@ describe('UsersService', () => { }); export function createUser(id: number, suffix = '') { - const result = new UserDto(`${id}`, + const result = new UserDto( + `${id}`, `user${id}${suffix}@domain.com`, `user${id}${suffix}`, [ diff --git a/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts b/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts index 4a69ffbeb..ba384f5d1 100644 --- a/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts +++ b/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts @@ -11,14 +11,14 @@ import { IMock, It, Mock, Times } from 'typemoq'; import { DialogService } from '@app/framework'; -import { EventConsumerDto, EventConsumersService } from '@app/features/administration/internal'; +import { EventConsumersDto, EventConsumersService } from '@app/features/administration/internal'; import { EventConsumersState } from './event-consumers.state'; +import { createEventConsumer } from './../services/event-consumers.service.spec'; + describe('EventConsumersState', () => { - const oldConsumers = [ - new EventConsumerDto('name1', false, false, 'error', '1'), - new EventConsumerDto('name2', true, true, 'error', '2') - ]; + const eventConsumer1 = createEventConsumer(1); + const eventConsumer2 = createEventConsumer(2); let dialogs: IMock; let eventConsumersService: IMock; @@ -38,11 +38,11 @@ describe('EventConsumersState', () => { describe('Loading', () => { it('should load event consumers', () => { eventConsumersService.setup(x => x.getEventConsumers()) - .returns(() => of(oldConsumers)).verifiable(); + .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable(); eventConsumersState.load().subscribe(); - expect(eventConsumersState.snapshot.eventConsumers.values).toEqual(oldConsumers); + expect(eventConsumersState.snapshot.eventConsumers.values).toEqual([eventConsumer1, eventConsumer2]); expect(eventConsumersState.snapshot.isLoaded).toBeTruthy(); dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never()); @@ -50,7 +50,7 @@ describe('EventConsumersState', () => { it('should show notification on load when reload is true', () => { eventConsumersService.setup(x => x.getEventConsumers()) - .returns(() => of(oldConsumers)).verifiable(); + .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable(); eventConsumersState.load(true).subscribe(); @@ -74,42 +74,48 @@ describe('EventConsumersState', () => { describe('Updates', () => { beforeEach(() => { eventConsumersService.setup(x => x.getEventConsumers()) - .returns(() => of(oldConsumers)).verifiable(); + .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable(); eventConsumersState.load().subscribe(); }); - it('should unmark as stopped when started', () => { - eventConsumersService.setup(x => x.putStart(oldConsumers[1].name)) - .returns(() => of({})).verifiable(); + it('should update evnet consumer when started', () => { + const updated = createEventConsumer(2, '_new'); + + eventConsumersService.setup(x => x.putStart(eventConsumer2)) + .returns(() => of(updated)).verifiable(); - eventConsumersState.start(oldConsumers[1]).subscribe(); + eventConsumersState.start(eventConsumer2).subscribe(); - const es_1 = eventConsumersState.snapshot.eventConsumers.at(1); + const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1); - expect(es_1.isStopped).toBeFalsy(); + expect(newConsumer2).toEqual(updated); }); - it('should mark as stopped when stopped', () => { - eventConsumersService.setup(x => x.putStop(oldConsumers[0].name)) - .returns(() => of({})).verifiable(); + it('should update event consumer when stopped', () => { + const updated = createEventConsumer(2, '_new'); - eventConsumersState.stop(oldConsumers[0]).subscribe(); + eventConsumersService.setup(x => x.putStop(eventConsumer2)) + .returns(() => of(updated)).verifiable(); - const es_1 = eventConsumersState.snapshot.eventConsumers.at(0); + eventConsumersState.stop(eventConsumer2).subscribe(); - expect(es_1.isStopped).toBeTruthy(); + const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1); + + expect(newConsumer2).toEqual(updated); }); - it('should mark as resetting when reset', () => { - eventConsumersService.setup(x => x.putReset(oldConsumers[0].name)) - .returns(() => of({})).verifiable(); + it('should update event consumer when reset', () => { + const updated = createEventConsumer(2, '_new'); + + eventConsumersService.setup(x => x.putReset(eventConsumer2)) + .returns(() => of(updated)).verifiable(); - eventConsumersState.reset(oldConsumers[0]).subscribe(); + eventConsumersState.reset(eventConsumer2).subscribe(); - const es_1 = eventConsumersState.snapshot.eventConsumers.at(0); + const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1); - expect(es_1.isResetting).toBeTruthy(); + expect(newConsumer2).toEqual(updated); }); }); }); \ No newline at end of file diff --git a/src/Squidex/app/features/administration/state/event-consumers.state.ts b/src/Squidex/app/features/administration/state/event-consumers.state.ts index f5c5a0d1d..278578beb 100644 --- a/src/Squidex/app/features/administration/state/event-consumers.state.ts +++ b/src/Squidex/app/features/administration/state/event-consumers.state.ts @@ -51,12 +51,12 @@ export class EventConsumersState extends State { } return this.eventConsumersService.getEventConsumers().pipe( - tap(payload => { + tap(({ items }) => { if (isReload && !silent) { this.dialogs.notifyInfo('Event Consumers reloaded.'); } - const eventConsumers = ImmutableArray.of(payload); + const eventConsumers = ImmutableArray.of(items); this.next(s => { return { ...s, eventConsumers, isLoaded: true }; @@ -66,8 +66,7 @@ export class EventConsumersState extends State { } public start(eventConsumer: EventConsumerDto): Observable { - return this.eventConsumersService.putStart(eventConsumer.name).pipe( - map(() => setStopped(eventConsumer, false)), + return this.eventConsumersService.putStart(eventConsumer).pipe( tap(updated => { this.replaceEventConsumer(updated); }), @@ -75,8 +74,7 @@ export class EventConsumersState extends State { } public stop(eventConsumer: EventConsumerDto): Observable { - return this.eventConsumersService.putStop(eventConsumer.name).pipe( - map(() => setStopped(eventConsumer, true)), + return this.eventConsumersService.putStop(eventConsumer).pipe( tap(updated => { this.replaceEventConsumer(updated); }), @@ -84,8 +82,7 @@ export class EventConsumersState extends State { } public reset(eventConsumer: EventConsumerDto): Observable { - return this.eventConsumersService.putReset(eventConsumer.name).pipe( - map(() => reset(eventConsumer)), + return this.eventConsumersService.putReset(eventConsumer).pipe( tap(updated => { this.replaceEventConsumer(updated); }), @@ -99,10 +96,4 @@ export class EventConsumersState extends State { return { ...s, eventConsumers }; }); } -} - -const setStopped = (eventConsumer: EventConsumerDto, isStopped: boolean) => - eventConsumer.with({ isStopped }); - -const reset = (eventConsumer: EventConsumerDto) => - eventConsumer.with({ isResetting: true }); \ No newline at end of file +} \ No newline at end of file diff --git a/src/Squidex/app/features/administration/state/users.state.spec.ts b/src/Squidex/app/features/administration/state/users.state.spec.ts index b9e4971db..3000bf6f6 100644 --- a/src/Squidex/app/features/administration/state/users.state.spec.ts +++ b/src/Squidex/app/features/administration/state/users.state.spec.ts @@ -168,7 +168,7 @@ describe('UsersState', () => { expect(usersState.snapshot.selectedUser).toBeNull(); }); - it('should mark as locked when locked', () => { + it('should update user selected user when locked', () => { const updated = createUser(2, '_new'); usersService.setup(x => x.lockUser(user2)) @@ -177,12 +177,12 @@ describe('UsersState', () => { usersState.select(user2.id).subscribe(); usersState.lock(user2).subscribe(); - const userUser2 = usersState.snapshot.users.at(1); + const newUser2 = usersState.snapshot.users.at(1); - expect(userUser2).toBe(usersState.snapshot.selectedUser!); + expect(newUser2).toBe(usersState.snapshot.selectedUser!); }); - it('should unmark as locked when unlocked', () => { + it('should update user and selected user when unlocked', () => { const updated = createUser(2, '_new'); usersService.setup(x => x.unlockUser(user2)) @@ -197,7 +197,7 @@ describe('UsersState', () => { expect(newUser2).toBe(usersState.snapshot.selectedUser!); }); - it('should update user properties when updated', () => { + it('should update user and selected user when updated', () => { const request = { email: 'new@mail.com', displayName: 'New', permissions: ['Permission1'] }; const updated = createUser(2, '_new'); From 242df48c18e66466980b5a0ba6ba0de03ccb9e37 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 8 Jun 2019 21:10:30 +0200 Subject: [PATCH 006/175] Some progress. --- src/Squidex.Shared/Permissions.cs | 2 - src/Squidex.Web/PermissionExtensions.cs | 18 +-- .../Controllers/Apps/AppPatternsController.cs | 2 +- .../Api/Controllers/Apps/AppsController.cs | 4 +- .../Api/Controllers/Apps/Models/AppDto.cs | 91 ++++++++++- .../Controllers/Plans/AppPlansController.cs | 2 +- .../Schemas/Models/SchemaDetailsDto.cs | 70 +-------- .../Controllers/Schemas/Models/SchemaDto.cs | 20 ++- .../Controllers/Schemas/SchemasController.cs | 4 +- .../Controllers/Users/Models/ResourcesDto.cs | 39 +++++ .../Api/Controllers/Users/UsersController.cs | 17 ++ .../administration-area.component.html | 8 +- .../administration-area.component.ts | 6 + .../pages/dashboard-page.component.html | 2 +- .../pages/dashboard-page.component.ts | 5 +- .../settings/settings-area.component.html | 18 +-- .../framework/angular/http/hateos.pipes.ts | 20 +-- src/Squidex/app/framework/internal.ts | 1 - src/Squidex/app/framework/module.ts | 3 - .../app/framework/utils/permission.spec.ts | 145 ------------------ src/Squidex/app/framework/utils/permission.ts | 116 -------------- .../components/asset-uploader.component.html | 2 +- .../shared/components/permission.directive.ts | 133 ---------------- .../components/schema-category.component.html | 2 +- src/Squidex/app/shared/declarations.ts | 1 - src/Squidex/app/shared/module.ts | 3 - .../app/shared/services/apps.service.spec.ts | 7 +- .../app/shared/services/apps.service.ts | 37 +++-- .../app/shared/services/auth.service.ts | 21 +-- .../app/shared/services/users.service.spec.ts | 28 ++++ .../app/shared/services/users.service.ts | 21 ++- .../app/shared/state/apps.state.spec.ts | 9 +- src/Squidex/app/shared/state/apps.state.ts | 9 +- src/Squidex/app/shared/state/ui.state.spec.ts | 23 ++- src/Squidex/app/shared/state/ui.state.ts | 37 +++-- .../shell/pages/app/left-menu.component.html | 14 +- .../shell/pages/app/left-menu.component.ts | 3 +- .../pages/internal/apps-menu.component.html | 24 ++- .../internal/profile-menu.component.html | 8 +- .../pages/internal/profile-menu.component.ts | 4 +- 40 files changed, 369 insertions(+), 610 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs delete mode 100644 src/Squidex/app/framework/utils/permission.spec.ts delete mode 100644 src/Squidex/app/framework/utils/permission.ts delete mode 100644 src/Squidex/app/shared/components/permission.directive.ts diff --git a/src/Squidex.Shared/Permissions.cs b/src/Squidex.Shared/Permissions.cs index 964925efb..ee1e20cf4 100644 --- a/src/Squidex.Shared/Permissions.cs +++ b/src/Squidex.Shared/Permissions.cs @@ -79,7 +79,6 @@ namespace Squidex.Shared public const string AppRolesDelete = "squidex.apps.{app}.roles.delete"; public const string AppPatterns = "squidex.apps.{app}.patterns"; - public const string AppPatternsRead = "squidex.apps.{app}.patterns.read"; public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create"; public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update"; public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete"; @@ -117,7 +116,6 @@ namespace Squidex.Shared public const string AppContents = "squidex.apps.{app}.contents.{name}"; public const string AppContentsRead = "squidex.apps.{app}.contents.{name}.read"; - public const string AppContentsGraphQL = "squidex.apps.{app}.contents.{name}.graphql"; public const string AppContentsCreate = "squidex.apps.{app}.contents.{name}.create"; public const string AppContentsUpdate = "squidex.apps.{app}.contents.{name}.update"; public const string AppContentsDiscard = "squidex.apps.{app}.contents.{name}.discard"; diff --git a/src/Squidex.Web/PermissionExtensions.cs b/src/Squidex.Web/PermissionExtensions.cs index 6dc7d0610..a6751e9e9 100644 --- a/src/Squidex.Web/PermissionExtensions.cs +++ b/src/Squidex.Web/PermissionExtensions.cs @@ -24,7 +24,7 @@ namespace Squidex.Web } } - public static PermissionSet GetPermissions(this HttpContext httpContext) + public static PermissionSet Permissions(this HttpContext httpContext) { var feature = httpContext.Features.Get(); @@ -38,22 +38,22 @@ namespace Squidex.Web return feature.Permissions; } - public static bool HasPermission(this HttpContext httpContext, Permission permission) + public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet permissions = null) { - return httpContext.GetPermissions().Includes(permission); + return httpContext.Permissions().Includes(permission) || permission?.Includes(permission) == true; } - public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*") + public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*", PermissionSet permissions = null) { - return httpContext.GetPermissions().Includes(Permissions.ForApp(id, app, schema)); + return httpContext.HasPermission(Shared.Permissions.ForApp(id, app, schema), permissions); } - public static bool HasPermission(this ApiController controller, Permission permission) + public static bool HasPermission(this ApiController controller, Permission permission, PermissionSet permissions = null) { - return controller.HttpContext.GetPermissions().Includes(permission); + return controller.HttpContext.HasPermission(permission, permissions); } - public static bool HasPermission(this ApiController controller, string id, string app = "*", string schema = "*") + public static bool HasPermission(this ApiController controller, string id, string app = "*", string schema = "*", PermissionSet permissions = null) { if (app == "*") { @@ -71,7 +71,7 @@ namespace Squidex.Web } } - return controller.HttpContext.GetPermissions().Includes(Permissions.ForApp(id, app, schema)); + return controller.HasPermission(Shared.Permissions.ForApp(id, app, schema), permissions); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index fd4fac1c5..fef02c374 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -43,7 +43,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpGet] [Route("apps/{app}/patterns/")] [ProducesResponseType(typeof(AppPatternDto[]), 200)] - [ApiPermission(Permissions.AppPatternsRead)] + [ApiPermission(Permissions.AppCommon)] [ApiCosts(0)] public IActionResult GetPatterns(string app) { diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 4219f42dd..36f336ced 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -58,11 +58,11 @@ namespace Squidex.Areas.Api.Controllers.Apps public async Task GetApps() { var userOrClientId = HttpContext.User.UserOrClientId(); - var userPermissions = HttpContext.User.Permissions(); + var userPermissions = HttpContext.Permissions(); var entities = await appProvider.GetUserApps(userOrClientId, userPermissions); - var response = entities.ToArray(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider)); + var response = entities.ToArray(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider, this)); Response.Headers[HeaderNames.ETag] = response.ToManyEtag(); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index 7fe96d7bc..dbb2f4ded 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -9,6 +9,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using NodaTime; +using Squidex.Areas.Api.Controllers.Assets; +using Squidex.Areas.Api.Controllers.Backups; +using Squidex.Areas.Api.Controllers.Plans; +using Squidex.Areas.Api.Controllers.Rules; +using Squidex.Areas.Api.Controllers.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; @@ -16,10 +21,11 @@ using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; using Squidex.Shared; using Squidex.Web; +using AllPermissions = Squidex.Shared.Permissions; namespace Squidex.Areas.Api.Controllers.Apps.Models { - public sealed class AppDto : IGenerateETag + public sealed class AppDto : Resource, IGenerateETag { /// /// The name of the app. @@ -63,7 +69,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// public string PlanUpgrade { get; set; } - public static AppDto FromApp(IAppEntity app, string userId, PermissionSet userPermissions, IAppPlansProvider plans) + public static AppDto FromApp(IAppEntity app, string userId, PermissionSet userPermissions, IAppPlansProvider plans, ApiController controller) { var permissions = new List(); @@ -77,13 +83,84 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models permissions.AddRange(userPermissions.ToAppPermissions(app.Name)); } - var response = SimpleMapper.Map(app, new AppDto()); + var result = SimpleMapper.Map(app, new AppDto()); - response.Permissions = permissions.ToArray(x => x.Id); - response.PlanName = plans.GetPlanForApp(app)?.Name; - response.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name; + result.Permissions = permissions.ToArray(x => x.Id); + result.PlanName = plans.GetPlanForApp(app)?.Name; - return response; + if (controller.HasPermission(AllPermissions.AppPlansChange, app.Name)) + { + result.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name; + } + + return CreateLinks(result, controller, new PermissionSet(permissions)); + } + + private static AppDto CreateLinks(AppDto result, ApiController controller, PermissionSet permissions) + { + var values = new { app = result.Name }; + + if (controller.HasPermission(AllPermissions.AppDelete, result.Name, permissions: permissions)) + { + result.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteApp), values)); + } + + if (controller.HasPermission(AllPermissions.AppAssetsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("assets", controller.Url(x => nameof(x.GetAssets), values)); + } + + if (controller.HasPermission(AllPermissions.AppBackupsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("backups", controller.Url(x => nameof(x.GetJobs), values)); + } + + if (controller.HasPermission(AllPermissions.AppClientsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("clients", controller.Url(x => nameof(x.GetClients), values)); + } + + if (controller.HasPermission(AllPermissions.AppContributorsRead, result.Name, permissions: permissions)) + { + result.AddGetLink("contributors", controller.Url(x => nameof(x.GetContributors), values)); + } + + if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions)) + { + result.AddGetLink("languages", controller.Url(x => nameof(x.GetLanguages), values)); + } + + if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions)) + { + result.AddGetLink("patterns", controller.Url(x => nameof(x.GetPatterns), values)); + } + + if (controller.HasPermission(AllPermissions.AppPlansRead, result.Name, permissions: permissions)) + { + result.AddGetLink("plans", controller.Url(x => nameof(x.GetPlans), values)); + } + + if (controller.HasPermission(AllPermissions.AppRolesRead, result.Name, permissions: permissions)) + { + result.AddGetLink("roles", controller.Url(x => nameof(x.GetRoles), values)); + } + + if (controller.HasPermission(AllPermissions.AppRulesRead, result.Name, permissions: permissions)) + { + result.AddGetLink("rules", controller.Url(x => nameof(x.GetRules), values)); + } + + if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions)) + { + result.AddGetLink("schemas", controller.Url(x => nameof(x.GetSchemas), values)); + } + + if (controller.HasPermission(AllPermissions.AppSchemasCreate, result.Name, permissions: permissions)) + { + result.AddPostLink("schemas/create", controller.Url(x => nameof(x.PostSchema), values)); + } + + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index 9490a4f48..5611ac7ac 100644 --- a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -74,7 +74,7 @@ namespace Squidex.Areas.Api.Controllers.Plans [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppPlansChange)] [ApiCosts(0)] - public async Task ChangePlanAsync(string app, [FromBody] ChangePlanDto request) + public async Task PutPlan(string app, [FromBody] ChangePlanDto request) { var context = await CommandBus.PublishAsync(request.ToCommand()); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs index a6ed2b46b..41494e0c1 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs @@ -5,49 +5,20 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using NodaTime; using Squidex.Areas.Api.Controllers.Schemas.Models.Converters; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class SchemaDetailsDto + public sealed class SchemaDetailsDto : SchemaDto { private static readonly Dictionary EmptyPreviewUrls = new Dictionary(); - /// - /// The id of the schema. - /// - public Guid Id { get; set; } - - /// - /// The name of the schema. Unique within the app. - /// - [Required] - [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] - public string Name { get; set; } - - /// - /// The name of the category. - /// - public string Category { get; set; } - - /// - /// Indicates if the schema is a singleton. - /// - public bool IsSingleton { get; set; } - - /// - /// Indicates if the schema is published. - /// - public bool IsPublished { get; set; } - /// /// The scripts. /// @@ -64,40 +35,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [Required] public List Fields { get; set; } - /// - /// The schema properties. - /// - [Required] - public SchemaPropertiesDto Properties { get; set; } = new SchemaPropertiesDto(); - - /// - /// The user that has created the schema. - /// - [Required] - public RefToken CreatedBy { get; set; } - - /// - /// The user that has updated the schema. - /// - [Required] - public RefToken LastModifiedBy { get; set; } - - /// - /// The date and time when the schema has been created. - /// - public Instant Created { get; set; } - - /// - /// The date and time when the schema has been modified last. - /// - public Instant LastModified { get; set; } - - /// - /// The version of the schema. - /// - public long Version { get; set; } - - public static SchemaDetailsDto FromSchema(ISchemaEntity schema) + public static SchemaDetailsDto FromSchemaWithDetails(ISchemaEntity schema, ApiController controller, string app) { var response = new SchemaDetailsDto(); @@ -147,7 +85,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models response.Fields.Add(fieldDto); } - return response; + return CreateLinks(response, controller, app); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index f66d64d23..4bb86680f 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -8,14 +8,16 @@ using System; using System.ComponentModel.DataAnnotations; using NodaTime; +using Squidex.Areas.Api.Controllers.Contents; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class SchemaDto : IGenerateETag + public class SchemaDto : Resource, IGenerateETag { /// /// The id of the schema. @@ -77,7 +79,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// public long Version { get; set; } - public static SchemaDto FromSchema(ISchemaEntity schema) + public static SchemaDto FromSchema(ISchemaEntity schema, ApiController controller, string app) { var response = new SchemaDto { Properties = new SchemaPropertiesDto() }; @@ -85,6 +87,20 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models SimpleMapper.Map(schema.SchemaDef, response); SimpleMapper.Map(schema.SchemaDef.Properties, response.Properties); + return CreateLinks(response, controller, app); + } + + protected static T CreateLinks(T response, ApiController controller, string app) where T : SchemaDto + { + var values = new { app, name = response.Name }; + + response.AddSelfLink(controller.Url(x => nameof(x.GetSchema), values)); + + if (controller.HasPermission(Permissions.AppContentsRead, app, response.Name)) + { + response.AddGetLink("contents", controller.Url(x => nameof(x.GetContents), values)); + } + return response; } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 1fb64ec83..7320e4484 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -51,7 +51,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var schemas = await appProvider.GetSchemasAsync(AppId); - var response = schemas.ToArray(SchemaDto.FromSchema); + var response = schemas.ToArray(x => SchemaDto.FromSchema(x, this, app)); Response.Headers[HeaderNames.ETag] = response.ToManyEtag(); @@ -90,7 +90,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas return NotFound(); } - var response = SchemaDetailsDto.FromSchema(entity); + var response = SchemaDetailsDto.FromSchemaWithDetails(entity, this, app); Response.Headers[HeaderNames.ETag] = entity.Version.ToString(); diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs new file mode 100644 index 000000000..13570a1bf --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Areas.Api.Controllers.Backups; +using Squidex.Areas.Api.Controllers.EventConsumers; +using Squidex.Shared; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Users.Models +{ + public sealed class ResourcesDto : Resource + { + public static ResourcesDto FromController(ApiController controller) + { + var result = new ResourcesDto(); + + if (controller.HasPermission(Permissions.AdminEventsRead)) + { + result.AddGetLink("admin/eventConsumers", controller.Url(x => nameof(x.GetEventConsumers))); + } + + if (controller.HasPermission(Permissions.AdminRestoreRead)) + { + result.AddGetLink("admin/restore", controller.Url(x => nameof(x.GetJob))); + } + + if (controller.HasPermission(Permissions.AdminUsersRead)) + { + result.AddGetLink("admin/users", controller.Url(x => nameof(x.GetUsers))); + } + + return result; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs index a5cf31c74..0ab2944c5 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs @@ -56,6 +56,23 @@ namespace Squidex.Areas.Api.Controllers.Users this.log = log; } + /// + /// Get the user resources. + /// + /// + /// 200 => User resources returned. + /// + [HttpGet] + [Route("user/resources/")] + [ProducesResponseType(typeof(ResourcesDto), 200)] + [ApiPermission] + public IActionResult GetUserResources() + { + var response = ResourcesDto.FromController(this); + + return Ok(response); + } + /// /// Get users by query. /// diff --git a/src/Squidex/app/features/administration/administration-area.component.html b/src/Squidex/app/features/administration/administration-area.component.html index c3b623cdb..3f2e2c3d0 100644 --- a/src/Squidex/app/features/administration/administration-area.component.html +++ b/src/Squidex/app/features/administration/administration-area.component.html @@ -1,18 +1,18 @@ - @@ -37,12 +38,12 @@
-
- +
diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts index a3e1f140b..0e18f523f 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts @@ -29,6 +29,9 @@ export class ContentChangedTriggerComponent implements OnInit { @Input() public schemas: ImmutableArray; + @Input() + public canUpdate: boolean; + @Input() public trigger: any; diff --git a/src/Squidex/app/framework/angular/forms/toggle.component.ts b/src/Squidex/app/framework/angular/forms/toggle.component.ts index 7105ada32..33e53d9bf 100644 --- a/src/Squidex/app/framework/angular/forms/toggle.component.ts +++ b/src/Squidex/app/framework/angular/forms/toggle.component.ts @@ -28,6 +28,10 @@ export class ToggleComponent extends StatefulControlComponent { const version = new Version('1'); @@ -107,7 +107,7 @@ describe('RulesService', () => { it('should make get request to get app rules', inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - let rules: RuleDto[]; + let rules: RulesDto; rulesService.getRules('my-app').subscribe(result => { rules = result; @@ -118,49 +118,18 @@ describe('RulesService', () => { expect(req.request.method).toEqual('GET'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush([ - { - id: 'id1', - created: '2016-12-12T10:10', - createdBy: 'CreatedBy1', - lastModified: '2017-12-12T10:10', - lastModifiedBy: 'LastModifiedBy1', - url: 'http://squidex.io/hook', - version: '1', - trigger: { - param1: 1, - param2: 2, - triggerType: 'ContentChanged' - }, - action: { - param3: 3, - param4: 4, - actionType: 'Webhook' - }, - isEnabled: true - } - ]); + req.flush({ + items: [ + ruleResponse(12), + ruleResponse(13) + ] + }); expect(rules!).toEqual( - [ - new RuleDto('id1', 'CreatedBy1', 'LastModifiedBy1', - DateTime.parseISO_UTC('2016-12-12T10:10'), - DateTime.parseISO_UTC('2017-12-12T10:10'), - version, - true, - { - param1: 1, - param2: 2, - triggerType: 'ContentChanged' - }, - 'ContentChanged', - { - param3: 3, - param4: 4, - actionType: 'Webhook' - }, - 'Webhook') - ]); + new RulesDto(2, [ + createRule(12), + createRule(13) + ])); })); it('should make post request to create rule', @@ -179,7 +148,7 @@ describe('RulesService', () => { } }; - let rule: Versioned; + let rule: RuleDto; rulesService.postRule('my-app', dto).subscribe(result => { rule = result; @@ -190,18 +159,13 @@ describe('RulesService', () => { expect(req.request.method).toEqual('POST'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({ id: 'id1' }, { + req.flush(ruleResponse(12), { headers: { etag: '1' } }); - expect(rule!).toEqual({ - payload: { - id: 'id1' - }, - version - }); + expect(rule!).toEqual(createRule(12)); })); it('should make put request to update rule', @@ -216,46 +180,88 @@ describe('RulesService', () => { } }; - rulesService.putRule('my-app', '123', dto, version).subscribe(); + const resource: Resource = { + _links: { + update: { method: 'PUT', href: '/api/apps/my-app/rules/123' } + } + }; + + let rule: RuleDto; + + rulesService.putRule('my-app', resource, dto, version).subscribe(result => { + rule = result; + }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/123'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(ruleResponse(123)); + + expect(rule!).toEqual(createRule(123)); })); it('should make put request to enable rule', inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - rulesService.enableRule('my-app', '123', version).subscribe(); + const resource: Resource = { + _links: { + enable: { method: 'PUT', href: '/api/apps/my-app/rules/123/enable' } + } + }; + + let rule: RuleDto; + + rulesService.enableRule('my-app', resource, version).subscribe(result => { + rule = result; + }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/123/enable'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(ruleResponse(123)); + + expect(rule!).toEqual(createRule(123)); })); it('should make put request to disable rule', inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - rulesService.disableRule('my-app', '123', version).subscribe(); + const resource: Resource = { + _links: { + disable: { method: 'PUT', href: '/api/apps/my-app/rules/123/disable' } + } + }; + + let rule: RuleDto; + + rulesService.disableRule('my-app', resource, version).subscribe(result => { + rule = result; + }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/123/disable'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(ruleResponse(123)); + + expect(rule!).toEqual(createRule(123)); })); it('should make delete request to delete rule', inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - rulesService.deleteRule('my-app', '123', version).subscribe(); + const resource: Resource = { + _links: { + delete: { method: 'DELETE', href: '/api/apps/my-app/rules/123' } + } + }; + + rulesService.deleteRule('my-app', resource, version).subscribe(); const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/123'); @@ -322,7 +328,13 @@ describe('RulesService', () => { it('should make put request to enqueue rule event', inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - rulesService.enqueueEvent('my-app', '123').subscribe(); + const resource: Resource = { + _links: { + update: { method: 'PUT', href: '/api/apps/my-app/rules/events/123' } + } + }; + + rulesService.enqueueEvent('my-app', resource).subscribe(); const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/events/123'); @@ -335,7 +347,13 @@ describe('RulesService', () => { it('should make delete request to cancel rule event', inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - rulesService.cancelEvent('my-app', '123').subscribe(); + const resource: Resource = { + _links: { + delete: { method: 'DELETE', href: '/api/apps/my-app/rules/events/123' } + } + }; + + rulesService.cancelEvent('my-app', resource).subscribe(); const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/events/123'); @@ -344,4 +362,58 @@ describe('RulesService', () => { req.flush({}); })); -}); \ No newline at end of file + + function ruleResponse(id: number, suffix = '') { + return { + id: `id${id}`, + created: `${id % 1000 + 2000}-12-12T10:10`, + createdBy: `creator-${id}`, + lastModified: `${id % 1000 + 2000}-11-11T10:10`, + lastModifiedBy: `modifier-${id}`, + isEnabled: id % 2 === 0, + trigger: { + param1: 1, + param2: 2, + triggerType: `ContentChanged${id}${suffix}` + }, + action: { + param3: 3, + param4: 4, + actionType: `Webhook${id}${suffix}` + }, + version: id, + _links: { + update: { method: 'PUT', href: `/rules/${id}` } + } + }; + } +}); + +export function createRule(id: number, suffix = '') { + const result = new RuleDto( + `id${id}`, + `creator-${id}`, + `modifier-${id}`, + DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10`), + DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10`), + new Version(`${id}`), + id % 2 === 0, + { + param1: 1, + param2: 2, + triggerType: `ContentChanged${id}${suffix}` + }, + `ContentChanged${id}${suffix}`, + { + param3: 3, + param4: 4, + actionType: `Webhook${id}${suffix}` + }, + `Webhook${id}${suffix}`); + + result._links['update'] = { + method: 'PUT', href: `/rules/${id}` + }; + + return result; +} \ No newline at end of file diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index 7af1acfb7..c0a7030de 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -15,12 +15,13 @@ import { ApiUrlConfig, DateTime, HTTP, - mapVersioned, Model, pretifyError, + Resource, + ResourceLinks, ResultSet, Version, - Versioned + withLinks } from '@app/framework'; export const ALL_TRIGGERS = { @@ -74,7 +75,13 @@ export class RuleElementPropertyDto { } } +export class RulesDto extends ResultSet { + public readonly _links: ResourceLinks = {}; +} + export class RuleDto extends Model { + public readonly _links: ResourceLinks = {}; + constructor( public readonly id: string, public readonly createdBy: string, @@ -92,9 +99,13 @@ export class RuleDto extends Model { } } -export class RuleEventsDto extends ResultSet { } +export class RuleEventsDto extends ResultSet { + public readonly _links: ResourceLinks = {}; +} export class RuleEventDto extends Model { + public readonly _links: ResourceLinks = {}; + constructor( public readonly id: string, public readonly created: DateTime, @@ -115,10 +126,6 @@ export interface UpsertRuleDto { readonly action: RuleAction; } -export interface RuleCreatedDto { - readonly id: string; -} - export type RuleAction = { actionType: string } & any; export type RuleTrigger = { triggerType: string } & any; @@ -167,77 +174,87 @@ export class RulesService { pretifyError('Failed to load Rules. Please reload.')); } - public getRules(appName: string): Observable { + public getRules(appName: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`); return HTTP.getVersioned(this.http, url).pipe( map(({ payload }) => { - const items: any[] = payload.body; + const items: any[] = payload.body.items; - const rules = items.map(item => - new RuleDto( - item.id, - item.createdBy, - item.lastModifiedBy, - DateTime.parseISO_UTC(item.created), - DateTime.parseISO_UTC(item.lastModified), - new Version(item.version.toString()), - item.isEnabled, - item.trigger, - item.trigger.triggerType, - item.action, - item.action.actionType)); - - return rules; + const rules = items.map(item => parseRule(item)); + + return withLinks(new RulesDto(rules.length, rules), payload.body); }), pretifyError('Failed to load Rules. Please reload.')); } - public postRule(appName: string, dto: UpsertRuleDto): Observable> { + public postRule(appName: string, dto: UpsertRuleDto): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`); - return HTTP.postVersioned(this.http, url, dto).pipe( - mapVersioned(({ body }) => body!), + return HTTP.postVersioned(this.http, url, dto).pipe( + map(({ payload }) => { + return parseRule(payload.body); + }), tap(() => { this.analytics.trackEvent('Rule', 'Created', appName); }), pretifyError('Failed to create rule. Please reload.')); } - public putRule(appName: string, id: string, dto: Partial, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}`); + public putRule(appName: string, resource: Resource, dto: Partial, version: Version): Observable { + const link = resource._links['update']; - return HTTP.putVersioned(this.http, url, dto, version).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( + map(({ payload }) => { + return parseRule(payload.body); + }), tap(() => { this.analytics.trackEvent('Rule', 'Updated', appName); }), pretifyError('Failed to update rule. Please reload.')); } - public enableRule(appName: string, id: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}/enable`); + public enableRule(appName: string, resource: Resource, version: Version): Observable { + const link = resource._links['enable']; - return HTTP.putVersioned(this.http, url, {}, version).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version, {}).pipe( + map(({ payload }) => { + return parseRule(payload.body); + }), tap(() => { - this.analytics.trackEvent('Rule', 'Updated', appName); + this.analytics.trackEvent('Rule', 'Enabled', appName); }), pretifyError('Failed to enable rule. Please reload.')); } - public disableRule(appName: string, id: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}/disable`); + public disableRule(appName: string, resource: Resource, version: Version): Observable { + const link = resource._links['disable']; - return HTTP.putVersioned(this.http, url, {}, version).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version, {}).pipe( + map(({ payload }) => { + return parseRule(payload.body); + }), tap(() => { - this.analytics.trackEvent('Rule', 'Updated', appName); + this.analytics.trackEvent('Rule', 'Disabled', appName); }), pretifyError('Failed to disable rule. Please reload.')); } - public deleteRule(appName: string, id: string, version: Version): Observable { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}`); + public deleteRule(appName: string, resource: Resource, version: Version): Observable { + const link = resource._links['delete']; - return HTTP.deleteVersioned(this.http, url, version).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version).pipe( + map(({ payload }) => { + return parseRule(payload.body); + }), tap(() => { this.analytics.trackEvent('Rule', 'Deleted', appName); }), @@ -270,23 +287,44 @@ export class RulesService { pretifyError('Failed to load events. Please reload.')); } - public enqueueEvent(appName: string, id: string): Observable { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/events/${id}`); + public enqueueEvent(appName: string, resource: Resource): Observable { + const link = resource._links['update']; + + const url = this.apiUrl.buildUrl(link.href); - return HTTP.putVersioned(this.http, url, {}).pipe( + return HTTP.requestVersioned(this.http, link.method, url).pipe( tap(() => { this.analytics.trackEvent('Rule', 'EventEnqueued', appName); }), pretifyError('Failed to enqueue rule event. Please reload.')); } - public cancelEvent(appName: string, id: string): Observable { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/events/${id}`); + public cancelEvent(appName: string, resource: Resource): Observable { + const link = resource._links['delete']; - return HTTP.deleteVersioned(this.http, url).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url).pipe( tap(() => { this.analytics.trackEvent('Rule', 'EventDequeued', appName); }), pretifyError('Failed to cancel rule event. Please reload.')); } +} + +function parseRule(resource: any) { + return withLinks( + new RuleDto( + resource.id, + resource.createdBy, + resource.lastModifiedBy, + DateTime.parseISO_UTC(resource.created), + DateTime.parseISO_UTC(resource.lastModified), + new Version(resource.version.toString()), + resource.isEnabled, + resource.trigger, + resource.trigger.triggerType, + resource.action, + resource.action.actionType), + resource); } \ No newline at end of file diff --git a/src/Squidex/app/shared/state/asset-uploader.state.spec.ts b/src/Squidex/app/shared/state/asset-uploader.state.spec.ts index 17413beff..c3a185314 100644 --- a/src/Squidex/app/shared/state/asset-uploader.state.spec.ts +++ b/src/Squidex/app/shared/state/asset-uploader.state.spec.ts @@ -22,7 +22,7 @@ import { createAsset } from './../services/assets.service.spec'; import { TestValues } from './_test-helpers'; -describe('AssetsState', () => { +describe('AssetUploaderState', () => { const { app, appsState @@ -155,7 +155,7 @@ describe('AssetsState', () => { it('should update status when uploading asset completes', () => { const file: File = { name: 'my-file' }; - let updated = createAsset(1, undefined, '-new'); + let updated = createAsset(1, undefined, '_new'); assetsService.setup(x => x.replaceFile(app, asset, file, asset.version)) .returns(() => of(10, 20, updated)).verifiable(); diff --git a/src/Squidex/app/shared/state/assets.state.spec.ts b/src/Squidex/app/shared/state/assets.state.spec.ts index c9f045a02..f8df2c6d7 100644 --- a/src/Squidex/app/shared/state/assets.state.spec.ts +++ b/src/Squidex/app/shared/state/assets.state.spec.ts @@ -41,6 +41,9 @@ describe('AssetsState', () => { dialogs = Mock.ofType(); assetsService = Mock.ofType(); + assetsService.setup(x => x.getTags(app)) + .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable(Times.atLeastOnce()); + assetsState = new AssetsState(appsState.object, assetsService.object, dialogs.object); }); @@ -50,12 +53,9 @@ describe('AssetsState', () => { describe('Loading', () => { it('should load assets', () => { - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue([]))) .returns(() => of(new AssetsDto(200, [asset1, asset2]))).verifiable(); - assetsService.setup(x => x.getTags(app)) - .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable(); - assetsState.load().subscribe(); expect(assetsState.snapshot.assets.values).toEqual([asset1, asset2]); @@ -66,11 +66,8 @@ describe('AssetsState', () => { }); it('should show notification on load when reload is true', () => { - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) - .returns(() => of(new AssetsDto(200, [asset1, asset2]))); - - assetsService.setup(x => x.getTags(app)) - .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable(); + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue([]))) + .returns(() => of(new AssetsDto(200, [asset1, asset2]))).verifiable(); assetsState.load(true).subscribe(); @@ -80,20 +77,20 @@ describe('AssetsState', () => { }); it('should load with tags when tag toggled', () => { - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1'])) - .returns(() => of(new AssetsDto(0, []))); + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue(['tag1']))) + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.toggleTag('tag1').subscribe(); expect(assetsState.isTagSelected('tag1')).toBeTruthy(); }); - it('should load without tags when tag toggled', () => { - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1'])) - .returns(() => of(new AssetsDto(0, []))); + it('should load without tags when tag untoggled', () => { + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue(['tag1']))) + .returns(() => of(new AssetsDto(0, []))).verifiable(); - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) - .returns(() => of(new AssetsDto(0, []))); + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue([]))) + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.toggleTag('tag1').subscribe(); assetsState.toggleTag('tag1').subscribe(); @@ -102,8 +99,8 @@ describe('AssetsState', () => { }); it('should load with tags when tags selected', () => { - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, ['tag1', 'tag2'])) - .returns(() => of(new AssetsDto(0, []))); + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue(['tag1', 'tag2']))) + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.selectTags(['tag1', 'tag2']).subscribe(); @@ -111,8 +108,8 @@ describe('AssetsState', () => { }); it('should load without tags when tags reset', () => { - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) - .returns(() => of(new AssetsDto(0, []))); + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue([]))) + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.resetTags().subscribe(); @@ -120,9 +117,13 @@ describe('AssetsState', () => { }); it('should load next page and prev page when paging', () => { - assetsService.setup(x => x.getAssets(app, 30, 30, undefined, [])) - .returns(() => of(new AssetsDto(200, []))); + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue([]))) + .returns(() => of(new AssetsDto(200, []))).verifiable(Times.exactly(2)); + assetsService.setup(x => x.getAssets(app, 30, 30, undefined, It.isValue([]))) + .returns(() => of(new AssetsDto(200, []))).verifiable(); + + assetsState.load().subscribe(); assetsState.goNext().subscribe(); assetsState.goPrev().subscribe(); @@ -130,8 +131,8 @@ describe('AssetsState', () => { }); it('should load with query when searching', () => { - assetsService.setup(x => x.getAssets(app, 30, 0, 'my-query', [])) - .returns(() => of(new AssetsDto(0, []))); + assetsService.setup(x => x.getAssets(app, 30, 0, 'my-query', It.isValue([]))) + .returns(() => of(new AssetsDto(0, []))).verifiable(); assetsState.search('my-query').subscribe(); @@ -141,12 +142,9 @@ describe('AssetsState', () => { describe('Updates', () => { beforeEach(() => { - assetsService.setup(x => x.getAssets(app, 30, 0, undefined, [])) + assetsService.setup(x => x.getAssets(app, 30, 0, undefined, It.isValue([]))) .returns(() => of(new AssetsDto(200, [asset1, asset2]))).verifiable(); - assetsService.setup(x => x.getTags(app)) - .returns(() => of({ tag1: 1, shared: 2, tag2: 1 })).verifiable(); - assetsState.load(true).subscribe(); }); @@ -165,7 +163,7 @@ describe('AssetsState', () => { }); it('should update asset when updated', () => { - const update = createAsset(1, ['new'], '-new'); + const update = createAsset(1, ['new'], '_new'); assetsState.update(update); diff --git a/src/Squidex/app/shared/state/rule-events.state.spec.ts b/src/Squidex/app/shared/state/rule-events.state.spec.ts index 764654588..5adcd6f4a 100644 --- a/src/Squidex/app/shared/state/rule-events.state.spec.ts +++ b/src/Squidex/app/shared/state/rule-events.state.spec.ts @@ -76,24 +76,24 @@ describe('RuleEventsState', () => { }); it('should call service when enqueuing event', () => { - rulesService.setup(x => x.enqueueEvent(app, oldRuleEvents[0].id)) + rulesService.setup(x => x.enqueueEvent(app, oldRuleEvents[0])) .returns(() => of({})); ruleEventsState.enqueue(oldRuleEvents[0]).subscribe(); expect().nothing(); - rulesService.verify(x => x.enqueueEvent(app, oldRuleEvents[0].id), Times.once()); + rulesService.verify(x => x.enqueueEvent(app, oldRuleEvents[0]), Times.once()); }); it('should call service when cancelling event', () => { - rulesService.setup(x => x.cancelEvent(app, oldRuleEvents[0].id)) + rulesService.setup(x => x.cancelEvent(app, oldRuleEvents[0])) .returns(() => of({})); ruleEventsState.cancel(oldRuleEvents[0]).subscribe(); expect().nothing(); - rulesService.verify(x => x.cancelEvent(app, oldRuleEvents[0].id), Times.once()); + rulesService.verify(x => x.cancelEvent(app, oldRuleEvents[0]), Times.once()); }); }); \ No newline at end of file diff --git a/src/Squidex/app/shared/state/rule-events.state.ts b/src/Squidex/app/shared/state/rule-events.state.ts index b9bf61996..8f777b4d5 100644 --- a/src/Squidex/app/shared/state/rule-events.state.ts +++ b/src/Squidex/app/shared/state/rule-events.state.ts @@ -82,7 +82,7 @@ export class RuleEventsState extends State { } public enqueue(event: RuleEventDto): Observable { - return this.rulesService.enqueueEvent(this.appsState.appName, event.id).pipe( + return this.rulesService.enqueueEvent(this.appsState.appName, event).pipe( tap(() => { this.dialogs.notifyInfo('Events enqueued. Will be resend in a few seconds.'); }), @@ -90,7 +90,7 @@ export class RuleEventsState extends State { } public cancel(event: RuleEventDto): Observable { - return this.rulesService.cancelEvent(this.appsState.appName, event.id).pipe( + return this.rulesService.cancelEvent(this.appsState.appName, event).pipe( tap(() => { return this.next(s => { const ruleEvents = s.ruleEvents.replaceBy('id', setCancelled(event)); diff --git a/src/Squidex/app/shared/state/rules.state.spec.ts b/src/Squidex/app/shared/state/rules.state.spec.ts index 6b8a8cdd5..90ed19ada 100644 --- a/src/Squidex/app/shared/state/rules.state.spec.ts +++ b/src/Squidex/app/shared/state/rules.state.spec.ts @@ -12,30 +12,27 @@ import { RulesState } from './rules.state'; import { DialogService, - RuleDto, + RulesDto, RulesService, versioned } from '@app/shared/internal'; +import { createRule } from '../services/rules.service.spec'; + import { TestValues } from './_test-helpers'; describe('RulesState', () => { const { app, appsState, - authService, - creation, - creator, - modified, - modifier, newVersion, version } = TestValues; - const oldRules = [ - new RuleDto('id1', creator, creator, creation, creation, version, false, {}, 'trigger1', {}, 'action1'), - new RuleDto('id2', creator, creator, creation, creation, version, true, {}, 'trigger2', {}, 'action2') - ]; + const rule1 = createRule(1); + const rule2 = createRule(2); + + const newRule = createRule(3); let dialogs: IMock; let rulesService: IMock; @@ -45,7 +42,7 @@ describe('RulesState', () => { dialogs = Mock.ofType(); rulesService = Mock.ofType(); - rulesState = new RulesState(appsState.object, authService.object, dialogs.object, rulesService.object); + rulesState = new RulesState(appsState.object, dialogs.object, rulesService.object); }); afterEach(() => { @@ -55,11 +52,11 @@ describe('RulesState', () => { describe('Loading', () => { it('should load rules', () => { rulesService.setup(x => x.getRules(app)) - .returns(() => of(oldRules)).verifiable(); + .returns(() => of(new RulesDto(2, [rule1, rule2]))).verifiable(); rulesState.load().subscribe(); - expect(rulesState.snapshot.rules.values).toEqual(oldRules); + expect(rulesState.snapshot.rules.values).toEqual([rule1, rule2]); expect(rulesState.snapshot.isLoaded).toBeTruthy(); dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never()); @@ -67,7 +64,7 @@ describe('RulesState', () => { it('should show notification on load when reload is true', () => { rulesService.setup(x => x.getRules(app)) - .returns(() => of(oldRules)).verifiable(); + .returns(() => of(new RulesDto(2, [rule1, rule2]))).verifiable(); rulesState.load(true).subscribe(); @@ -81,89 +78,85 @@ describe('RulesState', () => { describe('Updates', () => { beforeEach(() => { rulesService.setup(x => x.getRules(app)) - .returns(() => of(oldRules)).verifiable(); + .returns(() => of(new RulesDto(2, [rule1, rule2]))).verifiable(); rulesState.load().subscribe(); }); it('should add rule to snapshot when created', () => { - const newRule = new RuleDto('id3', modifier, modifier, modified, modified, version, true, { value: 3 }, 'trigger3', { value: 1 }, 'action3'); - const request = { trigger: { triggerType: 'trigger3', value: 3 }, action: { actionType: 'action3', value: 1 } }; rulesService.setup(x => x.postRule(app, request)) - .returns(() => of(versioned(version, { id: 'id3' }))); + .returns(() => of(newRule)); - rulesState.create(request, modified).subscribe(); + rulesState.create(request).subscribe(); - expect(rulesState.snapshot.rules.values).toEqual([...oldRules, newRule]); + expect(rulesState.snapshot.rules.values).toEqual([rule1, rule2, newRule]); }); - it('should update action and update and user info when updated action', () => { + it('should update rule when updated action', () => { const newAction = {}; - rulesService.setup(x => x.putRule(app, oldRules[0].id, It.isAny(), version)) - .returns(() => of(versioned(newVersion))).verifiable(); + const updated = createRule(1, 'new'); + + rulesService.setup(x => x.putRule(app, rule1, It.isAny(), version)) + .returns(() => of(updated)).verifiable(); - rulesState.updateAction(oldRules[0], newAction, modified).subscribe(); + rulesState.updateAction(rule1, newAction).subscribe(); - const rule_1 = rulesState.snapshot.rules.at(0); + const newRule1 = rulesState.snapshot.rules.at(0); - expect(rule_1.action).toBe(newAction); - expectToBeModified(rule_1); + expect(newRule1).toEqual(updated); }); - it('should update trigger and update and user info when updated trigger', () => { + it('should update rule when updated trigger', () => { const newTrigger = {}; - rulesService.setup(x => x.putRule(app, oldRules[0].id, It.isAny(), version)) - .returns(() => of(versioned(newVersion))).verifiable(); + const updated = createRule(1, 'new'); - rulesState.updateTrigger(oldRules[0], newTrigger, modified).subscribe(); + rulesService.setup(x => x.putRule(app, rule1, It.isAny(), version)) + .returns(() => of(updated)).verifiable(); - const rule_1 = rulesState.snapshot.rules.at(0); + rulesState.updateTrigger(rule1, newTrigger).subscribe(); - expect(rule_1.trigger).toBe(newTrigger); - expectToBeModified(rule_1); + const rule1New = rulesState.snapshot.rules.at(0); + + expect(rule1New).toEqual(updated); }); - it('should mark as enabled and update and user info when enabled', () => { - rulesService.setup(x => x.enableRule(app, oldRules[0].id, version)) - .returns(() => of(versioned(newVersion))).verifiable(); + it('should update rule when enabled', () => { + const updated = createRule(1, 'new'); + + rulesService.setup(x => x.enableRule(app, rule1, version)) + .returns(() => of(updated)).verifiable(); - rulesState.enable(oldRules[0], modified).subscribe(); + rulesState.enable(rule1).subscribe(); - const rule_1 = rulesState.snapshot.rules.at(0); + const rule1New = rulesState.snapshot.rules.at(0); - expect(rule_1.isEnabled).toBeTruthy(); - expectToBeModified(rule_1); + expect(rule1New).toEqual(updated); }); - it('should mark as disabled and update and user info when disabled', () => { - rulesService.setup(x => x.disableRule(app, oldRules[1].id, version)) - .returns(() => of(versioned(newVersion))).verifiable(); + it('should update rule when disabled', () => { + const updated = createRule(1, 'new'); + + rulesService.setup(x => x.disableRule(app, rule1, version)) + .returns(() => of(updated)).verifiable(); - rulesState.disable(oldRules[1], modified).subscribe(); + rulesState.disable(rule1).subscribe(); - const rule_1 = rulesState.snapshot.rules.at(1); + const rule1New = rulesState.snapshot.rules.at(0); - expect(rule_1.isEnabled).toBeFalsy(); - expectToBeModified(rule_1); + expect(rule1New).toEqual(updated); }); it('should remove rule from snapshot when deleted', () => { - rulesService.setup(x => x.deleteRule(app, oldRules[0].id, version)) + rulesService.setup(x => x.deleteRule(app, rule1, version)) .returns(() => of(versioned(newVersion))).verifiable(); - rulesState.delete(oldRules[0]).subscribe(); + rulesState.delete(rule1).subscribe(); - expect(rulesState.snapshot.rules.values).toEqual([oldRules[1]]); + expect(rulesState.snapshot.rules.values).toEqual([rule2]); }); - - function expectToBeModified(rule_1: RuleDto) { - expect(rule_1.lastModified).toEqual(modified); - expect(rule_1.lastModifiedBy).toEqual(modifier); - expect(rule_1.version).toEqual(newVersion); - } }); }); \ No newline at end of file diff --git a/src/Squidex/app/shared/state/rules.state.ts b/src/Squidex/app/shared/state/rules.state.ts index 1a0bcac0f..2412433df 100644 --- a/src/Squidex/app/shared/state/rules.state.ts +++ b/src/Squidex/app/shared/state/rules.state.ts @@ -10,20 +10,16 @@ import { Observable } from 'rxjs'; import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { - DateTime, DialogService, ImmutableArray, + ResourceLinks, shareSubscribed, - State, - Version, - Versioned + State } from '@app/framework'; -import { AuthService} from './../services/auth.service'; import { AppsState } from './apps.state'; import { - RuleCreatedDto, RuleDto, RulesService, UpsertRuleDto @@ -33,6 +29,9 @@ interface Snapshot { // The current rules. rules: RulesList; + // The resource links. + links: ResourceLinks; + // Indicates if the rules are loaded. isLoaded?: boolean; } @@ -49,13 +48,16 @@ export class RulesState extends State { this.changes.pipe(map(x => !!x.isLoaded), distinctUntilChanged()); + public links = + this.changes.pipe(map(x => x.links), + distinctUntilChanged()); + constructor( private readonly appsState: AppsState, - private readonly authState: AuthService, private readonly dialogs: DialogService, private readonly rulesService: RulesService ) { - super({ rules: ImmutableArray.empty() }); + super({ rules: ImmutableArray.empty(), links: {} }); } public load(isReload = false): Observable { @@ -64,23 +66,22 @@ export class RulesState extends State { } return this.rulesService.getRules(this.appName).pipe( - tap(payload => { + tap(({ items, _links: links }) => { if (isReload) { this.dialogs.notifyInfo('Rules reloaded.'); } this.next(s => { - const rules = ImmutableArray.of(payload); + const rules = ImmutableArray.of(items); - return { ...s, rules, isLoaded: true }; + return { ...s, rules, isLoaded: true, links }; }); }), shareSubscribed(this.dialogs)); } - public create(request: UpsertRuleDto, now?: DateTime): Observable { + public create(request: UpsertRuleDto): Observable { return this.rulesService.postRule(this.appName, request).pipe( - map(payload => createRule(request, payload, this.user, now)), tap(created => { this.next(s => { const rules = s.rules.push(created); @@ -92,7 +93,7 @@ export class RulesState extends State { } public delete(rule: RuleDto): Observable { - return this.rulesService.deleteRule(this.appName, rule.id, rule.version).pipe( + return this.rulesService.deleteRule(this.appName, rule, rule.version).pipe( tap(() => { this.next(s => { const rules = s.rules.removeAll(x => x.id === rule.id); @@ -103,36 +104,32 @@ export class RulesState extends State { shareSubscribed(this.dialogs)); } - public updateAction(rule: RuleDto, action: any, now?: DateTime): Observable { - return this.rulesService.putRule(this.appName, rule.id, { action }, rule.version).pipe( - map(({ version }) => updateAction(rule, action, this.user, version, now)), + public updateAction(rule: RuleDto, action: any): Observable { + return this.rulesService.putRule(this.appName, rule, { action }, rule.version).pipe( tap(updated => { this.replaceRule(updated); }), shareSubscribed(this.dialogs)); } - public updateTrigger(rule: RuleDto, trigger: any, now?: DateTime): Observable { - return this.rulesService.putRule(this.appName, rule.id, { trigger }, rule.version).pipe( - map(({ version }) => updateTrigger(rule, trigger, this.user, version, now)), + public updateTrigger(rule: RuleDto, trigger: any): Observable { + return this.rulesService.putRule(this.appName, rule, { trigger }, rule.version).pipe( tap(updated => { this.replaceRule(updated); }), shareSubscribed(this.dialogs)); } - public enable(rule: RuleDto, now?: DateTime): Observable { - return this.rulesService.enableRule(this.appName, rule.id, rule.version).pipe( - map(({ version }) => setEnabled(rule, true, this.user, version, now)), + public enable(rule: RuleDto): Observable { + return this.rulesService.enableRule(this.appName, rule, rule.version).pipe( tap(updated => { this.replaceRule(updated); }), shareSubscribed(this.dialogs)); } - public disable(rule: RuleDto, now?: DateTime): Observable { - return this.rulesService.disableRule(this.appName, rule.id, rule.version).pipe( - map(({ version }) => setEnabled(rule, false, this.user, version, now)), + public disable(rule: RuleDto): Observable { + return this.rulesService.disableRule(this.appName, rule, rule.version).pipe( tap(updated => { this.replaceRule(updated); }), @@ -150,57 +147,4 @@ export class RulesState extends State { private get appName() { return this.appsState.appName; } - - private get user() { - return this.authState.user!.token; - } -} - -const updateTrigger = (rule: RuleDto, trigger: any, user: string, version: Version, now?: DateTime) => - rule.with({ - trigger, - triggerType: trigger.triggerType, - lastModified: now || DateTime.now(), - lastModifiedBy: user, - version - }); - -const updateAction = (rule: RuleDto, action: any, user: string, version: Version, now?: DateTime) => - rule.with({ - action, - actionType: action.actionType, - lastModified: now || DateTime.now(), - lastModifiedBy: user, - version - }); - -const setEnabled = (rule: RuleDto, isEnabled: boolean, user: string, version: Version, now?: DateTime) => - rule.with({ - isEnabled, - lastModified: now || DateTime.now(), - lastModifiedBy: user, - version - }); - -function createRule(request: UpsertRuleDto, { payload, version }: Versioned, user: string, now?: DateTime) { - now = now || DateTime.now(); - - const { triggerType, ...trigger } = request.trigger; - - const { actionType, ...action } = request.action; - - const rule = new RuleDto( - payload.id, - user, - user, - now, - now, - version, - true, - trigger, - triggerType, - action, - actionType); - - return rule; } \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs index d26a8335c..ee40fbd6e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs @@ -76,9 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Assets var result = context.Result(); - Assert.Equal(assetId, result.IdOrValue); - Assert.Contains("tag1", result.Tags); - Assert.Contains("tag2", result.Tags); + Assert.Equal(assetId, result.Asset.Id); + Assert.Contains("tag1", result.Asset.Tags); + Assert.Contains("tag2", result.Asset.Tags); AssertAssetHasBeenUploaded(0, context.ContextId); AssertAssetImageChecked(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs index eb2c0ac26..4f3773f9b 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Rules var result = await sut.ExecuteAsync(CreateRuleCommand(command)); - result.ShouldBeEquivalent(EntityCreatedResult.Create(Id, 0)); + result.ShouldBeEquivalent(sut.Snapshot); Assert.Equal(AppId, sut.Snapshot.AppId.Id); @@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Rules var result = await sut.ExecuteAsync(CreateRuleCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(2)); + result.ShouldBeEquivalent(sut.Snapshot); Assert.True(sut.Snapshot.RuleDef.IsEnabled); @@ -129,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Rules var result = await sut.ExecuteAsync(CreateRuleCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(1)); + result.ShouldBeEquivalent(sut.Snapshot); Assert.False(sut.Snapshot.RuleDef.IsEnabled); From 7a641431ca050f34f1c50a8d410b715b2a3e65bf Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 13 Jun 2019 12:40:41 +0100 Subject: [PATCH 009/175] Hateos for Backups. --- src/Squidex.Web/PermissionExtensions.cs | 1 - .../Api/Controllers/Apps/Models/AppDto.cs | 2 +- .../Controllers/Backups/BackupsController.cs | 7 +- .../Backups/Models/BackupJobDto.cs | 22 ++++++- .../Backups/Models/BackupJobsDto.cs | 49 ++++++++++++++ .../Controllers/Rules/Models/RuleEventsDto.cs | 1 - .../pages/backups/backups-page.component.html | 5 +- .../shared/services/backups.service.spec.ts | 31 ++++++--- .../app/shared/services/backups.service.ts | 65 ++++++++++++------- .../app/shared/state/backups.state.spec.ts | 19 +++--- src/Squidex/app/shared/state/backups.state.ts | 16 +++-- 11 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs diff --git a/src/Squidex.Web/PermissionExtensions.cs b/src/Squidex.Web/PermissionExtensions.cs index a6751e9e9..4bda25b58 100644 --- a/src/Squidex.Web/PermissionExtensions.cs +++ b/src/Squidex.Web/PermissionExtensions.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Http; using Squidex.Infrastructure.Security; -using Squidex.Shared; using Squidex.Shared.Identity; namespace Squidex.Web diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index dbb2f4ded..a41ad2dd0 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -112,7 +112,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models if (controller.HasPermission(AllPermissions.AppBackupsRead, result.Name, permissions: permissions)) { - result.AddGetLink("backups", controller.Url(x => nameof(x.GetJobs), values)); + result.AddGetLink("backups", controller.Url(x => nameof(x.GetBackups), values)); } if (controller.HasPermission(AllPermissions.AppClientsRead, result.Name, permissions: permissions)) diff --git a/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs b/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs index 0198fa186..e692bcfba 100644 --- a/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Mvc; using Orleans; using Squidex.Areas.Api.Controllers.Backups.Models; using Squidex.Domain.Apps.Entities.Backup; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Tasks; using Squidex.Shared; @@ -44,16 +43,16 @@ namespace Squidex.Areas.Api.Controllers.Backups /// [HttpGet] [Route("apps/{app}/backups/")] - [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(typeof(BackupJobsDto), 200)] [ApiPermission(Permissions.AppBackupsRead)] [ApiCosts(0)] - public async Task GetJobs(string app) + public async Task GetBackups(string app) { var backupGrain = grainFactory.GetGrain(AppId); var jobs = await backupGrain.GetStateAsync(); - var response = jobs.Value.ToArray(BackupJobDto.FromBackup); + var response = BackupJobsDto.FromBackups(jobs.Value, this, app); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs b/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs index 475f4b059..8cc2fd455 100644 --- a/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs @@ -9,10 +9,12 @@ using System; using NodaTime; using Squidex.Domain.Apps.Entities.Backup; using Squidex.Infrastructure.Reflection; +using Squidex.Shared; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Backups.Models { - public sealed class BackupJobDto + public sealed class BackupJobDto : Resource { /// /// The id of the backup job. @@ -44,9 +46,23 @@ namespace Squidex.Areas.Api.Controllers.Backups.Models /// public JobStatus Status { get; set; } - public static BackupJobDto FromBackup(IBackupJob backup) + public static BackupJobDto FromBackup(IBackupJob backup, ApiController controller, string app) { - return SimpleMapper.Map(backup, new BackupJobDto()); + var result = SimpleMapper.Map(backup, new BackupJobDto()); + + return CreateLinks(result, controller, app); + } + + private static BackupJobDto CreateLinks(BackupJobDto result, ApiController controller, string app) + { + var values = new { app, id = result.Id }; + + if (controller.HasPermission(Permissions.AppBackupsDelete, app)) + { + result.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteBackup), values)); + } + + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs b/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs new file mode 100644 index 000000000..33c88bc25 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs @@ -0,0 +1,49 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Squidex.Domain.Apps.Entities.Backup; +using Squidex.Shared; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Backups.Models +{ + public sealed class BackupJobsDto : Resource + { + /// + /// The backups. + /// + [Required] + public BackupJobDto[] Items { get; set; } + + public static BackupJobsDto FromBackups(IEnumerable backups, ApiController controller, string app) + { + var result = new BackupJobsDto + { + Items = backups.Select(x => BackupJobDto.FromBackup(x, controller, app)).ToArray() + }; + + return CreateLinks(result, controller, app); + } + + private static BackupJobsDto CreateLinks(BackupJobsDto result, ApiController controller, string app) + { + var values = new { app }; + + result.AddSelfLink(controller.Url(x => nameof(x.GetBackups), values)); + + if (controller.HasPermission(Permissions.AppBackupsCreate, app)) + { + result.AddPostLink("create", controller.Url(x => nameof(x.PostBackup), values)); + } + + return result; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs index add2af47f..83b6d4c6b 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html index dddcf8cf6..ca99931ff 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html @@ -12,7 +12,7 @@ - @@ -78,7 +78,8 @@
-
- + - {{userInfo.user.displayName}} + {{user.displayName}} - {{userInfo.user.email}} + {{user.email}} - - - - - - - - + +
- - - - diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 0abdd7eed..ee7498713 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -29,8 +29,6 @@ export class UserPageComponent extends ResourceOwner implements OnInit { public user?: UserDto; public userForm = new UserForm(this.formBuilder); - public isReadOnly = false; - constructor( public readonly usersState: UsersState, private readonly formBuilder: FormBuilder, @@ -49,9 +47,9 @@ export class UserPageComponent extends ResourceOwner implements OnInit { if (selectedUser) { this.userForm.load(selectedUser); - this.isReadOnly = !hasLink(this.user, 'update'); + this.canUpdate = hasLink(this.user, 'update'); - if (this.isReadOnly) { + if (!this.canUpdate) { this.userForm.form.disable(); } } @@ -59,7 +57,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit { } public save() { - if (this.isReadOnly) { + if (!this.canUpdate) { return; } diff --git a/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts b/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts index ba384f5d1..2a4c32c14 100644 --- a/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts +++ b/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts @@ -79,7 +79,7 @@ describe('EventConsumersState', () => { eventConsumersState.load().subscribe(); }); - it('should update evnet consumer when started', () => { + it('should update event consumer when started', () => { const updated = createEventConsumer(2, '_new'); eventConsumersService.setup(x => x.putStart(eventConsumer2)) diff --git a/src/Squidex/app/features/administration/state/users.state.spec.ts b/src/Squidex/app/features/administration/state/users.state.spec.ts index 3000bf6f6..279ff67c2 100644 --- a/src/Squidex/app/features/administration/state/users.state.spec.ts +++ b/src/Squidex/app/features/administration/state/users.state.spec.ts @@ -168,7 +168,7 @@ describe('UsersState', () => { expect(usersState.snapshot.selectedUser).toBeNull(); }); - it('should update user selected user when locked', () => { + it('should update user and selected user when locked', () => { const updated = createUser(2, '_new'); usersService.setup(x => x.lockUser(user2)) @@ -177,9 +177,9 @@ describe('UsersState', () => { usersState.select(user2.id).subscribe(); usersState.lock(user2).subscribe(); - const newUser2 = usersState.snapshot.users.at(1); + const user2New = usersState.snapshot.users.at(1); - expect(newUser2).toBe(usersState.snapshot.selectedUser!); + expect(user2New).toBe(usersState.snapshot.selectedUser!); }); it('should update user and selected user when unlocked', () => { @@ -191,10 +191,10 @@ describe('UsersState', () => { usersState.select(user2.id).subscribe(); usersState.unlock(user2).subscribe(); - const newUser2 = usersState.snapshot.users.at(1); + const user2New = usersState.snapshot.users.at(1); - expect(newUser2).toEqual(updated); - expect(newUser2).toBe(usersState.snapshot.selectedUser!); + expect(user2New).toEqual(updated); + expect(user2New).toBe(usersState.snapshot.selectedUser!); }); it('should update user and selected user when updated', () => { @@ -208,10 +208,10 @@ describe('UsersState', () => { usersState.select(user2.id).subscribe(); usersState.update(user2, request).subscribe(); - const newUser2 = usersState.snapshot.users.at(1); + const user2New = usersState.snapshot.users.at(1); - expect(newUser2).toEqual(updated); - expect(newUser2).toBe(usersState.snapshot.selectedUser!); + expect(user2New).toEqual(updated); + expect(user2New).toBe(usersState.snapshot.selectedUser!); }); it('should add user to snapshot when created', () => { diff --git a/src/Squidex/app/features/administration/state/users.state.ts b/src/Squidex/app/features/administration/state/users.state.ts index 04a4bc80a..d638c5e0c 100644 --- a/src/Squidex/app/features/administration/state/users.state.ts +++ b/src/Squidex/app/features/administration/state/users.state.ts @@ -60,10 +60,6 @@ export class UsersState extends State { this.changes.pipe(map(x => x.usersPager), distinctUntilChanged()); - public links = - this.changes.pipe(map(x => x.links), - distinctUntilChanged()); - public selectedUser = this.changes.pipe(map(x => x.selectedUser), distinctUntilChanged()); @@ -72,6 +68,10 @@ export class UsersState extends State { this.changes.pipe(map(x => !!x.isLoaded), distinctUntilChanged()); + public links = + this.changes.pipe(map(x => x.links), + distinctUntilChanged()); + constructor( private readonly dialogs: DialogService, private readonly usersService: UsersService @@ -131,7 +131,7 @@ export class UsersState extends State { selectedUser = users.find(x => x.id === selectedUser!.id) || selectedUser; } - return { ...s, users, usersPager, links, selectedUser, isLoaded: true }; + return { ...s, users, usersPager, selectedUser, isLoaded: true, links }; }); }), shareSubscribed(this.dialogs)); diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html index 422b0a98f..ecb11c85b 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html @@ -106,12 +106,12 @@ - + - + diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts index c9b1a77a2..fd7dcd668 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts @@ -5,11 +5,12 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { Form, + hasLink, ImmutableArray, RuleDto, RuleElementDto, @@ -26,7 +27,7 @@ export const MODE_EDIT_ACTION = 'EditAction'; styleUrls: ['./rule-wizard.component.scss'], templateUrl: './rule-wizard.component.html' }) -export class RuleWizardComponent implements OnInit { +export class RuleWizardComponent implements AfterViewInit, OnInit { public actionForm = new Form(new FormGroup({})); public actionType: string; public action: any = {}; @@ -35,6 +36,8 @@ export class RuleWizardComponent implements OnInit { public triggerType: string; public trigger: any = {}; + public canUpdate: boolean; + public step = 1; @Output() @@ -61,6 +64,8 @@ export class RuleWizardComponent implements OnInit { } public ngOnInit() { + this.canUpdate = !this.rule || hasLink(this.rule, 'update'); + if (this.mode === MODE_EDIT_ACTION) { this.step = 4; @@ -74,6 +79,14 @@ export class RuleWizardComponent implements OnInit { } } + public ngAfterViewInit() { + if (!this.canUpdate) { + this.actionForm.form.disable(); + + this.triggerForm.form.disable(); + } + } + public emitComplete() { this.complete.emit(); } @@ -132,6 +145,10 @@ export class RuleWizardComponent implements OnInit { } private updateTrigger() { + if (!this.canUpdate) { + return; + } + this.rulesState.updateTrigger(this.rule, this.trigger) .subscribe(() => { this.emitComplete(); @@ -143,6 +160,10 @@ export class RuleWizardComponent implements OnInit { } private updateAction() { + if (!this.canUpdate) { + return; + } + this.rulesState.updateAction(this.rule, this.action) .subscribe(() => { this.emitComplete(); diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html index d6102c1be..0e97c61f2 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html @@ -6,16 +6,19 @@ - - - + + + + + @@ -23,7 +26,7 @@
No rule created yet. -
@@ -48,10 +51,11 @@
- + -
- + @@ -47,7 +50,7 @@
- + - {{contributorInfo.contributor.contributorId | sqxUserName}} + {{contributor.contributorId | sqxUserName}} - -
-