From d87093ee90ee4744c0e9908222c5f362fcbb47d7 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 16 Jul 2017 21:33:02 +0200 Subject: [PATCH] RX Everything! --- src/Squidex.Domain.Users/UserExtensions.cs | 2 +- .../UserManagerExtensions.cs | 4 +- src/Squidex/Controllers/UI/Extensions.cs | 2 +- .../pages/content/content-page.component.ts | 4 +- .../pages/dashboard-page.component.ts | 9 +- .../angular/can-deactivate.guard.spec.ts | 6 +- .../framework/angular/can-deactivate.guard.ts | 2 +- .../guards/app-must-exist.guard.spec.ts | 13 +-- .../app/shared/guards/app-must-exist.guard.ts | 14 ++-- .../must-be-authenticated.guard.spec.ts | 13 +-- .../guards/must-be-authenticated.guard.ts | 16 ++-- .../must-be-not-authenticated.guard.spec.ts | 13 +-- .../guards/must-be-not-authenticated.guard.ts | 16 ++-- .../resolve-app-languages.guard.spec.ts | 6 +- .../guards/resolve-app-languages.guard.ts | 16 ++-- .../guards/resolve-content.guard.spec.ts | 6 +- .../shared/guards/resolve-content.guard.ts | 16 ++-- .../resolve-published-schema.guard.spec.ts | 8 +- .../guards/resolve-published-schema.guard.ts | 18 ++-- .../guards/resolve-schema.guard.spec.ts | 6 +- .../app/shared/guards/resolve-schema.guard.ts | 16 ++-- .../shared/guards/resolve-user.guard.spec.ts | 6 +- .../app/shared/guards/resolve-user.guard.ts | 16 ++-- .../interceptors/auth.interceptor.spec.ts | 16 +++- .../shared/interceptors/auth.interceptor.ts | 38 +++++---- .../services/apps-store.service.spec.ts | 49 ++--------- .../app/shared/services/apps-store.service.ts | 18 ++-- .../app/shared/services/auth.service.ts | 82 ++++--------------- .../pages/internal/profile-menu.component.ts | 14 ++-- 29 files changed, 176 insertions(+), 269 deletions(-) diff --git a/src/Squidex.Domain.Users/UserExtensions.cs b/src/Squidex.Domain.Users/UserExtensions.cs index 1fe92e1f9..3685cfe62 100644 --- a/src/Squidex.Domain.Users/UserExtensions.cs +++ b/src/Squidex.Domain.Users/UserExtensions.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Users { public static class UserExtensions { - public static void UpdateDisplayName(this IUser user, string displayName) + public static void SetDisplayName(this IUser user, string displayName) { user.SetClaim(SquidexClaimTypes.SquidexDisplayName, displayName); } diff --git a/src/Squidex.Domain.Users/UserManagerExtensions.cs b/src/Squidex.Domain.Users/UserManagerExtensions.cs index c38652539..c207d6c7e 100644 --- a/src/Squidex.Domain.Users/UserManagerExtensions.cs +++ b/src/Squidex.Domain.Users/UserManagerExtensions.cs @@ -56,7 +56,7 @@ namespace Squidex.Domain.Users try { - user.UpdateDisplayName(displayName); + user.SetDisplayName(displayName); user.SetPictureUrlFromGravatar(email); await DoChecked(() => userManager.CreateAsync(user), "Cannot create user."); @@ -92,7 +92,7 @@ namespace Squidex.Domain.Users if (!string.IsNullOrWhiteSpace(displayName)) { - user.UpdateDisplayName(displayName); + user.SetDisplayName(displayName); } await DoChecked(() => userManager.UpdateAsync(user), "Cannot update user."); diff --git a/src/Squidex/Controllers/UI/Extensions.cs b/src/Squidex/Controllers/UI/Extensions.cs index 5e5fc484a..1ba7f7e63 100644 --- a/src/Squidex/Controllers/UI/Extensions.cs +++ b/src/Squidex/Controllers/UI/Extensions.cs @@ -19,7 +19,7 @@ namespace Squidex.Controllers.UI public static Task UpdateAsync(this UserManager userManager, IUser user, string email, string displayName) { user.UpdateEmail(email); - user.UpdateDisplayName(displayName); + user.SetDisplayName(displayName); return userManager.UpdateAsync(user); } 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 344bbe6a7..d33807dc7 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 @@ -91,9 +91,9 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone }); } - public canDeactivate(): Observable | Promise | boolean { + public canDeactivate(): Observable { if (!this.contentForm.dirty) { - return true; + return Observable.of(true); } else { this.cancelDialog.show(); diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts index 4d72915ad..31cb06cef 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts @@ -154,14 +154,7 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit, }; }); - this.authenticationSubscription = - this.authService.isAuthenticated.subscribe(() => { - const user = this.authService.user; - - if (user) { - this.profileDisplayName = user.displayName; - } - }); + this.profileDisplayName = this.authService.user.displayName; } public showForum() { diff --git a/src/Squidex/app/framework/angular/can-deactivate.guard.spec.ts b/src/Squidex/app/framework/angular/can-deactivate.guard.spec.ts index 431cb5ee6..8409e5f29 100644 --- a/src/Squidex/app/framework/angular/can-deactivate.guard.spec.ts +++ b/src/Squidex/app/framework/angular/can-deactivate.guard.spec.ts @@ -5,6 +5,8 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ +import { Observable } from 'rxjs'; + import { CanDeactivateGuard } from './can-deactivate.guard'; describe('CanDeactivateGuard', () => { @@ -15,13 +17,13 @@ describe('CanDeactivateGuard', () => { canDeactivate: () => { called = true; - return true; + return Observable.of(true); } }; const result = new CanDeactivateGuard().canDeactivate(component); - expect(result).toBeTruthy(); + expect(result).toBeDefined(); expect(called).toBeTruthy(); }); }); diff --git a/src/Squidex/app/framework/angular/can-deactivate.guard.ts b/src/Squidex/app/framework/angular/can-deactivate.guard.ts index 6f83bd951..ca59c563a 100644 --- a/src/Squidex/app/framework/angular/can-deactivate.guard.ts +++ b/src/Squidex/app/framework/angular/can-deactivate.guard.ts @@ -10,7 +10,7 @@ import { CanDeactivate } from '@angular/router'; import { Observable } from 'rxjs/Observable'; export interface CanComponentDeactivate { - canDeactivate(): Observable | Promise | boolean; + canDeactivate(): Observable; } @Injectable() diff --git a/src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts b/src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts index 0fa2d7a23..bc86f1e14 100644 --- a/src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts +++ b/src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts @@ -6,6 +6,7 @@ */ import { IMock, Mock } from 'typemoq'; +import { Observable } from 'rxjs'; import { AppsStoreService } from 'shared'; @@ -21,14 +22,14 @@ describe('AppMustExistGuard', () => { it('should navigate to 404 page if app is not found', (done) => { appsStore.setup(x => x.selectApp('my-app')) - .returns(() => Promise.resolve(false)); + .returns(() => Observable.of(false)); const router = new RouterMockup(); const route = { params: { appName: 'my-app' } }; const guard = new AppMustExistGuard(appsStore.object, router); guard.canActivate(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -38,14 +39,14 @@ describe('AppMustExistGuard', () => { it('should navigate to 404 page if app loading fails', (done) => { appsStore.setup(x => x.selectApp('my-app')) - .returns(() => Promise.reject('error')); + .returns(() => Observable.throw('error')); const router = new RouterMockup(); const route = { params: { appName: 'my-app' } }; const guard = new AppMustExistGuard(appsStore.object, router); guard.canActivate(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -55,14 +56,14 @@ describe('AppMustExistGuard', () => { it('should return true if app is found', (done) => { appsStore.setup(x => x.selectApp('my-app')) - .returns(() => Promise.resolve(true)); + .returns(() => Observable.of(true)); const router = new RouterMockup(); const route = { params: { appName: 'my-app' } }; const guard = new AppMustExistGuard(appsStore.object, router); guard.canActivate(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeTruthy(); expect(router.lastNavigation).toBeUndefined(); diff --git a/src/Squidex/app/shared/guards/app-must-exist.guard.ts b/src/Squidex/app/shared/guards/app-must-exist.guard.ts index 70e7549b1..945caf277 100644 --- a/src/Squidex/app/shared/guards/app-must-exist.guard.ts +++ b/src/Squidex/app/shared/guards/app-must-exist.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { AppsStoreService } from './../services/apps-store.service'; @@ -18,21 +19,20 @@ export class AppMustExistGuard implements CanActivate { ) { } - public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const appName = route.params['appName']; const result = this.appsStore.selectApp(appName) - .then(hasApp => { - if (!hasApp) { + .do(dto => { + if (!dto) { this.router.navigate(['/404']); } - - return hasApp; - }, () => { + }) + .catch(error => { this.router.navigate(['/404']); - return false; + return Observable.of(false); }); return result; diff --git a/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts b/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts index 99dbace55..b9031ea16 100644 --- a/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts +++ b/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts @@ -6,6 +6,7 @@ */ import { IMock, Mock } from 'typemoq'; +import { Observable } from 'rxjs'; import { AuthService } from 'shared'; @@ -20,14 +21,14 @@ describe('MustBeAuthenticatedGuard', () => { }); it('should navigate to default page if not authenticated', (done) => { - authService.setup(x => x.checkLogin()) - .returns(() => Promise.resolve(false)); + authService.setup(x => x.userChanges) + .returns(() => Observable.of(null)); const router = new RouterMockup(); const guard = new MustBeAuthenticatedGuard(authService.object, router); guard.canActivate({}, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['']); @@ -36,14 +37,14 @@ describe('MustBeAuthenticatedGuard', () => { }); it('should return true if authenticated', (done) => { - authService.setup(x => x.checkLogin()) - .returns(() => Promise.resolve(true)); + authService.setup(x => x.userChanges) + .returns(() => Observable.of({})); const router = new RouterMockup(); const guard = new MustBeAuthenticatedGuard(authService.object, router); guard.canActivate({}, {}) - .then(result => { + .subscribe(result => { expect(result).toBeTruthy(); expect(router.lastNavigation).toBeUndefined(); diff --git a/src/Squidex/app/shared/guards/must-be-authenticated.guard.ts b/src/Squidex/app/shared/guards/must-be-authenticated.guard.ts index 6a4238df6..95edea329 100644 --- a/src/Squidex/app/shared/guards/must-be-authenticated.guard.ts +++ b/src/Squidex/app/shared/guards/must-be-authenticated.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { AuthService } from './../services/auth.service'; @@ -18,12 +19,13 @@ export class MustBeAuthenticatedGuard implements CanActivate { ) { } - public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - return this.authService.checkLogin().then(isAuthenticated => { - if (!isAuthenticated) { - this.router.navigate(['']); - } - return isAuthenticated; - }); + public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.authService.userChanges.first() + .do(user => { + if (!user) { + this.router.navigate(['']); + } + }) + .map(user => !!user); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts b/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts index 04a489d3d..6a2aaf56b 100644 --- a/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts +++ b/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts @@ -6,6 +6,7 @@ */ import { IMock, Mock } from 'typemoq'; +import { Observable } from 'rxjs'; import { AuthService } from 'shared'; @@ -20,14 +21,14 @@ describe('MustBeNotAuthenticatedGuard', () => { }); it('should navigate to app page if authenticated', (done) => { - authService.setup(x => x.checkLogin()) - .returns(() => Promise.resolve(true)); + authService.setup(x => x.userChanges) + .returns(() => Observable.of({})); const router = new RouterMockup(); const guard = new MustBeNotAuthenticatedGuard(authService.object, router); guard.canActivate({}, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['app']); @@ -36,14 +37,14 @@ describe('MustBeNotAuthenticatedGuard', () => { }); it('should return true if not authenticated', (done) => { - authService.setup(x => x.checkLogin()) - .returns(() => Promise.resolve(false)); + authService.setup(x => x.userChanges) + .returns(() => Observable.of(null)); const router = new RouterMockup(); const guard = new MustBeNotAuthenticatedGuard(authService.object, router); guard.canActivate({}, {}) - .then(result => { + .subscribe(result => { expect(result).toBeTruthy(); expect(router.lastNavigation).toBeUndefined(); diff --git a/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.ts b/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.ts index c4f92aa3f..9f11076e8 100644 --- a/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.ts +++ b/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { AuthService } from './../services/auth.service'; @@ -18,12 +19,13 @@ export class MustBeNotAuthenticatedGuard implements CanActivate { ) { } - public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - return this.authService.checkLogin().then(isAuthenticated => { - if (isAuthenticated) { - this.router.navigate(['app']); - } - return !isAuthenticated; - }); + public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.authService.userChanges.first() + .do(user => { + if (user) { + this.router.navigate(['app']); + } + }) + .map(user => !user); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/guards/resolve-app-languages.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-app-languages.guard.spec.ts index 4fb5b8fec..509cfa3de 100644 --- a/src/Squidex/app/shared/guards/resolve-app-languages.guard.spec.ts +++ b/src/Squidex/app/shared/guards/resolve-app-languages.guard.spec.ts @@ -43,7 +43,7 @@ describe('ResolveAppLanguagesGuard', () => { const guard = new ResolveAppLanguagesGuard(appLanguagesService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -59,7 +59,7 @@ describe('ResolveAppLanguagesGuard', () => { const guard = new ResolveAppLanguagesGuard(appLanguagesService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -77,7 +77,7 @@ describe('ResolveAppLanguagesGuard', () => { const guard = new ResolveAppLanguagesGuard(appLanguagesService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBe(languages); done(); diff --git a/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts b/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts index 8dd836c19..92128d956 100644 --- a/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { allParams } from 'framework'; @@ -20,7 +21,7 @@ export class ResolveAppLanguagesGuard implements Resolve { ) { } - public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const params = allParams(route); const appName = params['appName']; @@ -30,19 +31,16 @@ export class ResolveAppLanguagesGuard implements Resolve { } const result = - this.appLanguagesService.getLanguages(appName).toPromise() - .then(dto => { + this.appLanguagesService.getLanguages(appName) + .do(dto => { if (!dto) { this.router.navigate(['/404']); - - return null; } - - return dto; - }, error => { + }) + .catch(error => { this.router.navigate(['/404']); - return null; + return Observable.of(null); }); return result; 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 0e7930c17..63a8003fc 100644 --- a/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts +++ b/src/Squidex/app/shared/guards/resolve-content.guard.spec.ts @@ -62,7 +62,7 @@ describe('ResolveContentGuard', () => { const guard = new ResolveContentGuard(appsStore.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -78,7 +78,7 @@ describe('ResolveContentGuard', () => { const guard = new ResolveContentGuard(appsStore.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -96,7 +96,7 @@ describe('ResolveContentGuard', () => { const guard = new ResolveContentGuard(appsStore.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBe(content); done(); diff --git a/src/Squidex/app/shared/guards/resolve-content.guard.ts b/src/Squidex/app/shared/guards/resolve-content.guard.ts index 2d707e66e..d2e9efdfd 100644 --- a/src/Squidex/app/shared/guards/resolve-content.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-content.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { allParams } from 'framework'; @@ -20,7 +21,7 @@ export class ResolveContentGuard implements Resolve { ) { } - public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const params = allParams(route); const appName = params['appName']; @@ -42,19 +43,16 @@ export class ResolveContentGuard implements Resolve { } const result = - this.contentsService.getContent(appName, schemaName, contentId).toPromise() - .then(dto => { + this.contentsService.getContent(appName, schemaName, contentId) + .do(dto => { if (!dto) { this.router.navigate(['/404']); - - return null; } - - return dto; - }, error => { + }) + .catch(error => { this.router.navigate(['/404']); - return null; + return Observable.of(null); }); return result; 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 cc520fc5d..29d17ff91 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 @@ -51,7 +51,7 @@ describe('ResolvePublishedSchemaGuard', () => { const guard = new ResolvePublishedSchemaGuard(schemasService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -67,7 +67,7 @@ describe('ResolvePublishedSchemaGuard', () => { const guard = new ResolvePublishedSchemaGuard(schemasService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -85,7 +85,7 @@ describe('ResolvePublishedSchemaGuard', () => { const guard = new ResolvePublishedSchemaGuard(schemasService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -103,7 +103,7 @@ describe('ResolvePublishedSchemaGuard', () => { const guard = new ResolvePublishedSchemaGuard(schemasService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBe(schema); done(); 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 1c04ff128..8c061d63a 100644 --- a/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { allParams } from 'framework'; @@ -20,7 +21,7 @@ export class ResolvePublishedSchemaGuard implements Resolve { ) { } - public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const params = allParams(route); const appName = params['appName']; @@ -36,19 +37,16 @@ export class ResolvePublishedSchemaGuard implements Resolve { } const result = - this.schemasService.getSchema(appName, schemaName).toPromise() - .then(dto => { - if (!dto || !dto.isPublished) { + this.schemasService.getSchema(appName, schemaName).map(dto => dto && dto.isPublished ? dto : null) + .do(dto => { + if (!dto) { this.router.navigate(['/404']); - - return null; } - - return dto; - }, error => { + }) + .catch(error => { this.router.navigate(['/404']); - return null; + return Observable.of(error); }); return result; 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 89d62e24e..2abb64e09 100644 --- a/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts +++ b/src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts @@ -51,7 +51,7 @@ describe('ResolveSchemaGuard', () => { const guard = new ResolveSchemaGuard(schemasService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -67,7 +67,7 @@ describe('ResolveSchemaGuard', () => { const guard = new ResolveSchemaGuard(schemasService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -85,7 +85,7 @@ describe('ResolveSchemaGuard', () => { const guard = new ResolveSchemaGuard(schemasService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBe(schema); done(); diff --git a/src/Squidex/app/shared/guards/resolve-schema.guard.ts b/src/Squidex/app/shared/guards/resolve-schema.guard.ts index e6ac61945..093bd45e2 100644 --- a/src/Squidex/app/shared/guards/resolve-schema.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-schema.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { allParams } from 'framework'; @@ -20,7 +21,7 @@ export class ResolveSchemaGuard implements Resolve { ) { } - public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const params = allParams(route); const appName = params['appName']; @@ -36,19 +37,16 @@ export class ResolveSchemaGuard implements Resolve { } const result = - this.schemasService.getSchema(appName, schemaName).toPromise() - .then(dto => { + this.schemasService.getSchema(appName, schemaName) + .do(dto => { if (!dto) { this.router.navigate(['/404']); - - return null; } - - return dto; - }, error => { + }) + .catch(error => { this.router.navigate(['/404']); - return null; + return Observable.of(error); }); return result; diff --git a/src/Squidex/app/shared/guards/resolve-user.guard.spec.ts b/src/Squidex/app/shared/guards/resolve-user.guard.spec.ts index a5d4b1ae7..f9788f179 100644 --- a/src/Squidex/app/shared/guards/resolve-user.guard.spec.ts +++ b/src/Squidex/app/shared/guards/resolve-user.guard.spec.ts @@ -43,7 +43,7 @@ describe('ResolveUserGuard', () => { const guard = new ResolveUserGuard(usersService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -59,7 +59,7 @@ describe('ResolveUserGuard', () => { const guard = new ResolveUserGuard(usersService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBeFalsy(); expect(router.lastNavigation).toEqual(['/404']); @@ -77,7 +77,7 @@ describe('ResolveUserGuard', () => { const guard = new ResolveUserGuard(usersService.object, router); guard.resolve(route, {}) - .then(result => { + .subscribe(result => { expect(result).toBe(user); done(); diff --git a/src/Squidex/app/shared/guards/resolve-user.guard.ts b/src/Squidex/app/shared/guards/resolve-user.guard.ts index d03d98478..a65482ff2 100644 --- a/src/Squidex/app/shared/guards/resolve-user.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-user.guard.ts @@ -7,6 +7,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; import { allParams } from 'framework'; @@ -20,7 +21,7 @@ export class ResolveUserGuard implements Resolve { ) { } - public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const params = allParams(route); const userId = params['userId']; @@ -30,19 +31,16 @@ export class ResolveUserGuard implements Resolve { } const result = - this.userManagementService.getUser(userId).toPromise() - .then(dto => { + this.userManagementService.getUser(userId) + .do(dto => { if (!dto) { this.router.navigate(['/404']); - - return null; } - - return dto; - }, error => { + }) + .catch(error => { this.router.navigate(['/404']); - return null; + return Observable.of(null); }); return result; diff --git a/src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts b/src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts index 9d9f54ded..9f5d379ef 100644 --- a/src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts +++ b/src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts @@ -8,6 +8,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClient, HttpHeaders, HTTP_INTERCEPTORS } from '@angular/common/http'; import { inject, TestBed } from '@angular/core/testing'; +import { Observable } from 'rxjs'; import { IMock, Mock, Times } from 'typemoq'; import { @@ -16,12 +17,11 @@ import { AuthInterceptor } from './../'; -describe('AppClientsService', () => { +describe('AuthInterceptor', () => { let authService: IMock = null; beforeEach(() => { authService = Mock.ofType(AuthService); - authService.setup(x => x.user).returns(() => { return { authToken: 'letmein' }; }); TestBed.configureTestingModule({ imports: [ @@ -46,6 +46,8 @@ describe('AppClientsService', () => { it('should append headers to request', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + authService.setup(x => x.userChanges).returns(() => { return Observable.of({ authToken: 'letmein' }); }); + http.get('http://service/p/apps').subscribe(); const req = httpMock.expectOne('http://service/p/apps'); @@ -59,6 +61,8 @@ describe('AppClientsService', () => { it('should not append headers for no auth headers', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + authService.setup(x => x.userChanges).returns(() => { return Observable.of({ authToken: 'letmein' }); }); + http.get('http://service/p/apps', { headers: new HttpHeaders().set('NoAuth', '') }).subscribe(); const req = httpMock.expectOne('http://service/p/apps'); @@ -72,6 +76,8 @@ describe('AppClientsService', () => { it('should not append headers for other requests', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + authService.setup(x => x.userChanges).returns(() => { return Observable.of({ authToken: 'letmein' }); }); + http.get('http://cloud/p/apps').subscribe(); const req = httpMock.expectOne('http://cloud/p/apps'); @@ -85,7 +91,7 @@ describe('AppClientsService', () => { it(`should logout for 404 status code when user is expired.`, inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { - authService.setup(x => x.user).returns(() => { return { authToken: 'letmein', isExpired: true }; }); + authService.setup(x => x.userChanges).returns(() => { return Observable.of({ authToken: 'letmein', isExpired: true }); }); http.get('http://service/p/apps').subscribe( _ => { /* NOOP */ }, @@ -102,6 +108,8 @@ describe('AppClientsService', () => { it(`should logout for ${statusCode} status code`, inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + authService.setup(x => x.userChanges).returns(() => { return Observable.of({ authToken: 'letmein' }); }); + http.get('http://service/p/apps').subscribe( _ => { /* NOOP */ }, _ => { /* NOOP */ }); @@ -118,6 +126,8 @@ describe('AppClientsService', () => { it(`should not logout for ${statusCode} status code`, inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + authService.setup(x => x.userChanges).returns(() => { return Observable.of({ authToken: 'letmein' }); }); + http.get('http://service/p/apps').subscribe( _ => { /* NOOP */ }, _ => { /* NOOP */ }); diff --git a/src/Squidex/app/shared/interceptors/auth.interceptor.ts b/src/Squidex/app/shared/interceptors/auth.interceptor.ts index db9035384..e5efce3cc 100644 --- a/src/Squidex/app/shared/interceptors/auth.interceptor.ts +++ b/src/Squidex/app/shared/interceptors/auth.interceptor.ts @@ -24,26 +24,30 @@ export class AuthInterceptor implements HttpInterceptor { public intercept(req: HttpRequest, next: HttpHandler): Observable> { if (req.url.indexOf(this.baseUrl) === 0 && !req.headers.has('NoAuth')) { - const authReq = req.clone({ - headers: req.headers - .set('Authorization', this.authService.user ? this.authService.user.authToken : '') - .set('Accept-Language', '*') - .set('Pragma', 'no-cache') - }); + return this.authService.userChanges.first().switchMap(user => { + const token = user ? user.authToken : ''; + + const authReq = req.clone({ + headers: req.headers + .set('Authorization', token) + .set('Accept-Language', '*') + .set('Pragma', 'no-cache') + }); - return next.handle(authReq) - .catch((error: HttpErrorResponse) => { - if (error.status === 404 && (!this.authService.user || this.authService.user.isExpired)) { - this.authService.logoutRedirect(); + return next.handle(authReq) + .catch((error: HttpErrorResponse) => { + if (error.status === 404 && (!user || user.isExpired)) { + this.authService.logoutRedirect(); - return Observable.empty(); - } else if (error.status === 401 || error.status === 403) { - this.authService.logoutRedirect(); + return Observable.empty(); + } else if (error.status === 401 || error.status === 403) { + this.authService.logoutRedirect(); - return Observable.empty(); - } - return Observable.throw(error); - }); + return Observable.empty(); + } + return Observable.throw(error); + }); + }); } else { return next.handle(req); } diff --git a/src/Squidex/app/shared/services/apps-store.service.spec.ts b/src/Squidex/app/shared/services/apps-store.service.spec.ts index 6fe17f1a0..1f89a72ec 100644 --- a/src/Squidex/app/shared/services/apps-store.service.spec.ts +++ b/src/Squidex/app/shared/services/apps-store.service.spec.ts @@ -12,7 +12,6 @@ import { AppDto, AppsService, AppsStoreService, - AuthService, CreateAppDto, DateTime } from './../'; @@ -24,23 +23,17 @@ describe('AppsStoreService', () => { const newApp = new AppDto('id', 'new-name', 'Owner', now, now); let appsService: IMock; - let authService: IMock; beforeEach(() => { appsService = Mock.ofType(AppsService); - authService = Mock.ofType(AuthService); }); - it('should load when authenticated once', () => { - authService.setup(x => x.isAuthenticated) - .returns(() => Observable.of(true)) - .verifiable(Times.once()); - + it('should load automatically', () => { appsService.setup(x => x.getApps()) .returns(() => Observable.of(oldApps)) .verifiable(Times.once()); - const store = new AppsStoreService(authService.object, appsService.object); + const store = new AppsStoreService(appsService.object); let result1: AppDto[] | null = null; let result2: AppDto[] | null = null; @@ -60,10 +53,6 @@ describe('AppsStoreService', () => { }); it('should add app to cache when created', () => { - authService.setup(x => x.isAuthenticated) - .returns(() => Observable.of(true)) - .verifiable(Times.once()); - appsService.setup(x => x.getApps()) .returns(() => Observable.of(oldApps)) .verifiable(Times.once()); @@ -72,7 +61,7 @@ describe('AppsStoreService', () => { .returns(() => Observable.of(newApp)) .verifiable(Times.once()); - const store = new AppsStoreService(authService.object, appsService.object); + const store = new AppsStoreService(appsService.object); let result1: AppDto[] | null = null; let result2: AppDto[] | null = null; @@ -93,42 +82,14 @@ describe('AppsStoreService', () => { appsService.verifyAll(); }); - it('should not add app to cache when cache is null', () => { - authService.setup(x => x.isAuthenticated) - .returns(() => Observable.of(false)) - .verifiable(Times.once()); - - appsService.setup(x => x.postApp(It.isAny())) - .returns(() => Observable.of(newApp)) - .verifiable(Times.once()); - - const store = new AppsStoreService(authService.object, appsService.object); - - let result: AppDto[] | null = null; - - store.createApp(new CreateAppDto('new-name'), now).subscribe(x => { /* Do Nothing */ }); - - store.apps.subscribe(x => { - result = x; - }).unsubscribe(); - - expect(result).toBeNull(); - - appsService.verifyAll(); - }); - it('should select app', (done) => { - authService.setup(x => x.isAuthenticated) - .returns(() => Observable.of(true)) - .verifiable(Times.once()); - appsService.setup(x => x.getApps()) .returns(() => Observable.of(oldApps)) .verifiable(Times.once()); - const store = new AppsStoreService(authService.object, appsService.object); + const store = new AppsStoreService(appsService.object); - store.selectApp('old-name').then((isSelected) => { + store.selectApp('old-name').subscribe(isSelected => { expect(isSelected).toBeTruthy(); appsService.verifyAll(); diff --git a/src/Squidex/app/shared/services/apps-store.service.ts b/src/Squidex/app/shared/services/apps-store.service.ts index ab516d41f..934ef21df 100644 --- a/src/Squidex/app/shared/services/apps-store.service.ts +++ b/src/Squidex/app/shared/services/apps-store.service.ts @@ -16,8 +16,6 @@ import { CreateAppDto } from './apps.service'; -import { AuthService } from './auth.service'; - @Injectable() export class AppsStoreService { private readonly apps$ = new ReplaySubject(); @@ -32,21 +30,21 @@ export class AppsStoreService { } constructor( - private readonly authService: AuthService, private readonly appsService: AppsService ) { if (!appsService) { return; } - this.authService.isAuthenticated.filter(t => !!t).first() - .switchMap(() => this.appsService.getApps()) + this.appsService.getApps() .subscribe(apps => { this.apps$.next(apps); + }, error => { + this.apps$.next([]); }); } - public selectApp(name: string | null): Promise { + public selectApp(name: string | null): Observable { return Observable.create((observer: Observer) => { this.apps$.subscribe(apps => { const app = apps.find(x => x.name === name) || null; @@ -58,7 +56,7 @@ export class AppsStoreService { }, error => { observer.error(error); }); - }).toPromise(); + }); } public createApp(dto: CreateAppDto, now?: DateTime): Observable { @@ -69,10 +67,8 @@ export class AppsStoreService { return new AppDto(created.id, dto.name, 'Owner', now, now); }) .do(app => { - this.apps$.defaultIfEmpty().first().subscribe(apps => { - if (apps) { - this.apps$.next(apps.concat([app])); - } + this.apps$.first().subscribe(apps => { + this.apps$.next(apps.concat([app])); }); }); } diff --git a/src/Squidex/app/shared/services/auth.service.ts b/src/Squidex/app/shared/services/auth.service.ts index 69b9c68b3..c22933116 100644 --- a/src/Squidex/app/shared/services/auth.service.ts +++ b/src/Squidex/app/shared/services/auth.service.ts @@ -6,7 +6,7 @@ */ import { Injectable } from '@angular/core'; -import { Observable, Subject } from 'rxjs'; +import { Observable, ReplaySubject } from 'rxjs'; import { Log, @@ -54,22 +54,15 @@ export class Profile { @Injectable() export class AuthService { private readonly userManager: UserManager; - private readonly isAuthenticatedChanged$ = new Subject(); - private loginCompleted: boolean | null = false; - private loginCache: Promise | null = null; - private currentUser: Profile | null = null; - - private readonly isAuthenticatedChangedPublished$ = - this.isAuthenticatedChanged$ - .distinctUntilChanged() - .publishReplay(1); + private readonly user$ = new ReplaySubject(); + private currentUser: Profile = null; public get user(): Profile | null { return this.currentUser; } - public get isAuthenticated(): Observable { - return this.isAuthenticatedChangedPublished$; + public get userChanges(): Observable { + return this.user$; } constructor(apiUrl: ApiUrlConfig) { @@ -92,27 +85,18 @@ export class AuthService { }); this.userManager.events.addUserLoaded(user => { - this.onAuthenticated(user); + this.user$.next(new Profile(user)); }); this.userManager.events.addUserUnloaded(() => { - this.onDeauthenticated(); + this.user$.next(null); }); - this.checkLogin(); - - this.isAuthenticatedChangedPublished$.connect(); - } + this.user$.subscribe(user => { + this.currentUser = user; + }); - public checkLogin(): Promise { - if (this.loginCompleted) { - return Promise.resolve(this.currentUser !== null); - } else { - if (!this.loginCache) { - this.loginCache = this.checkState(this.userManager.signinSilent()); - } - return this.loginCache; - } + this.userManager.signinSilent(); } public logoutRedirect(): Observable { @@ -123,6 +107,10 @@ export class AuthService { return Observable.fromPromise(this.userManager.signoutRedirectCallback()); } + public loginPopup(): Observable { + return Observable.fromPromise(this.userManager.signinPopup()); + } + public loginRedirect(): Observable { return Observable.fromPromise(this.userManager.signinRedirect()); } @@ -130,44 +118,4 @@ export class AuthService { public loginRedirectComplete(): Observable { return Observable.fromPromise(this.userManager.signinRedirectCallback()); } - - public loginPopup(): Observable { - const promise = this.checkState(this.userManager.signinPopup()); - - return Observable.fromPromise(promise); - } - - private onAuthenticated(user: User) { - this.currentUser = new Profile(user); - - this.isAuthenticatedChanged$.next(true); - } - - private onDeauthenticated() { - this.currentUser = null; - - this.isAuthenticatedChanged$.next(false); - } - - private checkState(promise: Promise): Promise { - const resultPromise = - promise - .then(user => { - this.loginCache = null; - this.loginCompleted = null; - - this.onAuthenticated(user); - - return !!this.currentUser; - }).catch((err) => { - this.loginCache = null; - this.loginCompleted = null; - - this.onDeauthenticated(); - - return false; - }); - - return resultPromise; - } } \ No newline at end of file diff --git a/src/Squidex/app/shell/pages/internal/profile-menu.component.ts b/src/Squidex/app/shell/pages/internal/profile-menu.component.ts index d417c53c2..d15f586e0 100644 --- a/src/Squidex/app/shell/pages/internal/profile-menu.component.ts +++ b/src/Squidex/app/shell/pages/internal/profile-menu.component.ts @@ -47,16 +47,12 @@ export class ProfileMenuComponent implements OnInit, OnDestroy { public ngOnInit() { this.authenticationSubscription = - this.authService.isAuthenticated.first() - .subscribe(() => { - const user = this.authService.user; + this.authService.userChanges.filter(user => !!user) + .subscribe(user => { + this.profileId = user.id; + this.profileDisplayName = user.displayName; - if (user) { - this.profileId = user.id; - this.profileDisplayName = user.displayName; - - this.isAdmin = user.isAdmin; - } + this.isAdmin = user.isAdmin; }); }