diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html
index cc8e5d6ec..2b138cddd 100644
--- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html
+++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html
@@ -67,8 +67,8 @@
-
-
+
();
+ public created = new EventEmitter();
@Output()
public cancelled = new EventEmitter();
@@ -63,7 +59,6 @@ export class SchemaFormComponent {
constructor(
public readonly apiUrl: ApiUrlConfig,
- private readonly notifications: NotificationService,
private readonly schemas: SchemasService,
private readonly formBuilder: FormBuilder,
private readonly authService: AuthService
@@ -95,25 +90,11 @@ export class SchemaFormComponent {
const me = this.authService.user!.token;
this.schemas.postSchema(this.appName, requestDto, me, undefined, schemaVersion)
- .switchMap(dto => {
- return this.schemas.getSchema(this.appName, dto.id)
- .retryWhen(errors => errors
- .delay(500)
- .take(10)
- .concat(Observable.throw(dto)));
- })
.subscribe(dto => {
- this.emitCreated(dto, true);
+ this.emitCreated(dto);
this.resetCreateForm();
}, error => {
- if (error instanceof SchemaDetailsDto) {
- this.notifications.notify(Notification.error('Schema has been created but is awaiting to be processed. Reload in a few seconds.'));
-
- this.emitCreated(error, false);
- this.resetCreateForm();
- } else {
- this.enableCreateForm(error.displayMessage);
- }
+ this.enableCreateForm(error.displayMessage);
});
}
}
@@ -122,8 +103,8 @@ export class SchemaFormComponent {
this.cancelled.emit();
}
- private emitCreated(schema: SchemaDto, isAvailable: boolean) {
- this.created.emit({ schema, isAvailable });
+ private emitCreated(schema: SchemaDetailsDto) {
+ this.created.emit(schema);
}
private enableCreateForm(message: string) {
diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
index d1c6d1646..4c45aafdb 100644
--- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
+++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
@@ -66,7 +66,7 @@
-
+
diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
index 75c79aaa1..a246013f1 100644
--- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
+++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
@@ -18,6 +18,7 @@ import {
MessageBus,
ModalView,
NotificationService,
+ RoutingCache,
SchemaDto,
SchemasService
} from 'shared';
@@ -47,7 +48,8 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
private readonly schemasService: SchemasService,
private readonly messageBus: MessageBus,
private readonly router: Router,
- private readonly route: ActivatedRoute
+ private readonly route: ActivatedRoute,
+ private readonly routingCache: RoutingCache
) {
super(notifications, apps);
}
@@ -97,14 +99,13 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
});
}
- public onSchemaCreated(dto: SchemaDto, isAvailable: boolean) {
+ public onSchemaCreated(dto: SchemaDto) {
this.updateSchemas(this.schemas.push(dto), this.schemaQuery);
this.addSchemaDialog.hide();
- if (isAvailable) {
- this.router.navigate([ dto.name ], { relativeTo: this.route });
- }
+ this.routingCache.set(`schema.${dto.name}`, dto);
+ this.router.navigate([ dto.name ], { relativeTo: this.route });
}
private updateSchemas(schemas: ImmutableArray, query?: string) {
diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts
index 2352082bb..8c98a1f7e 100644
--- a/src/Squidex/app/framework/declarations.ts
+++ b/src/Squidex/app/framework/declarations.ts
@@ -54,6 +54,7 @@ export * from './services/message-bus';
export * from './services/notification.service';
export * from './services/resource-loader.service';
export * from './services/root-view.service';
+export * from './services/routing-cache.service';
export * from './services/shortcut.service';
export * from './services/title.service';
diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts
index b255de2f1..d219c8157 100644
--- a/src/Squidex/app/framework/module.ts
+++ b/src/Squidex/app/framework/module.ts
@@ -50,6 +50,7 @@ import {
RichEditorComponent,
RootViewDirective,
RootViewService,
+ RoutingCache,
ScrollActiveDirective,
ShortcutComponent,
ShortcutService,
@@ -182,6 +183,7 @@ export class SqxFrameworkModule {
NotificationService,
ResourceLoaderService,
RootViewService,
+ RoutingCache,
ShortcutService,
TitleService
]
diff --git a/src/Squidex/app/framework/services/routing-cache.service.ts b/src/Squidex/app/framework/services/routing-cache.service.ts
new file mode 100644
index 000000000..ead7b1cfa
--- /dev/null
+++ b/src/Squidex/app/framework/services/routing-cache.service.ts
@@ -0,0 +1,24 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Sebastian Stehle. All rights reserved
+ */
+
+export class RoutingCache {
+ private readonly entries: { [key: string]: { value: any, inserted: number } } = {};
+
+ public getValue(key: string) {
+ let entry = this.entries[key];
+
+ if (entry && (new Date().getTime() - entry.inserted) < 100) {
+ return entry.value;
+ } else {
+ return undefined;
+ }
+ }
+
+ public set(key: string, value: T) {
+ this.entries[key] = { value, inserted: new Date().getTime() };
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts
index 63a8003fc..73fee408f 100644
--- a/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts
@@ -8,7 +8,7 @@
import { IMock, Mock } from 'typemoq';
import { Observable } from 'rxjs';
-import { ContentsService } from 'shared';
+import { ContentsService, RoutingCache } from 'shared';
import { ResolveContentGuard } from './resolve-content.guard';
import { RouterMockup } from './router-mockup';
@@ -31,35 +31,54 @@ describe('ResolveContentGuard', () => {
};
let appsStore: IMock;
+ let routingCache: IMock;
beforeEach(() => {
appsStore = Mock.ofType(ContentsService);
+
+ routingCache = Mock.ofType(RoutingCache);
});
it('should throw if route does not contain app name', () => {
- const guard = new ResolveContentGuard(appsStore.object, new RouterMockup());
+ const guard = new ResolveContentGuard(appsStore.object, new RouterMockup(), routingCache.object);
expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app name.');
});
it('should throw if route does not contain schema name', () => {
- const guard = new ResolveContentGuard(appsStore.object, new RouterMockup());
+ const guard = new ResolveContentGuard(appsStore.object, new RouterMockup(), routingCache.object);
expect(() => guard.resolve({ params: { appName: 'my-app' } }, {})).toThrow('Route must contain schema name.');
});
it('should throw if route does not contain content id', () => {
- const guard = new ResolveContentGuard(appsStore.object, new RouterMockup());
+ const guard = new ResolveContentGuard(appsStore.object, new RouterMockup(), routingCache.object);
expect(() => guard.resolve({ params: { appName: 'my-app', schemaName: 'my-schema' } }, {})).toThrow('Route must contain content id.');
});
+ it('should provide content from cache if found', (done) => {
+ const content = { };
+
+ routingCache.setup(x => x.getValue('content.123'))
+ .returns(() => content);
+
+ const guard = new ResolveContentGuard(appsStore.object, new RouterMockup(), routingCache.object);
+
+ guard.resolve(route, {})
+ .subscribe(result => {
+ expect(result).toBe(content);
+
+ done();
+ });
+ });
+
it('should navigate to 404 page if schema is not found', (done) => {
appsStore.setup(x => x.getContent('my-app', 'my-schema', '123'))
.returns(() => Observable.of(null!));
const router = new RouterMockup();
- const guard = new ResolveContentGuard(appsStore.object, router);
+ const guard = new ResolveContentGuard(appsStore.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
@@ -75,7 +94,7 @@ describe('ResolveContentGuard', () => {
.returns(() => Observable.throw(null!));
const router = new RouterMockup();
- const guard = new ResolveContentGuard(appsStore.object, router);
+ const guard = new ResolveContentGuard(appsStore.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
@@ -93,7 +112,7 @@ describe('ResolveContentGuard', () => {
.returns(() => Observable.of(content));
const router = new RouterMockup();
- const guard = new ResolveContentGuard(appsStore.object, router);
+ const guard = new ResolveContentGuard(appsStore.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
diff --git a/src/Squidex/app/shared/guards/resolve-content.guard.ts b/src/Squidex/app/shared/guards/resolve-content.guard.ts
index d2e9efdfd..4b8b4b47f 100644
--- a/src/Squidex/app/shared/guards/resolve-content.guard.ts
+++ b/src/Squidex/app/shared/guards/resolve-content.guard.ts
@@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
-import { allParams } from 'framework';
+import { allParams, RoutingCache } from 'framework';
import { ContentDto, ContentsService } from './../services/contents.service';
@@ -17,7 +17,8 @@ import { ContentDto, ContentsService } from './../services/contents.service';
export class ResolveContentGuard implements Resolve {
constructor(
private readonly contentsService: ContentsService,
- private readonly router: Router
+ private readonly router: Router,
+ private readonly routingCache: RoutingCache
) {
}
@@ -42,6 +43,12 @@ export class ResolveContentGuard implements Resolve {
throw 'Route must contain content id.';
}
+ const content = this.routingCache.getValue(`content.${contentId}`);
+
+ if (content) {
+ return Observable.of(content);
+ }
+
const result =
this.contentsService.getContent(appName, schemaName, contentId)
.do(dto => {
diff --git a/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts
index 29d17ff91..5d1bc2978 100644
--- a/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts
@@ -8,10 +8,9 @@
import { IMock, Mock } from 'typemoq';
import { Observable } from 'rxjs';
-import { SchemasService } from 'shared';
-
-import { ResolvePublishedSchemaGuard } from './resolve-published-schema.guard';
+import { RoutingCache, SchemasService } from 'shared';
import { RouterMockup } from './router-mockup';
+import { ResolvePublishedSchemaGuard } from './resolve-published-schema.guard';
describe('ResolvePublishedSchemaGuard', () => {
const route = {
@@ -26,29 +25,48 @@ describe('ResolvePublishedSchemaGuard', () => {
};
let schemasService: IMock;
+ let routingCache: IMock;
beforeEach(() => {
schemasService = Mock.ofType(SchemasService);
+
+ routingCache = Mock.ofType(RoutingCache);
});
it('should throw if route does not contain app name', () => {
- const guard = new ResolvePublishedSchemaGuard(schemasService.object, new RouterMockup());
+ const guard = new ResolvePublishedSchemaGuard(schemasService.object, new RouterMockup(), routingCache.object);
expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app name.');
});
it('should throw if route does not contain schema name', () => {
- const guard = new ResolvePublishedSchemaGuard(schemasService.object, new RouterMockup());
+ const guard = new ResolvePublishedSchemaGuard(schemasService.object, new RouterMockup(), routingCache.object);
expect(() => guard.resolve({ params: { appName: 'my-app' } }, {})).toThrow('Route must contain schema name.');
});
+ it('should provide schema from cache if found', (done) => {
+ const schema = { isPublished: true };
+
+ routingCache.setup(x => x.getValue('schema.my-schema'))
+ .returns(() => schema);
+
+ const guard = new ResolvePublishedSchemaGuard(schemasService.object, new RouterMockup(), routingCache.object);
+
+ guard.resolve(route, {})
+ .subscribe(result => {
+ expect(result).toBe(schema);
+
+ done();
+ });
+ });
+
it('should navigate to 404 page if schema is not found', (done) => {
schemasService.setup(x => x.getSchema('my-app', 'my-schema'))
.returns(() => Observable.of(null!));
const router = new RouterMockup();
- const guard = new ResolvePublishedSchemaGuard(schemasService.object, router);
+ const guard = new ResolvePublishedSchemaGuard(schemasService.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
@@ -64,7 +82,7 @@ describe('ResolvePublishedSchemaGuard', () => {
.returns(() => Observable.throw(null));
const router = new RouterMockup();
- const guard = new ResolvePublishedSchemaGuard(schemasService.object, router);
+ const guard = new ResolvePublishedSchemaGuard(schemasService.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
@@ -82,11 +100,11 @@ describe('ResolvePublishedSchemaGuard', () => {
.returns(() => Observable.of(schema));
const router = new RouterMockup();
- const guard = new ResolvePublishedSchemaGuard(schemasService.object, router);
+ const guard = new ResolvePublishedSchemaGuard(schemasService.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
- expect(result).toBeFalsy();
+ expect(result).toBe(schema);
expect(router.lastNavigation).toEqual(['/404']);
done();
@@ -100,7 +118,7 @@ describe('ResolvePublishedSchemaGuard', () => {
.returns(() => Observable.of(schema));
const router = new RouterMockup();
- const guard = new ResolvePublishedSchemaGuard(schemasService.object, router);
+ const guard = new ResolvePublishedSchemaGuard(schemasService.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
diff --git a/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts b/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts
index 8c061d63a..ad94f8ac8 100644
--- a/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts
+++ b/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts
@@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
-import { allParams } from 'framework';
+import { allParams, RoutingCache } from 'framework';
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service';
@@ -17,7 +17,8 @@ import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'
export class ResolvePublishedSchemaGuard implements Resolve {
constructor(
private readonly schemasService: SchemasService,
- private readonly router: Router
+ private readonly router: Router,
+ private readonly routingCache: RoutingCache
) {
}
@@ -36,10 +37,16 @@ export class ResolvePublishedSchemaGuard implements Resolve {
throw 'Route must contain schema name.';
}
+ const schema = this.routingCache.getValue(`schema.${schemaName}`);
+
+ if (schema && schema.isPublished) {
+ return Observable.of(schema);
+ }
+
const result =
- this.schemasService.getSchema(appName, schemaName).map(dto => dto && dto.isPublished ? dto : null)
+ this.schemasService.getSchema(appName, schemaName)
.do(dto => {
- if (!dto) {
+ if (!dto || !dto.isPublished) {
this.router.navigate(['/404']);
}
})
diff --git a/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts
index 2abb64e09..2628b7f58 100644
--- a/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts
@@ -8,10 +8,9 @@
import { IMock, Mock } from 'typemoq';
import { Observable } from 'rxjs';
-import { SchemasService } from 'shared';
-
-import { ResolveSchemaGuard } from './resolve-schema.guard';
+import { RoutingCache, SchemasService } from 'shared';
import { RouterMockup } from './router-mockup';
+import { ResolveSchemaGuard } from './resolve-schema.guard';
describe('ResolveSchemaGuard', () => {
const route = {
@@ -26,29 +25,48 @@ describe('ResolveSchemaGuard', () => {
};
let schemasService: IMock;
+ let routingCache: IMock;
beforeEach(() => {
schemasService = Mock.ofType(SchemasService);
+
+ routingCache = Mock.ofType(RoutingCache);
});
it('should throw if route does not contain app name', () => {
- const guard = new ResolveSchemaGuard(schemasService.object, new RouterMockup());
+ const guard = new ResolveSchemaGuard(schemasService.object, new RouterMockup(), routingCache.object);
expect(() => guard.resolve({ params: {} }, {})).toThrow('Route must contain app name.');
});
it('should throw if route does not contain schema name', () => {
- const guard = new ResolveSchemaGuard(schemasService.object, new RouterMockup());
+ const guard = new ResolveSchemaGuard(schemasService.object, new RouterMockup(), routingCache.object);
expect(() => guard.resolve({ params: { appName: 'my-app' } }, {})).toThrow('Route must contain schema name.');
});
+ it('should provide schema from cache if found', (done) => {
+ const schema = { isPublished: true };
+
+ routingCache.setup(x => x.getValue('schema.my-schema'))
+ .returns(() => schema);
+
+ const guard = new ResolveSchemaGuard(schemasService.object, new RouterMockup(), routingCache.object);
+
+ guard.resolve(route, {})
+ .subscribe(result => {
+ expect(result).toBe(schema);
+
+ done();
+ });
+ });
+
it('should navigate to 404 page if schema is not found', (done) => {
schemasService.setup(x => x.getSchema('my-app', 'my-schema'))
.returns(() => Observable.of(null!));
const router = new RouterMockup();
- const guard = new ResolveSchemaGuard(schemasService.object, router);
+ const guard = new ResolveSchemaGuard(schemasService.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
@@ -64,7 +82,7 @@ describe('ResolveSchemaGuard', () => {
.returns(() => Observable.throw(null!));
const router = new RouterMockup();
- const guard = new ResolveSchemaGuard(schemasService.object, router);
+ const guard = new ResolveSchemaGuard(schemasService.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
@@ -82,7 +100,7 @@ describe('ResolveSchemaGuard', () => {
.returns(() => Observable.of(schema));
const router = new RouterMockup();
- const guard = new ResolveSchemaGuard(schemasService.object, router);
+ const guard = new ResolveSchemaGuard(schemasService.object, router, routingCache.object);
guard.resolve(route, {})
.subscribe(result => {
diff --git a/src/Squidex/app/shared/guards/resolve-schema.guard.ts b/src/Squidex/app/shared/guards/resolve-schema.guard.ts
index 093bd45e2..95e494cc4 100644
--- a/src/Squidex/app/shared/guards/resolve-schema.guard.ts
+++ b/src/Squidex/app/shared/guards/resolve-schema.guard.ts
@@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
-import { allParams } from 'framework';
+import { allParams, RoutingCache } from 'framework';
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service';
@@ -17,7 +17,8 @@ import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'
export class ResolveSchemaGuard implements Resolve {
constructor(
private readonly schemasService: SchemasService,
- private readonly router: Router
+ private readonly router: Router,
+ private readonly routingCache: RoutingCache
) {
}
@@ -36,6 +37,12 @@ export class ResolveSchemaGuard implements Resolve {
throw 'Route must contain schema name.';
}
+ const schema = this.routingCache.getValue(`schema.${schemaName}`);
+
+ if (schema) {
+ return Observable.of(schema);
+ }
+
const result =
this.schemasService.getSchema(appName, schemaName)
.do(dto => {