mirror of https://github.com/Squidex/squidex.git
16 changed files with 993 additions and 146 deletions
@ -0,0 +1,87 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
/* |
||||
|
import { IMock, Mock } from 'typemoq'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import { UserManagementService } from 'shared'; |
||||
|
|
||||
|
import { ResolveUserGuard } from './resolve-user.guard'; |
||||
|
import { RouterMockup } from './router-mockup'; |
||||
|
|
||||
|
describe('ResolveUserGuard', () => { |
||||
|
const route = { |
||||
|
params: {}, |
||||
|
parent: { |
||||
|
params: { |
||||
|
userId: 'my-user' |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
let usersService: IMock<UserManagementService>; |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
usersService = Mock.ofType(UserManagementService); |
||||
|
}); |
||||
|
|
||||
|
it('should throw if route does not contain parameter', () => { |
||||
|
const guard = new ResolveUserGuard(usersService.object, <any>new RouterMockup()); |
||||
|
|
||||
|
expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain user id.'); |
||||
|
}); |
||||
|
|
||||
|
it('should navigate to 404 page if user is not found', (done) => { |
||||
|
usersService.setup(x => x.getUser('my-user')) |
||||
|
.returns(() => Observable.of(null!)); |
||||
|
const router = new RouterMockup(); |
||||
|
|
||||
|
const guard = new ResolveUserGuard(usersService.object, <any>router); |
||||
|
|
||||
|
guard.resolve(<any>route, <any>{}) |
||||
|
.subscribe(result => { |
||||
|
expect(result).toBeFalsy(); |
||||
|
expect(router.lastNavigation).toEqual(['/404']); |
||||
|
|
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should navigate to 404 page if user loading fails', (done) => { |
||||
|
usersService.setup(x => x.getUser('my-user')) |
||||
|
.returns(() => Observable.throw(null!)); |
||||
|
const router = new RouterMockup(); |
||||
|
|
||||
|
const guard = new ResolveUserGuard(usersService.object, <any>router); |
||||
|
|
||||
|
guard.resolve(<any>route, <any>{}) |
||||
|
.subscribe(result => { |
||||
|
expect(result).toBeFalsy(); |
||||
|
expect(router.lastNavigation).toEqual(['/404']); |
||||
|
|
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should return user if loading succeeded', (done) => { |
||||
|
const user: any = {}; |
||||
|
|
||||
|
usersService.setup(x => x.getUser('my-user')) |
||||
|
.returns(() => Observable.of(user)); |
||||
|
const router = new RouterMockup(); |
||||
|
|
||||
|
const guard = new ResolveUserGuard(usersService.object, <any>router); |
||||
|
|
||||
|
guard.resolve(<any>route, <any>{}) |
||||
|
.subscribe(result => { |
||||
|
expect(result).toBe(user); |
||||
|
|
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
*/ |
||||
@ -0,0 +1,24 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import { UsersState } from './../state/users.state'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class UnsetUserGuard implements CanActivate { |
||||
|
constructor( |
||||
|
private readonly usersState: UsersState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { |
||||
|
return this.usersState.selectUser(null).map(r => !r); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import { allParams } from 'framework'; |
||||
|
|
||||
|
import { UsersState } from './../state/users.state'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class UserMustExistGuard implements CanActivate { |
||||
|
constructor( |
||||
|
private readonly usersState: UsersState, |
||||
|
private readonly router: Router |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { |
||||
|
const params = allParams(route); |
||||
|
|
||||
|
const userId = params['userId']; |
||||
|
|
||||
|
if (!userId) { |
||||
|
throw 'Route must contain user id.'; |
||||
|
} |
||||
|
|
||||
|
const result = |
||||
|
this.usersState.selectUser(userId).map(u => !!u) |
||||
|
.do(dto => { |
||||
|
if (!dto) { |
||||
|
this.router.navigate(['/404']); |
||||
|
} |
||||
|
}) |
||||
|
.catch(error => { |
||||
|
this.router.navigate(['/404']); |
||||
|
|
||||
|
return Observable.of(false); |
||||
|
}); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; |
||||
|
import { inject, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { ApiUrlConfig } from 'framework'; |
||||
|
|
||||
|
import { EventConsumerDto, EventConsumersService } from './event-consumers.service'; |
||||
|
|
||||
|
describe('EventConsumerDto', () => { |
||||
|
it('should update isStopped property when starting', () => { |
||||
|
const consumer_1 = new EventConsumerDto('consumer', true, false, 'error', 'position'); |
||||
|
const consumer_2 = consumer_1.start(); |
||||
|
|
||||
|
expect(consumer_2.isStopped).toBeFalsy(); |
||||
|
}); |
||||
|
|
||||
|
it('should update isStopped property when starting', () => { |
||||
|
const consumer_1 = new EventConsumerDto('consumer', false, false, 'error', 'position'); |
||||
|
const consumer_2 = consumer_1.stop(); |
||||
|
|
||||
|
expect(consumer_2.isStopped).toBeTruthy(); |
||||
|
}); |
||||
|
|
||||
|
it('should update isResetting property when resetting', () => { |
||||
|
const consumer_1 = new EventConsumerDto('consumer', false, false, 'error', 'position'); |
||||
|
const consumer_2 = consumer_1.reset(); |
||||
|
|
||||
|
expect(consumer_2.isResetting).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('EventConsumersService', () => { |
||||
|
beforeEach(() => { |
||||
|
TestBed.configureTestingModule({ |
||||
|
imports: [ |
||||
|
HttpClientTestingModule |
||||
|
], |
||||
|
providers: [ |
||||
|
EventConsumersService, |
||||
|
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } |
||||
|
] |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => { |
||||
|
httpMock.verify(); |
||||
|
})); |
||||
|
|
||||
|
it('should make get request to get event consumers', |
||||
|
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
let eventConsumers: EventConsumerDto[] | null = null; |
||||
|
|
||||
|
eventConsumersService.getEventConsumers().subscribe(result => { |
||||
|
eventConsumers = result; |
||||
|
}); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/event-consumers'); |
||||
|
|
||||
|
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' |
||||
|
} |
||||
|
]); |
||||
|
|
||||
|
expect(eventConsumers).toEqual([ |
||||
|
new EventConsumerDto('event-consumer1', true, true, 'an error 1', '13'), |
||||
|
new EventConsumerDto('event-consumer2', true, true, 'an error 2', '29') |
||||
|
]); |
||||
|
})); |
||||
|
|
||||
|
it('should make put request to start event consumer', |
||||
|
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
eventConsumersService.startEventConsumer('event-consumer1').subscribe(); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/start'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('PUT'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush({}); |
||||
|
})); |
||||
|
|
||||
|
it('should make put request to stop event consumer', |
||||
|
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
eventConsumersService.stopEventConsumer('event-consumer1').subscribe(); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/stop'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('PUT'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush({}); |
||||
|
})); |
||||
|
|
||||
|
it('should make put request to reset event consumer', |
||||
|
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
eventConsumersService.resetEventConsumer('event-consumer1').subscribe(); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/reset'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('PUT'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush({}); |
||||
|
})); |
||||
|
}); |
||||
@ -0,0 +1,88 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { HttpClient } from '@angular/common/http'; |
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import 'framework/angular/http-extensions'; |
||||
|
|
||||
|
import { ApiUrlConfig, HTTP } from 'framework'; |
||||
|
|
||||
|
export class EventConsumerDto { |
||||
|
constructor( |
||||
|
public readonly name: string, |
||||
|
public readonly isStopped: boolean, |
||||
|
public readonly isResetting: boolean, |
||||
|
public readonly error: string, |
||||
|
public readonly position: string |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public start(): EventConsumerDto { |
||||
|
return new EventConsumerDto(this.name, false, false, this.error, this.position); |
||||
|
} |
||||
|
|
||||
|
public stop(): EventConsumerDto { |
||||
|
return new EventConsumerDto(this.name, true, false, this.error, this.position); |
||||
|
} |
||||
|
|
||||
|
public reset(): EventConsumerDto { |
||||
|
return new EventConsumerDto(this.name, this.isStopped, true, this.error, this.position); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Injectable() |
||||
|
export class EventConsumersService { |
||||
|
constructor( |
||||
|
private readonly http: HttpClient, |
||||
|
private readonly apiUrl: ApiUrlConfig |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public getEventConsumers(): Observable<EventConsumerDto[]> { |
||||
|
const url = this.apiUrl.buildUrl('/api/event-consumers'); |
||||
|
|
||||
|
return HTTP.getVersioned<any>(this.http, url) |
||||
|
.map(response => { |
||||
|
const body = response.payload.body; |
||||
|
|
||||
|
const items: any[] = body; |
||||
|
|
||||
|
return items.map(item => { |
||||
|
return new EventConsumerDto( |
||||
|
item.name, |
||||
|
item.isStopped, |
||||
|
item.isResetting, |
||||
|
item.error, |
||||
|
item.position); |
||||
|
}); |
||||
|
}) |
||||
|
.pretifyError('Failed to load event consumers. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public startEventConsumer(name: string): Observable<any> { |
||||
|
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/start`); |
||||
|
|
||||
|
return HTTP.putVersioned(this.http, url, {}) |
||||
|
.pretifyError('Failed to start event consumer. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public stopEventConsumer(name: string): Observable<any> { |
||||
|
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/stop`); |
||||
|
|
||||
|
return HTTP.putVersioned(this.http, url, {}) |
||||
|
.pretifyError('Failed to stop event consumer. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public resetEventConsumer(name: string): Observable<any> { |
||||
|
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/reset`); |
||||
|
|
||||
|
return HTTP.putVersioned(this.http, url, {}) |
||||
|
.pretifyError('Failed to reset event consumer. Please reload.'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,230 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; |
||||
|
import { inject, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { ApiUrlConfig } from 'framework'; |
||||
|
|
||||
|
import { |
||||
|
CreateUserDto, |
||||
|
UpdateUserDto, |
||||
|
UserDto, |
||||
|
UsersService, |
||||
|
UsersDto |
||||
|
} from './users.service'; |
||||
|
|
||||
|
describe('UserDto', () => { |
||||
|
it('should update email and display name property when unlocking', () => { |
||||
|
const user_1 = new UserDto('1', 'sebastian@squidex.io', 'Sebastian', 'picture', true); |
||||
|
const user_2 = user_1.update('qaisar@squidex.io', 'Qaisar'); |
||||
|
|
||||
|
expect(user_2.email).toEqual('qaisar@squidex.io'); |
||||
|
expect(user_2.displayName).toEqual('Qaisar'); |
||||
|
}); |
||||
|
|
||||
|
it('should update isLocked property when locking', () => { |
||||
|
const user_1 = new UserDto('1', 'sebastian@squidex.io', 'Sebastian', 'picture', false); |
||||
|
const user_2 = user_1.lock(); |
||||
|
|
||||
|
expect(user_2.isLocked).toBeTruthy(); |
||||
|
}); |
||||
|
|
||||
|
it('should update isLocked property when unlocking', () => { |
||||
|
const user_1 = new UserDto('1', 'sebastian@squidex.io', 'Sebastian', 'picture', true); |
||||
|
const user_2 = user_1.unlock(); |
||||
|
|
||||
|
expect(user_2.isLocked).toBeFalsy(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('UsersService', () => { |
||||
|
beforeEach(() => { |
||||
|
TestBed.configureTestingModule({ |
||||
|
imports: [ |
||||
|
HttpClientTestingModule |
||||
|
], |
||||
|
providers: [ |
||||
|
UsersService, |
||||
|
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } |
||||
|
] |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => { |
||||
|
httpMock.verify(); |
||||
|
})); |
||||
|
|
||||
|
it('should make get request to get many users', |
||||
|
inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
let users: UsersDto | null = null; |
||||
|
|
||||
|
userManagementService.getUsers(20, 30).subscribe(result => { |
||||
|
users = result; |
||||
|
}); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/user-management?take=20&skip=30&query='); |
||||
|
|
||||
|
expect(req.request.method).toEqual('GET'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush({ |
||||
|
total: 100, |
||||
|
items: [ |
||||
|
{ |
||||
|
id: '123', |
||||
|
email: 'mail1@domain.com', |
||||
|
displayName: 'User1', |
||||
|
pictureUrl: 'path/to/image1', |
||||
|
isLocked: true |
||||
|
}, |
||||
|
{ |
||||
|
id: '456', |
||||
|
email: 'mail2@domain.com', |
||||
|
displayName: 'User2', |
||||
|
pictureUrl: 'path/to/image2', |
||||
|
isLocked: true |
||||
|
} |
||||
|
] |
||||
|
}); |
||||
|
|
||||
|
expect(users).toEqual( |
||||
|
new UsersDto(100, [ |
||||
|
new UserDto('123', 'mail1@domain.com', 'User1', 'path/to/image1', true), |
||||
|
new UserDto('456', 'mail2@domain.com', 'User2', 'path/to/image2', true) |
||||
|
])); |
||||
|
})); |
||||
|
|
||||
|
it('should make get request with query to get many users', |
||||
|
inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
let users: UsersDto | null = null; |
||||
|
|
||||
|
userManagementService.getUsers(20, 30, 'my-query').subscribe(result => { |
||||
|
users = result; |
||||
|
}); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/user-management?take=20&skip=30&query=my-query'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('GET'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush({ |
||||
|
total: 100, |
||||
|
items: [ |
||||
|
{ |
||||
|
id: '123', |
||||
|
email: 'mail1@domain.com', |
||||
|
displayName: 'User1', |
||||
|
pictureUrl: 'path/to/image1', |
||||
|
isLocked: true |
||||
|
}, |
||||
|
{ |
||||
|
id: '456', |
||||
|
email: 'mail2@domain.com', |
||||
|
displayName: 'User2', |
||||
|
pictureUrl: 'path/to/image2', |
||||
|
isLocked: true |
||||
|
} |
||||
|
] |
||||
|
}); |
||||
|
|
||||
|
expect(users).toEqual( |
||||
|
new UsersDto(100, [ |
||||
|
new UserDto('123', 'mail1@domain.com', 'User1', 'path/to/image1', true), |
||||
|
new UserDto('456', 'mail2@domain.com', 'User2', 'path/to/image2', true) |
||||
|
])); |
||||
|
})); |
||||
|
|
||||
|
it('should make get request to get single user', |
||||
|
inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
let user: UserDto | null = null; |
||||
|
|
||||
|
userManagementService.getUser('123').subscribe(result => { |
||||
|
user = result; |
||||
|
}); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/user-management/123'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('GET'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush({ |
||||
|
id: '123', |
||||
|
email: 'mail1@domain.com', |
||||
|
displayName: 'User1', |
||||
|
pictureUrl: 'path/to/image1', |
||||
|
isLocked: true |
||||
|
}); |
||||
|
|
||||
|
expect(user).toEqual(new UserDto('123', 'mail1@domain.com', 'User1', 'path/to/image1', true)); |
||||
|
})); |
||||
|
|
||||
|
it('should make post request to create user', |
||||
|
inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
const dto = new CreateUserDto('mail@squidex.io', 'Squidex User', 'password'); |
||||
|
|
||||
|
let user: UserDto | null = null; |
||||
|
|
||||
|
userManagementService.postUser(dto).subscribe(result => { |
||||
|
user = result; |
||||
|
}); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/user-management'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('POST'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush({ id: '123', pictureUrl: 'path/to/image1' }); |
||||
|
|
||||
|
expect(user).toEqual(new UserDto('123', dto.email, dto.displayName, 'path/to/image1', false)); |
||||
|
})); |
||||
|
|
||||
|
it('should make put request to update user', |
||||
|
inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
const dto = new UpdateUserDto('mail@squidex.io', 'Squidex User', 'password'); |
||||
|
|
||||
|
userManagementService.putUser('123', dto).subscribe(); |
||||
|
|
||||
|
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({}); |
||||
|
})); |
||||
|
|
||||
|
it('should make put request to lock user', |
||||
|
inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
userManagementService.lockUser('123').subscribe(); |
||||
|
|
||||
|
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({}); |
||||
|
})); |
||||
|
|
||||
|
it('should make put request to unlock user', |
||||
|
inject([UsersService, HttpTestingController], (userManagementService: UsersService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
userManagementService.unlockUser('123').subscribe(); |
||||
|
|
||||
|
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({}); |
||||
|
})); |
||||
|
}); |
||||
@ -0,0 +1,145 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { HttpClient } from '@angular/common/http'; |
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import 'framework/angular/http-extensions'; |
||||
|
|
||||
|
import { ApiUrlConfig, HTTP } from 'framework'; |
||||
|
|
||||
|
export class UsersDto { |
||||
|
constructor( |
||||
|
public readonly total: number, |
||||
|
public readonly items: UserDto[] |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class UserDto { |
||||
|
constructor( |
||||
|
public readonly id: string, |
||||
|
public readonly email: string, |
||||
|
public readonly displayName: string, |
||||
|
public readonly pictureUrl: string | null, |
||||
|
public readonly isLocked: boolean |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public update(email: string, displayName: string): UserDto { |
||||
|
return new UserDto(this.id, email, displayName, this.pictureUrl, this.isLocked); |
||||
|
} |
||||
|
|
||||
|
public lock(): UserDto { |
||||
|
return new UserDto(this.id, this.email, this.displayName, this.pictureUrl, true); |
||||
|
} |
||||
|
|
||||
|
public unlock(): UserDto { |
||||
|
return new UserDto(this.id, this.email, this.displayName, this.pictureUrl, false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class CreateUserDto { |
||||
|
constructor( |
||||
|
public readonly email: string, |
||||
|
public readonly displayName: string, |
||||
|
public readonly password: string |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class UpdateUserDto { |
||||
|
constructor( |
||||
|
public readonly email: string, |
||||
|
public readonly displayName: string, |
||||
|
public readonly password: string |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Injectable() |
||||
|
export class UsersService { |
||||
|
constructor( |
||||
|
private readonly http: HttpClient, |
||||
|
private readonly apiUrl: ApiUrlConfig |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public getUsers(take: number, skip: number, query?: string): Observable<UsersDto> { |
||||
|
const url = this.apiUrl.buildUrl(`api/user-management?take=${take}&skip=${skip}&query=${query || ''}`); |
||||
|
|
||||
|
return HTTP.getVersioned<any>(this.http, url) |
||||
|
.map(response => { |
||||
|
const body = response.payload.body; |
||||
|
|
||||
|
const items: any[] = body.items; |
||||
|
|
||||
|
const users = items.map(item => { |
||||
|
return new UserDto( |
||||
|
item.id, |
||||
|
item.email, |
||||
|
item.displayName, |
||||
|
item.pictureUrl, |
||||
|
item.isLocked); |
||||
|
}); |
||||
|
|
||||
|
return new UsersDto(body.total, users); |
||||
|
}) |
||||
|
.pretifyError('Failed to load users. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public getUser(id: string): Observable<UserDto> { |
||||
|
const url = this.apiUrl.buildUrl(`api/user-management/${id}`); |
||||
|
|
||||
|
return HTTP.getVersioned<any>(this.http, url) |
||||
|
.map(response => { |
||||
|
const body = response.payload.body; |
||||
|
|
||||
|
return new UserDto( |
||||
|
body.id, |
||||
|
body.email, |
||||
|
body.displayName, |
||||
|
body.pictureUrl, |
||||
|
body.isLocked); |
||||
|
}) |
||||
|
.pretifyError('Failed to load user. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public postUser(dto: CreateUserDto): Observable<UserDto> { |
||||
|
const url = this.apiUrl.buildUrl('api/user-management'); |
||||
|
|
||||
|
return HTTP.postVersioned<any>(this.http, url, dto) |
||||
|
.map(response => { |
||||
|
const body = response.payload.body; |
||||
|
|
||||
|
return new UserDto(body.id, dto.email, dto.displayName, body.pictureUrl, false); |
||||
|
}) |
||||
|
.pretifyError('Failed to create user. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public putUser(id: string, dto: UpdateUserDto): Observable<any> { |
||||
|
const url = this.apiUrl.buildUrl(`api/user-management/${id}`); |
||||
|
|
||||
|
return HTTP.putVersioned(this.http, url, dto) |
||||
|
.pretifyError('Failed to update user. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public lockUser(id: string): Observable<any> { |
||||
|
const url = this.apiUrl.buildUrl(`api/user-management/${id}/lock`); |
||||
|
|
||||
|
return HTTP.putVersioned(this.http, url, {}) |
||||
|
.pretifyError('Failed to load users. Please retry.'); |
||||
|
} |
||||
|
|
||||
|
public unlockUser(id: string): Observable<any> { |
||||
|
const url = this.apiUrl.buildUrl(`api/user-management/${id}/unlock`); |
||||
|
|
||||
|
return HTTP.putVersioned(this.http, url, {}) |
||||
|
.pretifyError('Failed to load users. Please retry.'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,138 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { BehaviorSubject, Observable } from 'rxjs'; |
||||
|
|
||||
|
import 'framework/utils/rxjs-extensions'; |
||||
|
|
||||
|
import { |
||||
|
DialogService, |
||||
|
ErrorDto, |
||||
|
ImmutableArray, |
||||
|
Notification, |
||||
|
Pager |
||||
|
} from 'shared'; |
||||
|
|
||||
|
import { |
||||
|
CreateUserDto, |
||||
|
UserDto, |
||||
|
UsersService |
||||
|
} from './../services/users.service'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class UsersState { |
||||
|
public usersItems = new BehaviorSubject<ImmutableArray<UserDto>>(ImmutableArray.empty()); |
||||
|
public usersPager = new BehaviorSubject(new Pager(0)); |
||||
|
public usersQuery = new BehaviorSubject(''); |
||||
|
|
||||
|
public selectedUser = new BehaviorSubject<UserDto | null>(null); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly usersService: UsersService, |
||||
|
private readonly dialogs: DialogService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public selectUser(id: string | null): Observable<boolean> { |
||||
|
const observable = |
||||
|
!id ? |
||||
|
Observable.of(null) : |
||||
|
Observable.of(this.usersItems.value.find(x => x.id === id)) |
||||
|
.switchMap(user => { |
||||
|
if (!user) { |
||||
|
return this.usersService.getUser(id).catch(() => Observable.of(null)); |
||||
|
} else { |
||||
|
return Observable.of(user); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return observable |
||||
|
.do(user => { |
||||
|
this.selectedUser.next(user); |
||||
|
}) |
||||
|
.map(u => u !== null); |
||||
|
} |
||||
|
|
||||
|
public loadUsers(): Observable<any> { |
||||
|
return this.usersService.getUsers(this.usersPager.value.pageSize, this.usersPager.value.skip, this.usersQuery.value) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dtos => { |
||||
|
this.usersItems.nextBy(v => ImmutableArray.of(dtos.items)); |
||||
|
this.usersPager.nextBy(v => v.setCount(dtos.total)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public updateUser(request: CreateUserDto): Observable<any> { |
||||
|
const id = this.selectedUser.value!.id; |
||||
|
|
||||
|
return this.usersService.putUser(id, request) |
||||
|
.do(() => { |
||||
|
this.dialogs.notify(Notification.info('User saved successsfull')); |
||||
|
|
||||
|
this.usersItems.nextBy(v => v.replaceAll(x => x.id === id, u => u.update(request.email, request.displayName))); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public createUser(request: CreateUserDto): Observable<UserDto> { |
||||
|
return this.usersService.postUser(request) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(user => { |
||||
|
this.usersItems.nextBy(v => v.pushFront(user)); |
||||
|
this.usersPager.nextBy(v => v.incrementCount()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public lockUser(id: string): Observable<any> { |
||||
|
return this.usersService.lockUser(id) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(() => { |
||||
|
this.usersItems.nextBy(v => v.replaceAll(x => x.id === id, u => u.lock())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public unlockUser(id: string): Observable<any> { |
||||
|
return this.usersService.lockUser(id) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(() => { |
||||
|
this.usersItems.nextBy(v => v.replaceAll(x => x.id === id, u => u.unlock())); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public search(filter: string) { |
||||
|
this.usersPager.nextBy(v => new Pager(0)); |
||||
|
this.usersQuery.nextBy(v => filter); |
||||
|
|
||||
|
this.loadUsers(); |
||||
|
} |
||||
|
|
||||
|
public goNext() { |
||||
|
this.usersPager.nextBy(v => v.goNext()); |
||||
|
|
||||
|
this.loadUsers(); |
||||
|
} |
||||
|
|
||||
|
public goPrev() { |
||||
|
this.usersPager.nextBy(v => v.goPrev()); |
||||
|
|
||||
|
this.loadUsers(); |
||||
|
} |
||||
|
|
||||
|
public trackBy(index: number, user: UserDto): any { |
||||
|
return user.id; |
||||
|
} |
||||
|
|
||||
|
private notifyError(error: string | ErrorDto) { |
||||
|
if (error instanceof ErrorDto) { |
||||
|
this.dialogs.notify(Notification.error(error.displayMessage)); |
||||
|
} else { |
||||
|
this.dialogs.notify(Notification.error(error)); |
||||
|
} |
||||
|
|
||||
|
return Observable.throw(error); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
export function nextBy<T>(updater: (value: T) => T): void { |
||||
|
return this.next(updater(this.value)); |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; |
||||
|
|
||||
|
import { nextBy } from './rxjs-extensions-impl'; |
||||
|
|
||||
|
/* tslint:disable:no-shadowed-variable */ |
||||
|
|
||||
|
declare module 'rxjs/BehaviorSubject' { |
||||
|
interface BehaviorSubject<T> { |
||||
|
nextBy(updater: (value: T) => T): void; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
BehaviorSubject.prototype['nextBy'] = nextBy; |
||||
Loading…
Reference in new issue