Browse Source

Refactored some services.

pull/130/head
Sebastian Stehle 9 years ago
parent
commit
bb5346eb1a
  1. 8
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts
  2. 142
      src/Squidex/app/framework/angular/http-extensions-impl.ts
  3. 7
      src/Squidex/app/framework/angular/http-extensions.ts
  4. 72
      src/Squidex/app/framework/angular/http-utils.ts
  5. 2
      src/Squidex/app/framework/declarations.ts
  6. 6
      src/Squidex/app/framework/module.ts
  7. 2
      src/Squidex/app/shared/declarations-base.ts
  8. 51
      src/Squidex/app/shared/interceptors/auth.interceptor.ts
  9. 11
      src/Squidex/app/shared/module.ts
  10. 167
      src/Squidex/app/shared/services/app-clients.service.spec.ts
  11. 47
      src/Squidex/app/shared/services/app-clients.service.ts
  12. 112
      src/Squidex/app/shared/services/app-contributors.service.spec.ts
  13. 23
      src/Squidex/app/shared/services/app-contributors.service.ts
  14. 140
      src/Squidex/app/shared/services/app-languages.service.spec.ts
  15. 28
      src/Squidex/app/shared/services/app-languages.service.ts
  16. 106
      src/Squidex/app/shared/services/apps.service.spec.ts
  17. 16
      src/Squidex/app/shared/services/apps.service.ts
  18. 354
      src/Squidex/app/shared/services/assets.service.spec.ts
  19. 135
      src/Squidex/app/shared/services/assets.service.ts
  20. 89
      src/Squidex/app/shared/services/auth.service.ts
  21. 290
      src/Squidex/app/shared/services/contents.service.spec.ts
  22. 37
      src/Squidex/app/shared/services/contents.service.ts
  23. 109
      src/Squidex/app/shared/services/event-consumers.service.spec.ts
  24. 23
      src/Squidex/app/shared/services/event-consumers.service.ts
  25. 77
      src/Squidex/app/shared/services/help.service.spec.ts
  26. 7
      src/Squidex/app/shared/services/help.service.ts
  27. 68
      src/Squidex/app/shared/services/history.service.spec.ts
  28. 15
      src/Squidex/app/shared/services/history.service.ts
  29. 62
      src/Squidex/app/shared/services/languages.service.spec.ts
  30. 11
      src/Squidex/app/shared/services/languages.service.ts
  31. 99
      src/Squidex/app/shared/services/plans.service.spec.ts
  32. 19
      src/Squidex/app/shared/services/plans.service.ts
  33. 175
      src/Squidex/app/shared/services/schemas.fields.spec.ts
  34. 450
      src/Squidex/app/shared/services/schemas.service.spec.ts
  35. 70
      src/Squidex/app/shared/services/schemas.service.ts
  36. 149
      src/Squidex/app/shared/services/usages.service.spec.ts
  37. 38
      src/Squidex/app/shared/services/usages.service.ts
  38. 103
      src/Squidex/app/shared/services/users-provider.service.spec.ts
  39. 339
      src/Squidex/app/shared/services/users.service.spec.ts
  40. 49
      src/Squidex/app/shared/services/users.service.ts
  41. 99
      src/Squidex/app/shared/services/webhooks.service.1.ts
  42. 114
      src/Squidex/app/shared/services/webhooks.service.spec.ts
  43. 24
      src/Squidex/app/shared/services/webhooks.service.ts
  44. 35
      src/Squidex/package.json
  45. 4
      src/Squidex/tslint.json

8
src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts

@ -6,6 +6,7 @@
*/
import { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
@ -16,7 +17,6 @@ import {
ApiUrlConfig,
AppComponentBase,
AppsStoreService,
AuthService,
NotificationService
} from 'shared';
@ -31,8 +31,8 @@ export class GraphQLPageComponent extends AppComponentBase implements OnInit {
public graphiQLContainer: ElementRef;
constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig
private readonly apiUrl: ApiUrlConfig,
private readonly http: HttpClient
) {
super(notifications, apps);
}
@ -48,7 +48,7 @@ export class GraphQLPageComponent extends AppComponentBase implements OnInit {
private request(params: any) {
return this.appNameOnce()
.switchMap(app => this.authService.authPost(this.apiUrl.buildUrl(`api/content/${app}/graphql`), params).map(r => r.json()))
.switchMap(app => this.http.post(this.apiUrl.buildUrl(`api/content/${app}/graphql`), params))
.toPromise();
}
}

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

@ -0,0 +1,142 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Version } from './../utils/version';
export class EntityCreatedDto {
constructor(
public readonly id: any
) {
}
}
export class ErrorDto {
public get displayMessage(): string {
let result = this.message;
if (this.details && this.details.length > 0) {
const detailMessage = this.details[0];
const lastChar = result[result.length - 1];
if (lastChar !== '.' && lastChar !== ',') {
result += '.';
}
result += ' ';
result += detailMessage;
}
const lastChar = result[result.length - 1];
if (lastChar !== '.') {
result += '.';
}
return result;
}
constructor(
public readonly statusCode: number,
public readonly message: string,
public readonly details: string[] = []
) {
}
}
export module HTTP {
export function getVersioned(http: HttpClient, url: string, version?: Version): Observable<any> {
if (version) {
return http.get(url, { observe: 'response', headers: new HttpHeaders().set('If-Match', version.value) })
.do((response: HttpResponse<any>) => {
if (version && response.status.toString().indexOf('2') === 0 && response.headers) {
const etag = response.headers.get('etag');
if (etag) {
version.update(etag);
}
}
}).map((response: HttpResponse<any>) => response.body);
} else {
return http.get(url, { observe: 'response' }).map((response: HttpResponse<any>) => response.body);
}
}
export function postVersioned(http: HttpClient, url: string, body: any, version?: Version): Observable<any> {
if (version) {
return http.post(url, body, { observe: 'response', headers: new HttpHeaders().set('If-Match', version.value) })
.do((response: HttpResponse<any>) => {
if (version && response.status.toString().indexOf('2') === 0 && response.headers) {
const etag = response.headers.get('etag');
if (etag) {
version.update(etag);
}
}
}).map((response: HttpResponse<any>) => response.body);
} else {
return http.post(url, body, { observe: 'response' }).map((response: HttpResponse<any>) => response.body);
}
}
export function putVersioned(http: HttpClient, url: string, body: any, version?: Version): Observable<any> {
if (version) {
return http.put(url, body, { observe: 'response', headers: new HttpHeaders().set('If-Match', version.value) })
.do((response: HttpResponse<any>) => {
if (version && response.status.toString().indexOf('2') === 0 && response.headers) {
const etag = response.headers.get('etag');
if (etag) {
version.update(etag);
}
}
}).map((response: HttpResponse<any>) => response.body);
} else {
return http.put(url, body, { observe: 'response' }).map((response: HttpResponse<any>) => response.body);
}
}
export function deleteVersioned(http: HttpClient, url: string, version?: Version): Observable<any> {
if (version) {
return http.delete(url, { observe: 'response', headers: new HttpHeaders().set('If-Match', version.value) })
.do((response: HttpResponse<any>) => {
if (version && response.status.toString().indexOf('2') === 0 && response.headers) {
const etag = response.headers.get('etag');
if (etag) {
version.update(etag);
}
}
}).map((response: HttpResponse<any>) => response.body);
} else {
return http.delete(url, { observe: 'response' }).map((response: HttpResponse<any>) => response.body);
}
}
}
export function pretifyError(message: string): Observable<any> {
return this.catch((response: HttpErrorResponse) => {
let result = new ErrorDto(500, message);
if (!(response.error instanceof Error)) {
try {
if (response.status === 412) {
result = new ErrorDto(response.status, 'Failed to make the update. Another user has made a change. Please reload.');
} else if (response.status !== 500) {
result = new ErrorDto(response.status, response.error.message, response.error.details);
}
} catch (e) {
result = result;
}
}
return Observable.throw(result);
});
}

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

@ -6,12 +6,13 @@
*/
import { Observable } from 'rxjs/Observable';
import { catchError } from './http-utils';
import { pretifyError } from './http-extensions-impl';
declare module 'rxjs/Observable' {
interface Observable<T> {
catchError(message: string): Observable<any>;
pretifyError(message: string): Observable<any>;
}
}
Observable.prototype['catchError'] = catchError;
Observable.prototype['pretifyError'] = pretifyError;

72
src/Squidex/app/framework/angular/http-utils.ts

@ -1,72 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response } from '@angular/http';
import { Observable } from 'rxjs';
export class EntityCreatedDto {
constructor(
public readonly id: any
) {
}
}
export class ErrorDto {
public get displayMessage(): string {
let result = this.message;
if (this.details && this.details.length > 0) {
const detailMessage = this.details[0];
const lastChar = result[result.length - 1];
if (lastChar !== '.' && lastChar !== ',') {
result += '.';
}
result += ' ';
result += detailMessage;
}
const lastChar = result[result.length - 1];
if (lastChar !== '.') {
result += '.';
}
return result;
}
constructor(
public readonly statusCode: number,
public readonly message: string,
public readonly details: string[] = []
) {
}
}
export function catchError(message: string): Observable<any> {
return this.catch((error: any | Response) => {
let result = new ErrorDto(500, message);
if (error instanceof Response) {
try {
const body = error.json();
if (error.status === 412) {
result = new ErrorDto(error.status, 'Failed to make the update. Another user has made a change. Please reload.');
} else if (error.status !== 500) {
result = new ErrorDto(error.status, body.message, body.details);
}
} catch (e) {
result = result;
}
}
return Observable.throw(result);
});
}

2
src/Squidex/app/framework/declarations.ts

@ -18,7 +18,7 @@ export * from './angular/file-drop.directive';
export * from './angular/focus-on-change.directive';
export * from './angular/focus-on-init.directive';
export * from './angular/geolocation-editor.component';
export * from './angular/http-utils';
export * from './angular/http-extensions-impl';
export * from './angular/image-source.directive';
export * from './angular/indeterminate-value.directive';
export * from './angular/json-editor.component';

6
src/Squidex/app/framework/module.ts

@ -6,9 +6,9 @@
*/
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import {
@ -68,7 +68,7 @@ import {
@NgModule({
imports: [
HttpModule,
HttpClientModule,
FormsModule,
CommonModule,
RouterModule,
@ -163,7 +163,7 @@ import {
TitleComponent,
ToggleComponent,
UserReportComponent,
HttpModule,
HttpClientModule,
FormsModule,
CommonModule,
RouterModule,

2
src/Squidex/app/shared/declarations-base.ts

@ -14,6 +14,8 @@ export * from './guards/resolve-published-schema.guard';
export * from './guards/resolve-schema.guard';
export * from './guards/resolve-user.guard';
export * from './interceptors/auth.interceptor';
export * from './services/app-contributors.service';
export * from './services/app-clients.service';
export * from './services/app-languages.service';

51
src/Squidex/app/shared/interceptors/auth.interceptor.ts

@ -0,0 +1,51 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Injectable} from '@angular/core';
import { Observable } from 'rxjs';
import { AuthService } from './../services/auth.service';
import { ApiUrlConfig } from 'framework';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private baseUrl: string;
constructor(apiUrlConfig: ApiUrlConfig,
private readonly authService: AuthService
) {
this.baseUrl = apiUrlConfig.buildUrl('');
}
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
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 next.handle(authReq)
.catch((error: HttpErrorResponse) => {
if (error.status === 404 && (!this.authService.user || this.authService.user.isExpired)) {
this.authService.logoutRedirect();
return Observable.empty<Response>();
} else if (error.status === 401 || error.status === 403) {
this.authService.logoutRedirect();
return Observable.empty<Response>();
}
return Observable.throw(error);
});
} else {
return next.handle(req);
}
}
}

11
src/Squidex/app/shared/module.ts

@ -5,9 +5,9 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { DndModule } from 'ng2-dnd';
import { ProgressHttpModule } from 'angular-progress-http';
import { SqxFrameworkModule } from 'framework';
@ -21,6 +21,7 @@ import {
AppMustExistGuard,
AssetComponent,
AssetsService,
AuthInterceptor,
AuthService,
ContentsService,
EventConsumersService,
@ -56,7 +57,6 @@ import {
@NgModule({
imports: [
ProgressHttpModule,
DndModule,
SqxFrameworkModule
],
@ -122,7 +122,12 @@ export class SqxSharedModule {
UserManagementService,
UsersProviderService,
UsersService,
WebhooksService
WebhooksService,
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
};
}

167
src/Squidex/app/shared/services/app-clients.service.spec.ts

@ -5,144 +5,124 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Http, Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { It, IMock, Mock, Times } from 'typemoq';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import {
AccessTokenDto,
ApiUrlConfig,
AppClientDto,
AppClientsService,
AuthService,
CreateAppClientDto,
UpdateAppClientDto,
Version
} from './../';
describe('AppClientsService', () => {
let authService: IMock<AuthService>;
let appClientsService: AppClientsService;
let version = new Version('1');
let http: IMock<Http>;
beforeEach(() => {
http = Mock.ofType(Http);
authService = Mock.ofType(AuthService);
appClientsService = new AppClientsService(authService.object, new ApiUrlConfig('http://service/p/'), http.object);
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
AppClientsService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
});
it('should make get request to get app clients', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/clients', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
id: 'client1',
name: 'Client 1',
secret: 'secret1'
},
{
id: 'client2',
name: 'Client 2',
secret: 'secret2'
}
]
})
)
))
.verifiable(Times.once());
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));
it('should make get request to get app clients',
inject([AppClientsService, HttpTestingController], (appClientsService: AppClientsService, httpMock: HttpTestingController) => {
let clients: AppClientDto[] | null = null;
appClientsService.getClients('my-app', version).subscribe(result => {
clients = result;
}).unsubscribe();
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/clients');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush([
{
id: 'client1',
name: 'Client 1',
secret: 'secret1'
},
{
id: 'client2',
name: 'Client 2',
secret: 'secret2'
}
]);
expect(clients).toEqual(
[
new AppClientDto('client1', 'Client 1', 'secret1'),
new AppClientDto('client2', 'Client 2', 'secret2')
]);
}));
authService.verifyAll();
});
it('should make post request to create client',
inject([AppClientsService, HttpTestingController], (appClientsService: AppClientsService, httpMock: HttpTestingController) => {
it('should make post request to create client', () => {
const dto = new CreateAppClientDto('client1');
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/clients', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'client1',
name: 'Client 1',
secret: 'secret1'
}
})
)
))
.verifiable(Times.once());
let client: AppClientDto | null = null;
appClientsService.postClient('my-app', dto, version).subscribe(result => {
client = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/clients');
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush({ id: 'client1', name: 'Client 1', secret: 'secret1' });
expect(client).toEqual(
new AppClientDto('client1', 'Client 1', 'secret1'));
}));
authService.verifyAll();
});
it('should make put request to rename client',
inject([AppClientsService, HttpTestingController], (appClientsService: AppClientsService, httpMock: HttpTestingController) => {
it('should make put request to rename client', () => {
const dto = new UpdateAppClientDto('Client 1 New');
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/clients/client1', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
appClientsService.updateClient('my-app', 'client1', dto, version).subscribe();
appClientsService.updateClient('my-app', 'client1', dto, version);
const req = httpMock.expectOne('http://service/p/api/apps/my-app/clients/client1');
authService.verifyAll();
});
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toEqual('1');
it('should make delete request to remove client', () => {
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/clients/client1', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
req.flush({});
}));
appClientsService.deleteClient('my-app', 'client1', version);
it('should make delete request to remove client',
inject([AppClientsService, HttpTestingController], (appClientsService: AppClientsService, httpMock: HttpTestingController) => {
authService.verifyAll();
});
appClientsService.deleteClient('my-app', 'client1', version).subscribe();
it('should make form request to create token', () => {
const body = 'grant_type=client_credentials&scope=squidex-api&client_id=my-app:myClientId&client_secret=mySecret';
const req = httpMock.expectOne('http://service/p/api/apps/my-app/clients/client1');
http.setup(x => x.post('http://service/p/identity-server/connect/token', body, It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
access_token: 'token1', token_type: 'type1'
}
})
)
))
.verifiable(Times.once());
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush({});
}));
it('should make form request to create token',
inject([AppClientsService, HttpTestingController], (appClientsService: AppClientsService, httpMock: HttpTestingController) => {
let accessTokenDto: AccessTokenDto | null = null;
@ -150,9 +130,16 @@ describe('AppClientsService', () => {
accessTokenDto = result;
});
const body = 'grant_type=client_credentials&scope=squidex-api&client_id=my-app:myClientId&client_secret=mySecret';
const req = httpMock.expectOne('http://service/p/identity-server/connect/token');
expect(req.request.method).toEqual('POST');
expect(req.request.body).toEqual(body);
req.flush({ access_token: 'token1', token_type: 'type1' });
expect(accessTokenDto).toEqual(
new AccessTokenDto('token1', 'type1'));
http.verifyAll();
});
}));
});

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

@ -5,14 +5,17 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import 'framework/angular/http-extensions';
import { ApiUrlConfig, Version } from 'framework';
import { AuthService } from './auth.service';
import {
ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppClientDto {
constructor(
@ -48,17 +51,15 @@ export class AccessTokenDto {
@Injectable()
export class AppClientsService {
constructor(
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig,
private readonly http: Http
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
public getClients(appName: string, version?: Version): Observable<AppClientDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return this.authService.authGet(url, version)
.map(response => response.json())
return HTTP.getVersioned(this.http, url, version)
.map(response => {
const items: any[] = response;
@ -69,51 +70,51 @@ export class AppClientsService {
item.secret);
});
})
.catchError('Failed to load clients. Please reload.');
.pretifyError('Failed to load clients. Please reload.');
}
public postClient(appName: string, dto: CreateAppClientDto, version?: Version): Observable<AppClientDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return this.authService.authPost(url, dto, version)
.map(response => response.json())
return HTTP.postVersioned(this.http, url, dto, version)
.map(response => {
return new AppClientDto(
response.id,
response.name,
response.secret);
})
.catchError('Failed to add client. Please reload.');
.pretifyError('Failed to add client. Please reload.');
}
public updateClient(appName: string, id: string, dto: UpdateAppClientDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return this.authService.authPut(url, dto, version)
.catchError('Failed to revoke client. Please reload.');
return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to revoke client. Please reload.');
}
public deleteClient(appName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return this.authService.authDelete(url, version)
.catchError('Failed to revoke client. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to revoke client. Please reload.');
}
public createToken(appName: string, client: AppClientDto): Observable<AccessTokenDto> {
const options = new RequestOptions({
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
const options = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded', 'NoAuth': 'true'
})
});
};
const body = `grant_type=client_credentials&scope=squidex-api&client_id=${appName}:${client.id}&client_secret=${client.secret}`;
const url = this.apiUrl.buildUrl('identity-server/connect/token');
return this.http.post(url, body, options)
.map(response => response.json())
.map(response => new AccessTokenDto(response.access_token, response.token_type))
.catchError('Failed to create token. Please retry.');
.map((response: any) => {
return new AccessTokenDto(response.access_token, response.token_type);
})
.pretifyError('Failed to create token. Please retry.');
}
}

112
src/Squidex/app/shared/services/app-contributors.service.spec.ts

@ -5,94 +5,96 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import {
ApiUrlConfig,
AppContributorDto,
AppContributorsDto,
AppContributorsService,
AuthService,
Version
} from './../';
describe('AppContributorsService', () => {
let authService: IMock<AuthService>;
let appContributorsService: AppContributorsService;
let version = new Version('1');
beforeEach(() => {
authService = Mock.ofType(AuthService);
appContributorsService = new AppContributorsService(authService.object, new ApiUrlConfig('http://service/p/'));
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
AppContributorsService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
});
it('should make get request to get app contributors', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/contributors', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
contributors: [
{
contributorId: '123',
permission: 'Owner'
},
{
contributorId: '456',
permission: 'Owner'
}
],
maxContributors: 100
}
})
)
))
.verifiable(Times.once());
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));
it('should make get request to get app contributors',
inject([AppContributorsService, HttpTestingController], (appContributorsService: AppContributorsService, httpMock: HttpTestingController) => {
let contributors: AppContributorsDto | null = null;
appContributorsService.getContributors('my-app', version).subscribe(result => {
contributors = result;
}).unsubscribe();
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/contributors');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush({
contributors: [
{
contributorId: '123',
permission: 'Owner'
},
{
contributorId: '456',
permission: 'Owner'
}
],
maxContributors: 100
});
expect(contributors).toEqual(
new AppContributorsDto([
new AppContributorDto('123', 'Owner'),
new AppContributorDto('456', 'Owner')
], 100));
}));
authService.verifyAll();
});
it('should make post request to assign contributor',
inject([AppContributorsService, HttpTestingController], (appContributorsService: AppContributorsService, httpMock: HttpTestingController) => {
it('should make post request to assign contributor', () => {
const dto = new AppContributorDto('123', 'Owner');
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/contributors', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
appContributorsService.postContributor('my-app', dto, version).subscribe();
appContributorsService.postContributor('my-app', dto, version);
const req = httpMock.expectOne('http://service/p/api/apps/my-app/contributors');
authService.verifyAll();
});
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toEqual('1');
it('should make delete request to remove contributor', () => {
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/contributors/123', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
req.flush({});
}));
appContributorsService.deleteContributor('my-app', '123', version);
it('should make delete request to remove contributor',
inject([AppContributorsService, HttpTestingController], (appContributorsService: AppContributorsService, httpMock: HttpTestingController) => {
authService.verifyAll();
});
appContributorsService.deleteContributor('my-app', '123', version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/contributors/123');
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush({});
}));
});

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

@ -5,13 +5,17 @@
* Copyright (c) Sebastian Stehle. 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, Version } from 'framework';
import { AuthService } from './auth.service';
import {
ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppContributorDto {
constructor(
@ -32,7 +36,7 @@ export class AppContributorsDto {
@Injectable()
export class AppContributorsService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -40,8 +44,7 @@ export class AppContributorsService {
public getContributors(appName: string, version?: Version): Observable<AppContributorsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return this.authService.authGet(url, version)
.map(response => response.json())
return HTTP.getVersioned(this.http, url, version)
.map(response => {
const items: any[] = response.contributors;
@ -53,20 +56,20 @@ export class AppContributorsService {
}),
response.maxContributors);
})
.catchError('Failed to load contributors. Please reload.');
.pretifyError('Failed to load contributors. Please reload.');
}
public postContributor(appName: string, dto: AppContributorDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return this.authService.authPost(url, dto, version)
.catchError('Failed to add contributors. Please reload.');
return HTTP.postVersioned(this.http, url, dto, version)
.pretifyError('Failed to add contributors. Please reload.');
}
public deleteContributor(appName: string, contributorId: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`);
return this.authService.authDelete(url, version)
.catchError('Failed to delete contributors. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to delete contributors. Please reload.');
}
}

140
src/Squidex/app/shared/services/app-languages.service.spec.ts

@ -5,123 +5,121 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import {
AddAppLanguageDto,
ApiUrlConfig,
AppLanguageDto,
AppLanguagesService,
AuthService,
UpdateAppLanguageDto,
Version
} from './../';
describe('AppLanguagesService', () => {
let authService: IMock<AuthService>;
let appLanguagesService: AppLanguagesService;
let version = new Version('1');
beforeEach(() => {
authService = Mock.ofType(AuthService);
appLanguagesService = new AppLanguagesService(authService.object, new ApiUrlConfig('http://service/p/'));
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
AppLanguagesService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
});
it('should make get request to get app languages', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/languages', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
iso2Code: 'en',
englishName: 'English',
isMaster: true,
isOptional: true,
fallback: ['de', 'en']
},
{
iso2Code: 'it',
englishName: 'Italian'
}
]
})
)
))
.verifiable(Times.once());
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));
it('should make get request to get app languages',
inject([AppLanguagesService, HttpTestingController], (appLanguagesService: AppLanguagesService, httpMock: HttpTestingController) => {
let languages: AppLanguageDto[] | null = null;
appLanguagesService.getLanguages('my-app', version).subscribe(result => {
languages = result;
}).unsubscribe();
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush([
{
iso2Code: 'en',
englishName: 'English',
isMaster: true,
isOptional: true,
fallback: ['de', 'en']
},
{
iso2Code: 'it',
isMaster: false,
isOptional: false,
englishName: 'Italian'
}
]);
expect(languages).toEqual(
[
new AppLanguageDto('en', 'English', true, true, ['de', 'en']),
new AppLanguageDto('it', 'Italian', false, false, [])
]);
}));
authService.verifyAll();
});
it('should make post request to add language',
inject([AppLanguagesService, HttpTestingController], (appLanguagesService: AppLanguagesService, httpMock: HttpTestingController) => {
it('should make post request to add language', () => {
const dto = new AddAppLanguageDto('de');
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/languages', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
iso2Code: 'de',
englishName: 'German'
}
})
)
))
.verifiable(Times.once());
let language: AppLanguageDto | null = null;
appLanguagesService.postLanguages('my-app', dto, version).subscribe(result => {
language = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages');
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush({ iso2Code: 'de', englishName: 'German' });
expect(language).toEqual(
new AppLanguageDto('de', 'German', false, false, []));
}));
authService.verifyAll();
});
it('should make put request to make master language',
inject([AppLanguagesService, HttpTestingController], (appLanguagesService: AppLanguagesService, httpMock: HttpTestingController) => {
it('should make put request to make master language', () => {
const dto = new UpdateAppLanguageDto(true, true, []);
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/languages/de', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
appLanguagesService.updateLanguage('my-app', 'de', dto, version).subscribe();
appLanguagesService.updateLanguage('my-app', 'de', dto, version);
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages/de');
authService.verifyAll();
});
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toEqual('1');
it('should make delete request to remove language', () => {
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/languages/de', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
req.flush({});
}));
appLanguagesService.deleteLanguage('my-app', 'de', version);
it('should make delete request to remove language',
inject([AppLanguagesService, HttpTestingController], (appLanguagesService: AppLanguagesService, httpMock: HttpTestingController) => {
authService.verifyAll();
});
appLanguagesService.deleteLanguage('my-app', 'de', version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages/de');
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toEqual('1');
req.flush({});
}));
});

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

@ -5,13 +5,17 @@
* Copyright (c) Sebastian Stehle. 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, Version } from 'framework';
import { AuthService } from './auth.service';
import {
ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppLanguageDto {
constructor(
@ -43,7 +47,7 @@ export class UpdateAppLanguageDto {
@Injectable()
export class AppLanguagesService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -51,8 +55,7 @@ export class AppLanguagesService {
public getLanguages(appName: string, version?: Version): Observable<AppLanguageDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return this.authService.authGet(url, version)
.map(response => response.json())
return HTTP.getVersioned(this.http, url, version)
.map(response => {
const items: any[] = response;
@ -65,14 +68,13 @@ export class AppLanguagesService {
item.fallback || []);
});
})
.catchError('Failed to load languages. Please reload.');
.pretifyError('Failed to load languages. Please reload.');
}
public postLanguages(appName: string, dto: AddAppLanguageDto, version?: Version): Observable<AppLanguageDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return this.authService.authPost(url, dto, version)
.map(response => response.json())
return HTTP.postVersioned(this.http, url, dto, version)
.map(response => {
return new AppLanguageDto(
response.iso2Code,
@ -81,20 +83,20 @@ export class AppLanguagesService {
response.isOptional === true,
response.fallback || []);
})
.catchError('Failed to add language. Please reload.');
.pretifyError('Failed to add language. Please reload.');
}
public updateLanguage(appName: string, languageCode: string, dto: UpdateAppLanguageDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return this.authService.authPut(url, dto, version)
.catchError('Failed to change language. Please reload.');
return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to change language. Please reload.');
}
public deleteLanguage(appName: string, languageCode: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return this.authService.authDelete(url, version)
.catchError('Failed to add language. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to add language. Please reload.');
}
}

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

@ -5,92 +5,90 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import {
ApiUrlConfig,
AppDto,
AppsService,
AuthService,
CreateAppDto,
DateTime,
EntityCreatedDto
} from './../';
describe('AppsService', () => {
let authService: IMock<AuthService>;
let appsService: AppsService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
appsService = new AppsService(authService.object, new ApiUrlConfig('http://service/p/'));
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
AppsService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
});
it('should make get request to get apps', () => {
authService.setup(x => x.authGet('http://service/p/api/apps'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
id: '123',
name: 'name1',
permission: 'Owner',
created: '2016-01-01',
lastModified: '2016-02-02'
},
{
id: '456',
name: 'name2',
permission: 'Owner',
created: '2017-01-01',
lastModified: '2017-02-02'
}
]
})
)
))
.verifiable(Times.once());
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));
it('should make get request to get apps',
inject([AppsService, HttpTestingController], (appsService: AppsService, httpMock: HttpTestingController) => {
let apps: AppDto[] | null = null;
appsService.getApps().subscribe(result => {
apps = result;
}).unsubscribe();
});
const req = httpMock.expectOne('http://service/p/api/apps');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeUndefined();
req.flush([
{
id: '123',
name: 'name1',
permission: 'Owner',
created: '2016-01-01',
lastModified: '2016-02-02'
},
{
id: '456',
name: 'name2',
permission: 'Owner',
created: '2017-01-01',
lastModified: '2017-02-02'
}
]);
expect(apps).toEqual([
new AppDto('123', 'name1', 'Owner', DateTime.parseISO('2016-01-01'), DateTime.parseISO('2016-02-02')),
new AppDto('456', 'name2', 'Owner', DateTime.parseISO('2017-01-01'), DateTime.parseISO('2017-02-02'))
]);
}));
authService.verifyAll();
});
it('should make post request to create app',
inject([AppsService, HttpTestingController], (appsService: AppsService, httpMock: HttpTestingController) => {
it('should make post request to create app', () => {
const dto = new CreateAppDto('new-app');
authService.setup(x => x.authPost('http://service/p/api/apps', dto))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: '123'
}
})
)
))
.verifiable(Times.once());
let created: EntityCreatedDto | null = null;
appsService.postApp(dto).subscribe(result => {
created = result;
}).unsubscribe();
});
expect(created).toEqual(new EntityCreatedDto('123'));
const req = httpMock.expectOne('http://service/p/api/apps');
authService.verifyAll();
});
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toBeUndefined();
req.flush({ id: '123' });
expect(created).toEqual(new EntityCreatedDto('123'));
}));
});

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

@ -5,6 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@ -13,11 +14,10 @@ import 'framework/angular/http-extensions';
import {
ApiUrlConfig,
DateTime,
HTTP,
EntityCreatedDto
} from 'framework';
import { AuthService } from './auth.service';
export class AppDto {
constructor(
public readonly id: string,
@ -39,7 +39,7 @@ export class CreateAppDto {
@Injectable()
export class AppsService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -47,8 +47,7 @@ export class AppsService {
public getApps(): Observable<AppDto[]> {
const url = this.apiUrl.buildUrl('/api/apps');
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -61,17 +60,16 @@ export class AppsService {
DateTime.parseISO(item.lastModified));
});
})
.catchError('Failed to load apps. Please reload.');
.pretifyError('Failed to load apps. Please reload.');
}
public postApp(dto: CreateAppDto): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl('api/apps');
return this.authService.authPost(url, dto)
.map(response => response.json())
return HTTP.postVersioned(this.http, url, dto)
.map(response => {
return new EntityCreatedDto(response.id);
})
.catchError('Failed to create app. Please reload.');
.pretifyError('Failed to create app. Please reload.');
}
}

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

@ -1,354 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { ProgressHttp } from 'angular-progress-http';
import { IMock, It, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AssetsDto,
AssetDto,
AssetCreatedDto,
AssetReplacedDto,
AssetsService,
AuthService,
DateTime,
Profile,
UpdateAssetDto,
Version
} from './../';
describe('AssetsService', () => {
let authService: IMock<AuthService>;
let assetsService: AssetsService;
let progressHttp: IMock<ProgressHttp>;
let version = new Version('1');
beforeEach(() => {
const factory = {
create: () => {
return <any>null;
}
};
progressHttp = Mock.ofInstance(new ProgressHttp(null!, null!, factory, null!));
progressHttp.setup(x => x.withUploadProgressListener(It.isAny())).returns(() => <any> progressHttp.object);
authService = Mock.ofType(AuthService);
authService.setup(x => x.user).returns(() => new Profile(<any>{}));
assetsService = new AssetsService(authService.object, new ApiUrlConfig('http://service/p/'), progressHttp.object);
});
it('should make get request to get assets', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets?take=17&skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: [
{
id: 'id1',
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png',
fileSize: 1024,
fileVersion: 2000,
mimeType: 'text/plain',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
version: 11
},
{
id: 'id2',
created: '2016-10-12T10:10',
createdBy: 'Created2',
lastModified: '2017-10-12T10:10',
lastModifiedBy: 'LastModifiedBy2',
fileName: 'my-asset2.png',
fileSize: 1024,
fileVersion: 2000,
mimeType: 'text/plain',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
version: 22
}
]
}
})
)
))
.verifiable(Times.once());
let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13).subscribe(result => {
assets = result;
}).unsubscribe();
expect(assets).toEqual(
new AssetsDto(10, [
new AssetDto(
'id1', 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png',
1024,
2000,
'text/plain',
true,
1024,
2048,
new Version('11')),
new AssetDto('id2', 'Created2', 'LastModifiedBy2',
DateTime.parseISO_UTC('2016-10-12T10:10'),
DateTime.parseISO_UTC('2017-10-12T10:10'),
'my-asset2.png',
1024,
2000,
'text/plain',
true,
1024,
2048,
new Version('22'))
]));
authService.verifyAll();
});
it('should make get request to get asset', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets/123'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'id1',
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png',
fileSize: 1024,
fileVersion: 2000,
mimeType: 'text/plain',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
version: 11
}
})
)
))
.verifiable(Times.once());
let assets: AssetDto | null = null;
assetsService.getAsset('my-app', '123').subscribe(result => {
assets = result;
}).unsubscribe();
expect(assets).toEqual(
new AssetDto(
'id1', 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png',
1024,
2000,
'text/plain',
true,
1024,
2048,
new Version('11')));
authService.verifyAll();
});
it('should append query to find by name', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets?query=my-query&take=17&skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: []
}
})
)
))
.verifiable(Times.once());
let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13, 'my-query').subscribe(result => {
assets = result;
}).unsubscribe();
authService.verifyAll();
});
it('should append mime types to find by types', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets?mimeTypes=text/plain,image/png&take=17&skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: []
}
})
)
))
.verifiable(Times.once());
let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13, undefined, ['text/plain', 'image/png']).subscribe(result => {
assets = result;
}).unsubscribe();
authService.verifyAll();
});
it('should append mime types to find by ids', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets?ids=12,23&take=17&skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: []
}
})
)
))
.verifiable(Times.once());
let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13, undefined, undefined, ['12', '23']).subscribe(result => {
assets = result;
}).unsubscribe();
authService.verifyAll();
});
it('should make post request to create asset', () => {
progressHttp.setup(x => x.post('http://service/p/api/apps/my-app/assets', It.isAny(), It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'id1',
fileName: 'my-asset1.png',
fileSize: 1024,
fileVersion: 2,
mimeType: 'text/plain',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
version: 11
}
})
)
))
.verifiable(Times.once());
let asset: AssetCreatedDto | null = null;
assetsService.uploadFile('my-app', null!).subscribe(result => {
asset = <AssetCreatedDto>result;
});
expect(asset).toEqual(
new AssetCreatedDto(
'id1',
'my-asset1.png',
1024, 2,
'text/plain',
true,
1024,
2048,
new Version('11')));
authService.verifyAll();
});
it('should make put request to replace asset content', () => {
progressHttp.setup(x => x.put('http://service/p/api/apps/my-app/assets/123/content', It.isAny(), It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
fileSize: 1024,
fileVersion: 2,
mimeType: 'text/plain',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
version: 11
}
})
)
))
.verifiable(Times.once());
let asset: AssetReplacedDto | null = null;
assetsService.replaceFile('my-app', '123', null!, version).subscribe(result => {
asset = <AssetReplacedDto>result;
});
expect(asset).toEqual(
new AssetReplacedDto(
1024, 2,
'text/plain',
true,
1024,
2048,
new Version('11')));
authService.verifyAll();
});
it('should make put request to update asset', () => {
const dto = new UpdateAssetDto('My-Asset.pdf');
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/assets/123', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
assetsService.putAsset('my-app', '123', dto, version);
authService.verifyAll();
});
it('should make delete request to delete asset', () => {
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/assets/123', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
assetsService.deleteAsset('my-app', '123', version);
authService.verifyAll();
});
});

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

@ -5,19 +5,17 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient, HttpEventType, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Headers } from '@angular/http';
import { Observable } from 'rxjs';
import { ProgressHttp } from 'angular-progress-http';
import {
ApiUrlConfig,
DateTime,
HTTP,
Version
} from 'framework';
import { AuthService } from './auth.service';
export class AssetsDto {
constructor(
public readonly total: number,
@ -83,9 +81,8 @@ export class AssetReplacedDto {
@Injectable()
export class AssetsService {
constructor(
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig,
private readonly http: ProgressHttp
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -111,8 +108,7 @@ export class AssetsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?${fullQuery}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response.items;
@ -133,38 +129,40 @@ export class AssetsService {
new Version(item.version.toString()));
}));
})
.catchError('Failed to load assets. Please reload.');
.pretifyError('Failed to load assets. Please reload.');
}
public uploadFile(appName: string, file: File): Observable<number | AssetCreatedDto> {
return new Observable<number | AssetCreatedDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets`);
const content = new FormData();
const headers = new Headers({
'Authorization': `${this.authService.user!.user.token_type} ${this.authService.user!.user.access_token}`
const req = new HttpRequest('POST', url, getFormData(file), {
reportProgress: true
});
content.append('file', file);
this.http.withUploadProgressListener(progress => subscriber.next(progress.percentage))
.post(url, content, { headers })
.map(response => response.json())
.map(response => {
return new AssetCreatedDto(
response.id,
response.fileName,
response.fileSize,
response.fileVersion,
response.mimeType,
response.isImage,
response.pixelWidth,
response.pixelHeight,
new Version(response.version.toString()));
})
.catchError('Failed to upload asset. Please reload.')
.subscribe(value => {
subscriber.next(value);
this.http.request(req)
.pretifyError('Failed to upload asset. Please reload.')
.subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
const percentDone = Math.round(100 * event.loaded / event.total);
subscriber.next(percentDone);
} else if (event instanceof HttpResponse) {
const response = event.body;
const dto = new AssetCreatedDto(
response.id,
response.fileName,
response.fileSize,
response.fileVersion,
response.mimeType,
response.isImage,
response.pixelWidth,
response.pixelHeight,
new Version(response.version.toString()));
subscriber.next(dto);
}
}, err => {
subscriber.error(err);
}, () => {
@ -176,7 +174,7 @@ export class AssetsService {
public getAsset(appName: string, id: string, version?: Version): Observable<AssetDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return this.authService.authGet(url)
return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => {
return new AssetDto(
@ -194,40 +192,41 @@ export class AssetsService {
response.pixelHeight,
new Version(response.version.toString()));
})
.catchError('Failed to load assets. Please reload.');
.pretifyError('Failed to load assets. Please reload.');
}
public replaceFile(appName: string, id: string, file: File, version?: Version): Observable<number | AssetReplacedDto> {
return new Observable<number | AssetReplacedDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}/content`);
const content = new FormData();
const headers = new Headers({
'Authorization': `${this.authService.user!.user.token_type} ${this.authService.user!.user.access_token}`
const req = new HttpRequest('POST', url, file, {
headers: new HttpHeaders({
'If-Match': version.value
}),
reportProgress: true
});
if (version && version.value.length > 0) {
headers.append('If-Match', version.value);
}
content.append('file', file);
this.http.withUploadProgressListener(progress => subscriber.next(progress.percentage))
.put(url, content, { headers })
.map(response => response.json())
.map(response => {
return new AssetReplacedDto(
response.fileSize,
response.fileVersion,
response.mimeType,
response.isImage,
response.pixelWidth,
response.pixelHeight,
new Version(response.version.toString()));
})
.catchError('Failed to replace asset. Please reload.')
.subscribe(value => {
subscriber.next(value);
this.http.request(req)
.pretifyError('Failed to replace asset. Please reload.')
.subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
const percentDone = Math.round(100 * event.loaded / event.total);
subscriber.next(percentDone);
} else if (event instanceof HttpResponse) {
const response = event.body;
const dto = new AssetReplacedDto(
response.fileSize,
response.fileVersion,
response.mimeType,
response.isImage,
response.pixelWidth,
response.pixelHeight,
new Version(response.version.toString()));
subscriber.next(dto);
}
}, err => {
subscriber.error(err);
}, () => {
@ -239,14 +238,22 @@ export class AssetsService {
public putAsset(appName: string, id: string, dto: UpdateAssetDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return this.authService.authPut(url, dto, version)
.catchError('Failed to delete asset. Please reload.');
return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to delete asset. Please reload.');
}
public deleteAsset(appName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return this.authService.authDelete(url, version)
.catchError('Failed to delete asset. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to delete asset. Please reload.');
}
}
function getFormData(file: File) {
const formData = new FormData();
formData.append('file', file);
return formData;
}

89
src/Squidex/app/shared/services/auth.service.ts

@ -6,7 +6,6 @@
*/
import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions, Response } from '@angular/http';
import { Observable, Subject } from 'rxjs';
import {
@ -15,7 +14,7 @@ import {
UserManager
} from 'oidc-client';
import { ApiUrlConfig, Version } from 'framework';
import { ApiUrlConfig } from 'framework';
export class Profile {
public get id(): string {
@ -34,6 +33,14 @@ export class Profile {
return this.user.profile['role'] && this.user.profile['role'].toUpperCase() === 'ADMINISTRATOR';
}
public get isExpired(): boolean {
return this.user.expired;
}
public get authToken(): string {
return `${this.user.token_type} ${this.user.access_token}`;
}
public get token(): string {
return `subject:${this.id}`;
}
@ -65,9 +72,7 @@ export class AuthService {
return this.isAuthenticatedChangedPublished$;
}
constructor(apiUrl: ApiUrlConfig,
private readonly http: Http
) {
constructor(apiUrl: ApiUrlConfig) {
if (!apiUrl) {
return;
}
@ -166,78 +171,4 @@ export class AuthService {
return resultPromise;
}
public authGet(url: string, version?: Version, options?: RequestOptions): Observable<Response> {
options = this.setRequestOptions(options, version);
return this.checkResponse(this.http.get(url, options), version);
}
public authPut(url: string, data: any, version?: Version, options?: RequestOptions): Observable<Response> {
options = this.setRequestOptions(options, version);
return this.checkResponse(this.http.put(url, data, options), version);
}
public authDelete(url: string, version?: Version, options?: RequestOptions): Observable<Response> {
options = this.setRequestOptions(options, version);
return this.checkResponse(this.http.delete(url, options), version);
}
public authPost(url: string, data: any, version?: Version, options?: RequestOptions): Observable<Response> {
options = this.setRequestOptions(options, version);
return this.checkResponse(this.http.post(url, data, options), version);
}
private checkResponse(responseStream: Observable<Response>, version?: Version) {
return responseStream
.do((response: Response) => {
if (version && response.status.toString().indexOf('2') === 0 && response.headers) {
const etag = response.headers.get('etag');
if (etag) {
version.update(etag);
}
}
})
.catch((error: Response) => {
if (error.status === 404 && (!this.user || this.user.user.expired)) {
this.logoutRedirect();
return Observable.empty<Response>();
} else if (error.status === 401 || error.status === 403) {
this.logoutRedirect();
return Observable.empty<Response>();
}
return Observable.throw(error);
});
}
private setRequestOptions(options?: RequestOptions, version?: Version) {
if (!options) {
options = new RequestOptions();
}
if (!options.headers) {
options.headers = new Headers();
options.headers.append('Content-Type', 'application/json');
}
options.headers.append('Accept-Language', '*');
if (version && version.value.length > 0) {
options.headers.append('If-Match', version.value);
}
if (this.currentUser && this.currentUser.user) {
options.headers.append('Authorization', `${this.currentUser.user.token_type} ${this.currentUser.user.access_token}`);
}
options.headers.append('Pragma', 'no-cache');
return options;
}
}

290
src/Squidex/app/shared/services/contents.service.spec.ts

@ -1,290 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AuthService,
ContentDto,
ContentsDto,
ContentsService,
DateTime,
Version
} from './../';
describe('ContentsService', () => {
let authService: IMock<AuthService>;
let contentsService: ContentsService;
let version = new Version('1');
beforeEach(() => {
authService = Mock.ofType(AuthService);
contentsService = new ContentsService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get contents', () => {
authService.setup(x => x.authGet('http://service/p/api/content/my-app/my-schema?nonPublished=true&hidden=true&$top=17&$skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: [
{
id: 'id1',
isPublished: true,
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
version: 11,
data: {}
},
{
id: 'id2',
isPublished: true,
created: '2016-10-12T10:10',
createdBy: 'Created2',
lastModified: '2017-10-12T10:10',
lastModifiedBy: 'LastModifiedBy2',
version: 22,
data: {}
}
]
}
})
)
))
.verifiable(Times.once());
let contents: ContentsDto | null = null;
contentsService.getContents('my-app', 'my-schema', 17, 13).subscribe(result => {
contents = result;
}).unsubscribe();
expect(contents).toEqual(
new ContentsDto(10, [
new ContentDto('id1', true, 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
{},
new Version('11')),
new ContentDto('id2', true, 'Created2', 'LastModifiedBy2',
DateTime.parseISO_UTC('2016-10-12T10:10'),
DateTime.parseISO_UTC('2017-10-12T10:10'),
{},
new Version('22'))
]));
authService.verifyAll();
});
it('should append query to get request as search', () => {
authService.setup(x => x.authGet('http://service/p/api/content/my-app/my-schema?nonPublished=true&hidden=true&$search="my-query"&$top=17&$skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: []
}
})
)
))
.verifiable(Times.once());
let contents: ContentsDto | null = null;
contentsService.getContents('my-app', 'my-schema', 17, 13, 'my-query').subscribe(result => {
contents = result;
}).unsubscribe();
authService.verifyAll();
});
it('should append ids to get request with ids', () => {
authService.setup(x => x.authGet('http://service/p/api/content/my-app/my-schema?nonPublished=true&hidden=true&$top=17&$skip=13&ids=id1,id2'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: []
}
})
)
))
.verifiable(Times.once());
let contents: ContentsDto | null = null;
contentsService.getContents('my-app', 'my-schema', 17, 13, null, ['id1', 'id2']).subscribe(result => {
contents = result;
}).unsubscribe();
authService.verifyAll();
});
it('should append query to get request as plain query string', () => {
authService.setup(x => x.authGet('http://service/p/api/content/my-app/my-schema?nonPublished=true&hidden=true&$filter=my-filter&$top=17&$skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: []
}
})
)
))
.verifiable(Times.once());
let contents: ContentsDto | null = null;
contentsService.getContents('my-app', 'my-schema', 17, 13, '$filter=my-filter').subscribe(result => {
contents = result;
}).unsubscribe();
authService.verifyAll();
});
it('should make get request to get content', () => {
authService.setup(x => x.authGet('http://service/p/api/content/my-app/my-schema/content1?hidden=true', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'id1',
isPublished: true,
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
version: 11,
data: {}
}
})
)
))
.verifiable(Times.once());
let content: ContentDto | null = null;
contentsService.getContent('my-app', 'my-schema', 'content1', version).subscribe(result => {
content = result;
}).unsubscribe();
expect(content).toEqual(
new ContentDto('id1', true, 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
{},
new Version('11')));
authService.verifyAll();
});
it('should make post request to create content', () => {
const dto = {};
authService.setup(x => x.authPost('http://service/p/api/content/my-app/my-schema?publish=true', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'id1',
isPublished: true,
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
version: 11,
data: {}
}
})
)
))
.verifiable(Times.once());
let content: ContentDto | null = null;
contentsService.postContent('my-app', 'my-schema', dto, true, version).subscribe(result => {
content = result;
});
expect(content).toEqual(
new ContentDto('id1', true, 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
{},
new Version('11')));
authService.verifyAll();
});
it('should make put request to update content', () => {
const dto = {};
authService.setup(x => x.authPut('http://service/p/api/content/my-app/my-schema/content1', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
contentsService.putContent('my-app', 'my-schema', 'content1', dto, version);
authService.verifyAll();
});
it('should make put request to publish content', () => {
authService.setup(x => x.authPut('http://service/p/api/content/my-app/my-schema/content1/publish', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
contentsService.publishContent('my-app', 'my-schema', 'content1', version);
authService.verifyAll();
});
it('should make put request to unpublish content', () => {
authService.setup(x => x.authPut('http://service/p/api/content/my-app/my-schema/content1/unpublish', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
contentsService.unpublishContent('my-app', 'my-schema', 'content1', version);
authService.verifyAll();
});
it('should make delete request to delete content', () => {
authService.setup(x => x.authDelete('http://service/p/api/content/my-app/my-schema/content1', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
contentsService.deleteContent('my-app', 'my-schema', 'content1', version);
authService.verifyAll();
});
});

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

@ -5,6 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@ -13,11 +14,10 @@ import 'framework/angular/http-extensions';
import {
ApiUrlConfig,
DateTime,
HTTP,
Version
} from 'framework';
import { AuthService } from './auth.service';
export class ContentsDto {
constructor(
public readonly total: number,
@ -43,7 +43,7 @@ export class ContentDto {
@Injectable()
export class ContentsService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -75,8 +75,7 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?nonPublished=true&hidden=true${fullQuery}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response.items;
@ -92,14 +91,13 @@ export class ContentsService {
new Version(item.version.toString()));
}));
})
.catchError('Failed to load contents. Please reload.');
.pretifyError('Failed to load contents. Please reload.');
}
public getContent(appName: string, schemaName: string, id: string, version?: Version): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}?hidden=true`);
return this.authService.authGet(url, version)
.map(response => response.json())
return HTTP.getVersioned(this.http, url, version)
.map(response => {
return new ContentDto(
response.id,
@ -111,14 +109,13 @@ export class ContentsService {
response.data,
new Version(response.version.toString()));
})
.catchError('Failed to load content. Please reload.');
.pretifyError('Failed to load content. Please reload.');
}
public postContent(appName: string, schemaName: string, dto: any, publish: boolean, version?: Version): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`);
return this.authService.authPost(url, dto, version)
.map(response => response.json())
return HTTP.postVersioned(this.http, url, dto, version)
.map(response => {
return new ContentDto(
response.id,
@ -130,34 +127,34 @@ export class ContentsService {
response.data,
new Version(response.version.toString()));
})
.catchError('Failed to create content. Please reload.');
.pretifyError('Failed to create content. Please reload.');
}
public putContent(appName: string, schemaName: string, id: string, dto: any, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return this.authService.authPut(url, dto, version)
.catchError('Failed to update content. Please reload.');
return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to update content. Please reload.');
}
public publishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/publish`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to publish content. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to publish content. Please reload.');
}
public unpublishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/unpublish`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to unpublish content. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to unpublish content. Please reload.');
}
public deleteContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return this.authService.authDelete(url, version)
.catchError('Failed to delete content. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to delete content. Please reload.');
}
}

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

@ -1,109 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AuthService,
EventConsumerDto,
EventConsumersService
} from './../';
describe('EventConsumersService', () => {
let authService: IMock<AuthService>;
let eventConsumersService: EventConsumersService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
eventConsumersService = new EventConsumersService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get event consumers', () => {
authService.setup(x => x.authGet('http://service/p/api/event-consumers'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
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'
}
]
})
)
))
.verifiable(Times.once());
let eventConsumers: EventConsumerDto[] | null = null;
eventConsumersService.getEventConsumers().subscribe(result => {
eventConsumers = result;
}).unsubscribe();
expect(eventConsumers).toEqual([
new EventConsumerDto('event-consumer1', true, true, 'an error 1', '13'),
new EventConsumerDto('event-consumer2', true, true, 'an error 2', '29')
]);
authService.verifyAll();
});
it('should make put request to start event consumer', () => {
authService.setup(x => x.authPut('http://service/p/api/event-consumers/event-consumer1/start', It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
eventConsumersService.startEventConsumer('event-consumer1');
authService.verifyAll();
});
it('should make put request to stop event consumer', () => {
authService.setup(x => x.authPut('http://service/p/api/event-consumers/event-consumer1/stop', It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
eventConsumersService.stopEventConsumer('event-consumer1');
authService.verifyAll();
});
it('should make put request to reset event consumer', () => {
authService.setup(x => x.authPut('http://service/p/api/event-consumers/event-consumer1/reset', It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
eventConsumersService.resetEventConsumer('event-consumer1');
authService.verifyAll();
});
});

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

@ -5,13 +5,13 @@
* Copyright (c) Sebastian Stehle. 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 } from 'framework';
import { AuthService } from './auth.service';
import { ApiUrlConfig, HTTP } from 'framework';
export class EventConsumerDto {
constructor(
@ -27,7 +27,7 @@ export class EventConsumerDto {
@Injectable()
export class EventConsumersService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -35,8 +35,7 @@ export class EventConsumersService {
public getEventConsumers(): Observable<EventConsumerDto[]> {
const url = this.apiUrl.buildUrl('/api/event-consumers');
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -49,27 +48,27 @@ export class EventConsumersService {
item.position);
});
})
.catchError('Failed to load event consumers. Please reload.');
.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 this.authService.authPut(url, {})
.catchError('Failed to start event consumer. Please reload.');
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 this.authService.authPut(url, {})
.catchError('Failed to stop event consumer. Please reload.');
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 this.authService.authPut(url, {})
.catchError('Failed to reset event consumer. Please reload.');
return HTTP.putVersioned(this.http, url, {})
.pretifyError('Failed to reset event consumer. Please reload.');
}
}

77
src/Squidex/app/shared/services/help.service.spec.ts

@ -1,77 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Http, Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { HelpService } from './../';
describe('AppClientsService', () => {
let helpService: HelpService;
let http: IMock<Http>;
beforeEach(() => {
http = Mock.ofType(Http);
helpService = new HelpService(http.object);
});
it('should make get request to get help sections', () => {
http.setup(x => x.get('https://api.gitbook.com/book/squidex/squidex/contents/01-chapter/02-article.json'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
sections: [
{
content: 'A test content with'
},
{
content: 'A test content with a <a href="https://squidex.io">A Link</a>'
},
{
content: 'A test content with a <a href="../GLOSSARY.html#content">Glossary Link</a>'
}
]
}
})
)
))
.verifiable(Times.once());
let helpSections: string[] | null = null;
helpService.getHelp('01-chapter/02-article').subscribe(result => {
helpSections = result;
});
expect(helpSections).toEqual([
'A test content with',
'A test content with a <a href="https://squidex.io">A Link</a>',
'A test content with a <a target="_blank" href="https://docs.squidex.io/GLOSSARY.html#content">Glossary Link</a>'
]);
http.verifyAll();
});
it('should return empty sections if get request fails', () => {
http.setup(x => x.get('https://api.gitbook.com/book/squidex/squidex/contents/01-chapter/02-article.json'))
.returns(() => Observable.throw('An error'))
.verifiable(Times.once());
let helpSections: string[] | null = null;
helpService.getHelp('01-chapter/02-article').subscribe(result => {
helpSections = result;
});
expect(helpSections).toEqual([]);
http.verifyAll();
});
});

7
src/Squidex/app/shared/services/help.service.ts

@ -5,14 +5,14 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs';
@Injectable()
export class HelpService {
constructor(
private readonly http: Http
private readonly http: HttpClient
) {
}
@ -20,8 +20,7 @@ export class HelpService {
const url = `https://api.gitbook.com/book/squidex/squidex/contents/${helpPage}.json`;
return this.http.get(url)
.map(response => response.json())
.map(response => {
.map((response: any) => {
const result: string[] = [];
for (let section of response.sections) {

68
src/Squidex/app/shared/services/history.service.spec.ts

@ -1,68 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { DateTime } from 'framework';
import {
ApiUrlConfig,
AuthService,
HistoryEventDto,
HistoryService
} from './../';
describe('HistoryService', () => {
let authService: IMock<AuthService>;
let languageService: HistoryService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
languageService = new HistoryService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get history events', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/history?channel=settings.contributors'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
actor: 'User1',
eventId: '1',
message: 'Message 1',
created: '2016-12-12T10:10'
},
{
actor: 'User2',
eventId: '2',
message: 'Message 2',
created: '2016-12-13T10:10'
}
]
})
)
))
.verifiable(Times.once());
let events: HistoryEventDto[] | null = null;
languageService.getHistory('my-app', 'settings.contributors').subscribe(result => {
events = result;
}).unsubscribe();
expect(events).toEqual(
[
new HistoryEventDto('1', 'User1', 'Message 1', DateTime.parseISO_UTC('2016-12-12T10:10')),
new HistoryEventDto('2', 'User2', 'Message 2', DateTime.parseISO_UTC('2016-12-13T10:10'))
]);
authService.verifyAll();
});
});

15
src/Squidex/app/shared/services/history.service.ts

@ -5,13 +5,17 @@
* Copyright (c) Sebastian Stehle. 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, DateTime } from 'framework';
import { AuthService } from './auth.service';
import {
ApiUrlConfig,
HTTP,
DateTime
} from 'framework';
export class HistoryEventDto {
constructor(
@ -26,7 +30,7 @@ export class HistoryEventDto {
@Injectable()
export class HistoryService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -34,8 +38,7 @@ export class HistoryService {
public getHistory(appName: string, channel: string): Observable<HistoryEventDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/history?channel=${channel}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -47,6 +50,6 @@ export class HistoryService {
DateTime.parseISO_UTC(item.created));
});
})
.catchError('Failed to load history. Please reload.');
.pretifyError('Failed to load history. Please reload.');
}
}

62
src/Squidex/app/shared/services/languages.service.spec.ts

@ -1,62 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AuthService,
LanguageDto,
LanguageService
} from './../';
describe('LanguageService', () => {
let authService: IMock<AuthService>;
let languageService: LanguageService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
languageService = new LanguageService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get languages', () => {
authService.setup(x => x.authGet('http://service/p/api/languages'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
iso2Code: 'de',
englishName: 'German'
},
{
iso2Code: 'en',
englishName: 'English'
}
]
})
)
))
.verifiable(Times.once());
let languages: LanguageDto[] | null = null;
languageService.getLanguages().subscribe(result => {
languages = result;
}).unsubscribe();
expect(languages).toEqual(
[
new LanguageDto('de', 'German'),
new LanguageDto('en', 'English')
]);
authService.verifyAll();
});
});

11
src/Squidex/app/shared/services/languages.service.ts

@ -5,13 +5,13 @@
* Copyright (c) Sebastian Stehle. 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 } from 'framework';
import { AuthService } from './auth.service';
import { ApiUrlConfig, HTTP } from 'framework';
export class LanguageDto {
constructor(
@ -24,7 +24,7 @@ export class LanguageDto {
@Injectable()
export class LanguageService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -32,8 +32,7 @@ export class LanguageService {
public getLanguages(): Observable<LanguageDto[]> {
const url = this.apiUrl.buildUrl('api/languages');
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -43,6 +42,6 @@ export class LanguageService {
item.englishName);
});
})
.catchError('Failed to load languages. Please reload.');
.pretifyError('Failed to load languages. Please reload.');
}
}

99
src/Squidex/app/shared/services/plans.service.spec.ts

@ -1,99 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import {
AppPlansDto,
ApiUrlConfig,
AuthService,
ChangePlanDto,
PlanDto,
PlansService,
Version
} from './../';
describe('PlansService', () => {
let authService: IMock<AuthService>;
let plansService: PlansService;
let version = new Version('1');
beforeEach(() => {
authService = Mock.ofType(AuthService);
plansService = new PlansService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get app plans', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/plans', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
currentPlanId: '123',
hasConfigured: true,
hasPortal: true,
planOwner: '456',
plans: [{
id: 'free',
name: 'Free',
costs: '14 €',
maxApiCalls: 1000,
maxAssetSize: 1500,
maxContributors: 2500
}, {
id: 'prof',
name: 'Prof',
costs: '18 €',
maxApiCalls: 4000,
maxAssetSize: 5500,
maxContributors: 6500
}]
}
})
)
))
.verifiable(Times.once());
let plans: AppPlansDto | null = null;
plansService.getPlans('my-app', version).subscribe(result => {
plans = result;
}).unsubscribe();
expect(plans).toEqual(
new AppPlansDto(
'123',
'456',
true,
true,
[
new PlanDto('free', 'Free', '14 €', 1000, 1500, 2500),
new PlanDto('prof', 'Prof', '18 €', 4000, 5500, 6500)
]
));
authService.verifyAll();
});
it('should make put request to change plan', () => {
const dto = new ChangePlanDto('enterprise');
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/plan', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
plansService.putPlan('my-app', dto, version);
authService.verifyAll();
});
});

19
src/Squidex/app/shared/services/plans.service.ts

@ -5,13 +5,17 @@
* Copyright (c) Sebastian Stehle. 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, Version } from 'framework';
import { AuthService } from './auth.service';
import {
ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppPlansDto {
constructor(
@ -46,7 +50,7 @@ export class ChangePlanDto {
@Injectable()
export class PlansService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -54,8 +58,7 @@ export class PlansService {
public getPlans(appName: string, version?: Version): Observable<AppPlansDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plans`);
return this.authService.authGet(url, version)
.map(response => response.json())
return HTTP.getVersioned(this.http, url, version)
.map(response => {
const items: any[] = response.plans;
@ -74,13 +77,13 @@ export class PlansService {
item.maxContributors);
}));
})
.catchError('Failed to load plans. Please reload.');
.pretifyError('Failed to load plans. Please reload.');
}
public putPlan(appName: string, dto: ChangePlanDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`);
return this.authService.authPut(url, dto, version)
.catchError('Failed to change plan. Please reload.');
return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to change plan. Please reload.');
}
}

175
src/Squidex/app/shared/services/schemas.fields.spec.ts

@ -1,175 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import {
AssetsFieldPropertiesDto,
BooleanFieldPropertiesDto,
DateTimeFieldPropertiesDto,
FieldDto,
FieldPropertiesDto,
GeolocationFieldPropertiesDto,
JsonFieldPropertiesDto,
NumberFieldPropertiesDto,
ReferencesFieldPropertiesDto,
StringFieldPropertiesDto
} from './../';
describe('AssetsField', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, true, false, 1, 1));
it('should create validators', () => {
expect(field.createValidators().length).toBe(3);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to asset count', () => {
expect(field.formatValue([1, 2, 3])).toBe('3 Asset(s)');
});
it('should return zero formatting if other type', () => {
expect(field.formatValue(1)).toBe('0 Assets');
});
});
describe('BooleanField', () => {
const field = createField(new BooleanFieldPropertiesDto(null, null, null, true, false, 'Checkbox'));
it('should create validators', () => {
expect(field.createValidators().length).toBe(1);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to checkmark if true', () => {
expect(field.formatValue(true)).toBe('✔');
});
it('should format to minus if false', () => {
expect(field.formatValue(false)).toBe('-');
});
});
describe('DateTimeField', () => {
const field = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'Date'));
it('should create validators', () => {
expect(field.createValidators().length).toBe(1);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to input if parsing failed', () => {
expect(field.formatValue(true)).toBe(true);
});
it('should format to date', () => {
const dateField = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'Date'));
expect(dateField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12');
});
it('should format to date', () => {
const dateTimeField = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'DateTime'));
expect(dateTimeField.formatValue('2017-12-12T16:00:00Z').substr(0, 10)).toBe('2017-12-12');
});
});
describe('GeolocationField', () => {
const field = createField(new GeolocationFieldPropertiesDto(null, null, null, true, false, 'Default'));
it('should create validators', () => {
expect(field.createValidators().length).toBe(1);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to latitude and longitude', () => {
expect(field.formatValue({ latitude: 42, longitude: 3.14 })).toBe('3.14, 42');
});
});
describe('JsonField', () => {
const field = createField(new JsonFieldPropertiesDto(null, null, null, true, false));
it('should create validators', () => {
expect(field.createValidators().length).toBe(1);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to constant', () => {
expect(field.formatValue({})).toBe('<Json />');
});
});
describe('NumberField', () => {
const field = createField(new NumberFieldPropertiesDto(null, null, null, true, false, 'Input', undefined, 3, 1, [1, 2, 3]));
it('should create validators', () => {
expect(field.createValidators().length).toBe(4);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to number', () => {
expect(field.formatValue(42)).toBe(42);
});
});
describe('ReferencesField', () => {
const field = createField(new ReferencesFieldPropertiesDto(null, null, null, true, false, 1, 1));
it('should create validators', () => {
expect(field.createValidators().length).toBe(3);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to asset count', () => {
expect(field.formatValue([1, 2, 3])).toBe('3 Reference(s)');
});
it('should return zero formatting if other type', () => {
expect(field.formatValue(1)).toBe('0 References');
});
});
describe('NumberField', () => {
const field = createField(new StringFieldPropertiesDto(null, null, null, true, false, 'Input', undefined, 'pattern', undefined, 3, 1, ['1', '2']));
it('should create validators', () => {
expect(field.createValidators().length).toBe(5);
});
it('should format to empty string if null', () => {
expect(field.formatValue(null)).toBe('');
});
it('should format to string', () => {
expect(field.formatValue('hello')).toBe('hello');
});
});
function createField(properties: FieldPropertiesDto) {
return new FieldDto(1, 'field1', false, false, 'languages', properties);
}

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

@ -1,450 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { It, IMock, Mock, Times } from 'typemoq';
import {
AddFieldDto,
ApiUrlConfig,
AuthService,
CreateSchemaDto,
createProperties,
DateTime,
EntityCreatedDto,
FieldDto,
SchemaDetailsDto,
SchemaDto,
SchemaPropertiesDto,
SchemasService,
UpdateFieldDto,
UpdateSchemaDto,
Version
} from './../';
describe('SchemasService', () => {
let authService: IMock<AuthService>;
let schemasService: SchemasService;
let version = new Version('1');
beforeEach(() => {
authService = Mock.ofType(AuthService);
schemasService = new SchemasService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should throw if creating invalid property type', () => {
expect(() => createProperties('invalid')).toThrow('Invalid properties type');
});
it('should make get request to get schemas', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/schemas'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
id: 'id1',
name: 'name1',
properties: {
label: 'label1',
hints: 'hints1'
},
isPublished: true,
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
version: 11,
data: {}
},
{
id: 'id2',
name: 'name2',
properties: {
label: 'label2',
hints: 'hints2'
},
isPublished: true,
created: '2016-10-12T10:10',
createdBy: 'Created2',
lastModified: '2017-10-12T10:10',
lastModifiedBy: 'LastModifiedBy2',
version: 22,
data: {}
}
]
})
)
))
.verifiable(Times.once());
let schemas: SchemaDto[] | null = null;
schemasService.getSchemas('my-app').subscribe(result => {
schemas = result;
}).unsubscribe();
expect(schemas).toEqual([
new SchemaDto('id1', 'name1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
new Version('11')),
new SchemaDto('id2', 'name2', new SchemaPropertiesDto('label2', 'hints2'), true, 'Created2', 'LastModifiedBy2',
DateTime.parseISO_UTC('2016-10-12T10:10'),
DateTime.parseISO_UTC('2017-10-12T10:10'),
new Version('22'))
]);
authService.verifyAll();
});
it('should make get request to get schema', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/schemas/my-schema'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'id1',
name: 'name1',
properties: {
label: 'label1',
hints: 'hints1'
},
isPublished: true,
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
version: 11,
fields: [
{
fieldId: 1,
name: 'field1',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'Number'
}
},
{
fieldId: 2,
name: 'field2',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'String'
}
},
{
fieldId: 3,
name: 'field3',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'Boolean'
}
},
{
fieldId: 4,
name: 'field4',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'DateTime'
}
},
{
fieldId: 5,
name: 'field5',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'Json'
}
},
{
fieldId: 6,
name: 'field6',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'Geolocation'
}
},
{
fieldId: 7,
name: 'field7',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'Assets'
}
},
{
fieldId: 8,
name: 'field8',
isHidden: true,
isDisabled: true,
partitioning: 'language',
properties: {
fieldType: 'References'
}
}
]
}
})
)
))
.verifiable(Times.once());
let schema: SchemaDetailsDto | null = null;
schemasService.getSchema('my-app', 'my-schema', version).subscribe(result => {
schema = result;
}).unsubscribe();
expect(schema).toEqual(
new SchemaDetailsDto('id1', 'name1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
new Version('11'),
[
new FieldDto(1, 'field1', true, true, 'language', createProperties('Number')),
new FieldDto(2, 'field2', true, true, 'language', createProperties('String')),
new FieldDto(3, 'field3', true, true, 'language', createProperties('Boolean')),
new FieldDto(4, 'field4', true, true, 'language', createProperties('DateTime')),
new FieldDto(5, 'field5', true, true, 'language', createProperties('Json')),
new FieldDto(6, 'field6', true, true, 'language', createProperties('Geolocation')),
new FieldDto(7, 'field7', true, true, 'language', createProperties('Assets')),
new FieldDto(8, 'field8', true, true, 'language', createProperties('References'))
]));
authService.verifyAll();
});
it('should make post request to create schema', () => {
const dto = new CreateSchemaDto('name');
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/schemas', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'my-schema'
}
})
)
))
.verifiable(Times.once());
let created: EntityCreatedDto | null = null;
schemasService.postSchema('my-app', dto, version).subscribe(result => {
created = result;
});
expect(created).toEqual(
new EntityCreatedDto('my-schema'));
authService.verifyAll();
});
it('should make post request to add field', () => {
const dto = new AddFieldDto('name', 'invariant', createProperties('Number'));
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/schemas/my-schema/fields', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 123
}
})
)
))
.verifiable(Times.once());
let created: EntityCreatedDto | null = null;
schemasService.postField('my-app', 'my-schema', dto, version).subscribe(result => {
created = result;
});
expect(created).toEqual(
new EntityCreatedDto(123));
authService.verifyAll();
});
it('should make put request to update schema', () => {
const dto = new UpdateSchemaDto('label', 'hints');
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.putSchema('my-app', 'my-schema', dto, version);
authService.verifyAll();
});
it('should make put request to update field', () => {
const dto = new UpdateFieldDto(createProperties('Number'));
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/fields/1', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.putField('my-app', 'my-schema', 1, dto, version);
authService.verifyAll();
});
it('should make put request to update field ordering', () => {
const dto = [1, 2, 3];
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/fields/ordering', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.putFieldOrdering('my-app', 'my-schema', dto, version);
authService.verifyAll();
});
it('should make put request to publish schema', () => {
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/publish', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.publishSchema('my-app', 'my-schema', version);
authService.verifyAll();
});
it('should make put request to unpublish schema', () => {
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/unpublish', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.unpublishSchema('my-app', 'my-schema', version);
authService.verifyAll();
});
it('should make put request to enable field', () => {
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/enable', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.enableField('my-app', 'my-schema', 1, version);
authService.verifyAll();
});
it('should make put request to disable field', () => {
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/disable', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.disableField('my-app', 'my-schema', 1, version);
authService.verifyAll();
});
it('should make put request to show field', () => {
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/show', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.showField('my-app', 'my-schema', 1, version);
authService.verifyAll();
});
it('should make put request to hide field', () => {
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/hide', It.isAny(), version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.hideField('my-app', 'my-schema', 1, version);
authService.verifyAll();
});
it('should make delete request to delete field', () => {
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/schemas/my-schema/fields/1', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.deleteField('my-app', 'my-schema', 1, version);
authService.verifyAll();
});
it('should make delete request to delete schema', () => {
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/schemas/my-schema', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
schemasService.deleteSchema('my-app', 'my-schema', version);
authService.verifyAll();
});
});

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

@ -5,6 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ValidatorFn, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
@ -15,12 +16,11 @@ import {
ApiUrlConfig,
DateTime,
EntityCreatedDto,
HTTP,
ValidatorsEx,
Version
} from 'framework';
import { AuthService } from './auth.service';
export const fieldTypes: string[] = [
'Assets',
'Boolean',
@ -483,7 +483,7 @@ export class CreateSchemaDto {
@Injectable()
export class SchemasService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -491,8 +491,7 @@ export class SchemasService {
public getSchemas(appName: string): Observable<SchemaDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -510,14 +509,13 @@ export class SchemasService {
new Version(item.version.toString()));
});
})
.catchError('Failed to load schemas. Please reload.');
.pretifyError('Failed to load schemas. Please reload.');
}
public getSchema(appName: string, id: string, version?: Version): Observable<SchemaDetailsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${id}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const fields = response.fields.map((item: any) => {
const propertiesDto =
@ -547,105 +545,103 @@ export class SchemasService {
new Version(response.version.toString()),
fields);
})
.catchError('Failed to load schema. Please reload.');
.pretifyError('Failed to load schema. Please reload.');
}
public postSchema(appName: string, dto: CreateSchemaDto, version?: Version): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return this.authService.authPost(url, dto, version)
.map(response => response.json())
return HTTP.postVersioned(this.http, url, dto, version)
.map(response => {
return new EntityCreatedDto(response.id);
})
.catchError('Failed to create schema. Please reload.');
.pretifyError('Failed to create schema. Please reload.');
}
public postField(appName: string, schemaName: string, dto: AddFieldDto, version?: Version): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`);
return this.authService.authPost(url, dto, version)
.map(response => response.json())
return HTTP.postVersioned(this.http, url, dto, version)
.map(response => {
return new EntityCreatedDto(response.id);
})
.catchError('Failed to add field. Please reload.');
.pretifyError('Failed to add field. Please reload.');
}
public putSchema(appName: string, schemaName: string, dto: UpdateSchemaDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`);
return this.authService.authPut(url, dto, version)
.catchError('Failed to update schema. Please reload.');
return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to update schema. Please reload.');
}
public putFieldOrdering(appName: string, schemaName: string, dto: number[], version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/ordering`);
return this.authService.authPut(url, { fieldIds: dto }, version)
.catchError('Failed to reorder fields. Please reload.');
return HTTP.putVersioned(this.http, url, { fieldIds: dto }, version)
.pretifyError('Failed to reorder fields. Please reload.');
}
public publishSchema(appName: string, schemaName: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to publish schema. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to publish schema. Please reload.');
}
public unpublishSchema(appName: string, schemaName: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/unpublish`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to unpublish schema. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to unpublish schema. Please reload.');
}
public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`);
return this.authService.authPut(url, dto, version)
.catchError('Failed to update field. Please reload.');
return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to update field. Please reload.');
}
public enableField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/enable`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to enable field. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to enable field. Please reload.');
}
public disableField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/disable`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to disable field. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to disable field. Please reload.');
}
public showField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to show field. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to show field. Please reload.');
}
public hideField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/hide`);
return this.authService.authPut(url, {}, version)
.catchError('Failed to hide field. Please reload.');
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to hide field. Please reload.');
}
public deleteField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`);
return this.authService.authDelete(url, version)
.catchError('Failed to delete field. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to delete field. Please reload.');
}
public deleteSchema(appName: string, schemaName: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`);
return this.authService.authDelete(url, version)
.catchError('Failed to delete schema. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to delete schema. Please reload.');
}
}

149
src/Squidex/app/shared/services/usages.service.spec.ts

@ -1,149 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AuthService,
CallsUsageDto,
CurrentCallsDto,
CurrentStorageDto,
DateTime,
StorageUsageDto,
UsagesService
} from './../';
describe('UsagesService', () => {
let authService: IMock<AuthService>;
let usagesService: UsagesService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
usagesService = new UsagesService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get calls usages', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/usages/calls/2017-10-12/2017-10-13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
date: '2017-10-12',
count: 10,
averageMs: 130
},
{
date: '2017-10-13',
count: 13,
averageMs: 170
}
]
})
)
))
.verifiable(Times.once());
let usages: CallsUsageDto[] | null = null;
usagesService.getCallsUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => {
usages = result;
}).unsubscribe();
expect(usages).toEqual(
[
new CallsUsageDto(DateTime.parseISO_UTC('2017-10-12'), 10, 130),
new CallsUsageDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170)
]);
authService.verifyAll();
});
it('should make get request to get month calls', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/usages/calls/month'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: { count: 130, maxAllowed: 150 }
})
)
))
.verifiable(Times.once());
let usages: CurrentCallsDto | null = null;
usagesService.getMonthCalls('my-app').subscribe(result => {
usages = result;
}).unsubscribe();
expect(usages).toEqual(new CurrentCallsDto(130, 150));
authService.verifyAll();
});
it('should make get request to get storage usages', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/usages/storage/2017-10-12/2017-10-13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
date: '2017-10-12',
count: 10,
size: 130
},
{
date: '2017-10-13',
count: 13,
size: 170
}
]
})
)
))
.verifiable(Times.once());
let usages: StorageUsageDto[] | null = null;
usagesService.getStorageUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => {
usages = result;
}).unsubscribe();
expect(usages).toEqual(
[
new StorageUsageDto(DateTime.parseISO_UTC('2017-10-12'), 10, 130),
new StorageUsageDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170)
]);
authService.verifyAll();
});
it('should make get request to get today storage', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/usages/storage/today'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: { size: 130, maxAllowed: 150 }
})
)
))
.verifiable(Times.once());
let usages: CurrentStorageDto | null = null;
usagesService.getTodayStorage('my-app').subscribe(result => {
usages = result;
}).unsubscribe();
expect(usages).toEqual(new CurrentStorageDto(130, 150));
authService.verifyAll();
});
});

38
src/Squidex/app/shared/services/usages.service.ts

@ -5,13 +5,17 @@
* Copyright (c) Sebastian Stehle. 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, DateTime } from 'framework';
import { AuthService } from './auth.service';
import {
ApiUrlConfig,
DateTime,
HTTP
} from 'framework';
export class CallsUsageDto {
constructor(
@ -50,7 +54,7 @@ export class CurrentCallsDto {
@Injectable()
export class UsagesService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -58,26 +62,27 @@ export class UsagesService {
public getMonthCalls(app: string): Observable<CurrentCallsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/month`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => new CurrentCallsDto(response.count, response.maxAllowed))
.catchError('Failed to load monthly api calls. Please reload.');
return HTTP.getVersioned(this.http, url)
.map(response => {
return new CurrentCallsDto(response.count, response.maxAllowed);
})
.pretifyError('Failed to load monthly api calls. Please reload.');
}
public getTodayStorage(app: string): Observable<CurrentStorageDto> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/today`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => new CurrentStorageDto(response.size, response.maxAllowed))
.catchError('Failed to load todays storage size. Please reload.');
return HTTP.getVersioned(this.http, url)
.map(response => {
return new CurrentStorageDto(response.size, response.maxAllowed);
})
.pretifyError('Failed to load todays storage size. Please reload.');
}
public getCallsUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<CallsUsageDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/${fromDate.toStringFormat('YYYY-MM-DD')}/${toDate.toStringFormat('YYYY-MM-DD')}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -88,14 +93,13 @@ export class UsagesService {
item.averageMs);
});
})
.catchError('Failed to load calls usage. Please reload.');
.pretifyError('Failed to load calls usage. Please reload.');
}
public getStorageUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<StorageUsageDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/${fromDate.toStringFormat('YYYY-MM-DD')}/${toDate.toStringFormat('YYYY-MM-DD')}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -106,6 +110,6 @@ export class UsagesService {
item.size);
});
})
.catchError('Failed to load storage usage. Please reload.');
.pretifyError('Failed to load storage usage. Please reload.');
}
}

103
src/Squidex/app/shared/services/users-provider.service.spec.ts

@ -1,103 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import {
AuthService,
Profile,
UserDto,
UsersProviderService,
UsersService
} from './../';
describe('UsersProviderService', () => {
let authService: IMock<AuthService>;
let usersService: IMock<UsersService>;
let usersProviderService: UsersProviderService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
usersService = Mock.ofType(UsersService);
usersProviderService = new UsersProviderService(usersService.object, authService.object);
});
it('Should return users service when user not cached', () => {
const user = new UserDto('123', 'mail@domain.com', 'User1', 'path/to/image', true);
usersService.setup(x => x.getUser('123'))
.returns(() => Observable.of(user)).verifiable(Times.once());
let resultingUser: UserDto | null = null;
usersProviderService.getUser('123').subscribe(result => {
resultingUser = result;
}).unsubscribe();
expect(resultingUser).toBe(user);
usersService.verifyAll();
});
it('Should return provide user from cache', () => {
const user = new UserDto('123', 'mail@domain.com', 'User1', 'path/to/image', true);
usersService.setup(x => x.getUser('123'))
.returns(() => Observable.of(user)).verifiable(Times.once());
usersProviderService.getUser('123');
let resultingUser: UserDto | null = null;
usersProviderService.getUser('123').subscribe(result => {
resultingUser = result;
}).unsubscribe();
expect(resultingUser).toBe(user);
usersService.verifyAll();
});
it('Should return me when user is current user', () => {
const user = new UserDto('123', 'mail@domain.com', 'User1', 'path/to/image', true);
authService.setup(x => x.user)
.returns(() => new Profile(<any>{ profile: { sub: '123'}}));
usersService.setup(x => x.getUser('123'))
.returns(() => Observable.of(user)).verifiable(Times.once());
let resultingUser: UserDto | null = null;
usersProviderService.getUser('123').subscribe(result => {
resultingUser = result;
}).unsubscribe();
expect(resultingUser).toEqual(new UserDto('123', 'mail@domain.com', 'Me', 'path/to/image', true));
usersService.verifyAll();
});
it('Should return invalid user when not found', () => {
authService.setup(x => x.user)
.returns(() => new Profile(<any>{ profile: { sub: '123'}}));
usersService.setup(x => x.getUser('123'))
.returns(() => Observable.throw('NOT FOUND')).verifiable(Times.once());
let resultingUser: UserDto | null = null;
usersProviderService.getUser('123').subscribe(result => {
resultingUser = result;
}).unsubscribe();
expect(resultingUser).toEqual(new UserDto('NOT FOUND', 'NOT FOUND', 'NOT FOUND', null, false));
usersService.verifyAll();
});
});

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

@ -1,339 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { It, IMock, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AuthService,
CreateUserDto,
UpdateUserDto,
UserCreatedDto,
UserDto,
UserManagementService,
UsersDto,
UsersService
} from './../';
describe('UsersService', () => {
let authService: IMock<AuthService>;
let usersService: UsersService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
usersService = new UsersService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get many users', () => {
authService.setup(x => x.authGet('http://service/p/api/users?query='))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
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
}
]
})
)
))
.verifiable(Times.once());
let user: UserDto[] | null = null;
usersService.getUsers().subscribe(result => {
user = result;
}).unsubscribe();
expect(user).toEqual(
[
new UserDto('123', 'mail1@domain.com', 'User1', 'path/to/image1', true),
new UserDto('456', 'mail2@domain.com', 'User2', 'path/to/image2', true)
]);
authService.verifyAll();
});
it('should make get request with query to get many users', () => {
authService.setup(x => x.authGet('http://service/p/api/users?query=my-query'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [
{
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
}
]
})
)
))
.verifiable(Times.once());
let user: UserDto[] | null = null;
usersService.getUsers('my-query').subscribe(result => {
user = result;
}).unsubscribe();
expect(user).toEqual(
[
new UserDto('123', 'mail1@domain.com', 'User1', 'path/to/image1', true),
new UserDto('456', 'mail2@domain.com', 'User2', 'path/to/image2', true)
]);
authService.verifyAll();
});
it('should make get request to get single user', () => {
authService.setup(x => x.authGet('http://service/p/api/users/123'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: '123',
email: 'mail1@domain.com',
displayName: 'User1',
pictureUrl: 'path/to/image1',
isLocked: true
}
})
)
))
.verifiable(Times.once());
let user: UserDto | null = null;
usersService.getUser('123').subscribe(result => {
user = result;
}).unsubscribe();
expect(user).toEqual(new UserDto('123', 'mail1@domain.com', 'User1', 'path/to/image1', true));
authService.verifyAll();
});
});
describe('UserManagementService', () => {
let authService: IMock<AuthService>;
let userManagementService: UserManagementService;
beforeEach(() => {
authService = Mock.ofType(AuthService);
userManagementService = new UserManagementService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get many users', () => {
authService.setup(x => x.authGet('http://service/p/api/user-management?take=20&skip=30&query='))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
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
}
]
}
})
)
))
.verifiable(Times.once());
let user: UsersDto | null = null;
userManagementService.getUsers(20, 30).subscribe(result => {
user = result;
}).unsubscribe();
expect(user).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)
]));
authService.verifyAll();
});
it('should make get request with query to get many users', () => {
authService.setup(x => x.authGet('http://service/p/api/user-management?take=20&skip=30&query=my-query'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
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
}
]
}
})
)
))
.verifiable(Times.once());
let user: UsersDto | null = null;
userManagementService.getUsers(20, 30, 'my-query').subscribe(result => {
user = result;
}).unsubscribe();
expect(user).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)
]));
authService.verifyAll();
});
it('should make get request to get single user', () => {
authService.setup(x => x.authGet('http://service/p/api/user-management/123'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: '123',
email: 'mail1@domain.com',
displayName: 'User1',
pictureUrl: 'path/to/image1',
isLocked: true
}
})
)
))
.verifiable(Times.once());
let user: UserDto | null = null;
userManagementService.getUser('123').subscribe(result => {
user = result;
}).unsubscribe();
expect(user).toEqual(new UserDto('123', 'mail1@domain.com', 'User1', 'path/to/image1', true));
authService.verifyAll();
});
it('should make post request to create user', () => {
const dto = new CreateUserDto('mail@squidex.io', 'Squidex User', 'password');
authService.setup(x => x.authPost('http://service/p/api/user-management', It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: '123',
pictureUrl: 'path/to/image1'
}
})
)
))
.verifiable(Times.once());
let user: UserDto | null = null;
userManagementService.postUser(dto).subscribe(result => {
user = result;
}).unsubscribe();
expect(user).toEqual(new UserCreatedDto('123', 'path/to/image1'));
authService.verifyAll();
});
it('should make put request to update user', () => {
const dto = new UpdateUserDto('mail@squidex.io', 'Squidex User', 'password');
authService.setup(x => x.authPut('http://service/p/api/user-management/123', It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
userManagementService.putUser('123', dto);
authService.verifyAll();
});
it('should make put request to lock user', () => {
authService.setup(x => x.authPut('http://service/p/api/user-management/123/lock', It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
userManagementService.lockUser('123');
authService.verifyAll();
});
it('should make put request to unlock user', () => {
authService.setup(x => x.authPut('http://service/p/api/user-management/123/unlock', It.isAny()))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
userManagementService.unlockUser('123');
authService.verifyAll();
});
});

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

@ -5,13 +5,13 @@
* Copyright (c) Sebastian Stehle. 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 } from 'framework';
import { AuthService } from './auth.service';
import { ApiUrlConfig, HTTP } from 'framework';
export class UsersDto {
constructor(
@ -61,7 +61,7 @@ export class UserDto {
@Injectable()
export class UsersService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -69,8 +69,7 @@ export class UsersService {
public getUsers(query?: string): Observable<UserDto[]> {
const url = this.apiUrl.buildUrl(`api/users?query=${query || ''}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response;
@ -83,14 +82,13 @@ export class UsersService {
item.isLocked);
});
})
.catchError('Failed to load users. Please reload.');
.pretifyError('Failed to load users. Please reload.');
}
public getUser(id: string): Observable<UserDto> {
const url = this.apiUrl.buildUrl(`api/users/${id}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
return new UserDto(
response.id,
@ -99,14 +97,14 @@ export class UsersService {
response.pictureUrl,
response.isLocked);
})
.catchError('Failed to load user. Please reload.');
.pretifyError('Failed to load user. Please reload.');
}
}
@Injectable()
export class UserManagementService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -114,8 +112,7 @@ export class UserManagementService {
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 this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
const items: any[] = response.items;
@ -130,14 +127,13 @@ export class UserManagementService {
return new UsersDto(response.total, users);
})
.catchError('Failed to load users. Please reload.');
.pretifyError('Failed to load users. Please reload.');
}
public getUser(id: string): Observable<UserDto> {
const url = this.apiUrl.buildUrl(`api/user-management/${id}`);
return this.authService.authGet(url)
.map(response => response.json())
return HTTP.getVersioned(this.http, url)
.map(response => {
return new UserDto(
response.id,
@ -146,36 +142,37 @@ export class UserManagementService {
response.pictureUrl,
response.isLocked);
})
.catchError('Failed to load user. Please reload.');
.pretifyError('Failed to load user. Please reload.');
}
public postUser(dto: CreateUserDto): Observable<UserDto> {
const url = this.apiUrl.buildUrl('api/user-management');
return this.authService.authPost(url, dto)
.map(response => response.json())
.map(response => new UserCreatedDto(response.id, response.pictureUrl))
.catchError('Failed to create user. Please reload.');
return HTTP.postVersioned(this.http, url, dto)
.map(response => {
return new UserCreatedDto(response.id, response.pictureUrl);
})
.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 this.authService.authPut(url, dto)
.catchError('Failed to update user. Please reload.');
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 this.authService.authPut(url, {})
.catchError('Failed to load users. Please retry.');
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 this.authService.authPut(url, {})
.catchError('Failed to load users. Please retry.');
return HTTP.putVersioned(this.http, url, {})
.pretifyError('Failed to load users. Please retry.');
}
}

99
src/Squidex/app/shared/services/webhooks.service.1.ts

@ -0,0 +1,99 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. 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,
Version
} from 'framework';
export class WebhookDto {
constructor(
public readonly id: string,
public readonly schemaId: string,
public readonly sharedSecret: string,
public readonly url: string,
public readonly totalSucceeded: number,
public readonly totalFailed: number,
public readonly totalTimedout: number,
public readonly averageRequestTimeMs: number,
public readonly lastDumps: string[]
) {
}
}
export class WebhookCreatedDto {
constructor(
public readonly id: string,
public readonly sharedSecret: string
) {
}
}
export class CreateWebhookDto {
constructor(
public readonly url: string
) {
}
}
@Injectable()
export class WebhooksService {
constructor(
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
public getWebhooks(appName: string, version?: Version): Observable<WebhookDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`);
return HTTP.getVersioned(this.http, url, version)
.map(response => {
const items: any[] = response;
return items.map(item => {
return new WebhookDto(
item.id,
item.schemaId,
item.sharedSecret,
item.url,
item.totalSucceeded,
item.totalFailed,
item.totalTimedout,
item.averageRequestTimeMs,
item.lastDumps);
});
})
.pretifyError('Failed to load webhooks. Please reload.');
}
public postWebhook(appName: string, schemaName: string, dto: CreateWebhookDto, version?: Version): Observable<WebhookCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks`);
return HTTP.postVersioned(this.http, url, dto, version)
.map(response => {
return new WebhookCreatedDto(
response.id,
response.sharedSecret);
})
.pretifyError('Failed to create webhook. Please reload.');
}
public deleteWebhook(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks/${id}`);
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to delete webhook. Please reload.');
}
}

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

@ -1,114 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Response, ResponseOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import {
ApiUrlConfig,
AuthService,
CreateWebhookDto,
Version,
WebhookCreatedDto,
WebhookDto,
WebhooksService
} from './../';
describe('WebhooksService', () => {
let authService: IMock<AuthService>;
let webhooksService: WebhooksService;
let version = new Version('1');
beforeEach(() => {
authService = Mock.ofType(AuthService);
webhooksService = new WebhooksService(authService.object, new ApiUrlConfig('http://service/p/'));
});
it('should make get request to get app webhooks', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/webhooks', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: [{
id: 'id1',
schemaId: 'schemaId1',
sharedSecret: 'token1',
url: 'http://squidex.io/1',
totalSucceeded: 1,
totalFailed: 2,
totalTimedout: 3,
averageRequestTimeMs: 4,
lastDumps: ['dump1']
}, {
id: 'id2',
schemaId: 'schemaId2',
sharedSecret: 'token2',
url: 'http://squidex.io/2',
totalSucceeded: 5,
totalFailed: 6,
totalTimedout: 7,
averageRequestTimeMs: 8,
lastDumps: ['dump2']
}]
})
)
))
.verifiable(Times.once());
let webhooks: WebhookDto[] | null = null;
webhooksService.getWebhooks('my-app', version).subscribe(result => {
webhooks = result;
}).unsubscribe();
expect(webhooks).toEqual([
new WebhookDto('id1', 'schemaId1', 'token1', 'http://squidex.io/1', 1, 2, 3, 4, ['dump1']),
new WebhookDto('id2', 'schemaId2', 'token2', 'http://squidex.io/2', 5, 6, 7, 8, ['dump2'])
]);
authService.verifyAll();
});
it('should make post request to create webhook', () => {
const dto = new CreateWebhookDto('http://squidex.io/hook');
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/schemas/my-schema/webhooks', dto, version))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: { id: 'id1', sharedSecret: 'token1' }
})
)
))
.verifiable(Times.once());
let webhook: WebhookCreatedDto | null = null;
webhooksService.postWebhook('my-app', 'my-schema', dto, version).subscribe(result => {
webhook = result;
}).unsubscribe();
expect(webhook).toEqual(new WebhookCreatedDto('id1', 'token1'));
authService.verifyAll();
});
it('should make delete request to delete webhook', () => {
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/schemas/my-schema/webhooks/123', version))
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
webhooksService.deleteWebhook('my-app', 'my-schema', '123', version);
authService.verifyAll();
});
});

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

@ -5,13 +5,17 @@
* Copyright (c) Sebastian Stehle. 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, Version } from 'framework';
import { AuthService } from './auth.service';
import {
ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class WebhookDto {
constructor(
@ -46,7 +50,7 @@ export class CreateWebhookDto {
@Injectable()
export class WebhooksService {
constructor(
private readonly authService: AuthService,
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -54,8 +58,7 @@ export class WebhooksService {
public getWebhooks(appName: string, version?: Version): Observable<WebhookDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`);
return this.authService.authGet(url, version)
.map(response => response.json())
return HTTP.getVersioned(this.http, url, version)
.map(response => {
const items: any[] = response;
@ -72,26 +75,25 @@ export class WebhooksService {
item.lastDumps);
});
})
.catchError('Failed to load webhooks. Please reload.');
.pretifyError('Failed to load webhooks. Please reload.');
}
public postWebhook(appName: string, schemaName: string, dto: CreateWebhookDto, version?: Version): Observable<WebhookCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks`);
return this.authService.authPost(url, dto, version)
.map(response => response.json())
return HTTP.postVersioned(this.http, url, dto, version)
.map(response => {
return new WebhookCreatedDto(
response.id,
response.sharedSecret);
})
.catchError('Failed to create webhook. Please reload.');
.pretifyError('Failed to create webhook. Please reload.');
}
public deleteWebhook(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks/${id}`);
return this.authService.authDelete(url, version)
.catchError('Failed to delete webhook. Please reload.');
return HTTP.deleteVersioned(this.http, url, version)
.pretifyError('Failed to delete webhook. Please reload.');
}
}

35
src/Squidex/package.json

@ -15,15 +15,15 @@
"build:clean": "rimraf wwwroot/build"
},
"dependencies": {
"@angular/animations": "4.2.5",
"@angular/common": "4.2.5",
"@angular/compiler": "4.2.5",
"@angular/core": "4.2.5",
"@angular/forms": "4.2.5",
"@angular/http": "4.2.5",
"@angular/platform-browser": "4.2.5",
"@angular/platform-browser-dynamic": "4.2.5",
"@angular/router": "4.2.5",
"@angular/animations": "4.3.0",
"@angular/common": "4.3.0",
"@angular/compiler": "4.3.0",
"@angular/core": "4.3.0",
"@angular/forms": "4.3.0",
"@angular/http": "4.3.0",
"@angular/platform-browser": "4.3.0",
"@angular/platform-browser-dynamic": "4.3.0",
"@angular/router": "4.3.0",
"angular-progress-http": "0.5.1",
"angular2-chartjs": "^0.2.0",
"babel-polyfill": "6.23.0",
@ -36,25 +36,26 @@
"oidc-client": "1.3.0",
"pikaday": "1.6.1",
"progressbar.js": "1.0.1",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react": "15.6.1",
"react-dom": "15.6.1",
"redoc": "1.16.1",
"rxjs": "5.4.2",
"zone.js": "0.8.12"
"zone.js": "0.8.13"
},
"devDependencies": {
"@angular/compiler-cli": "4.2.5",
"@angular/tsc-wrapped": "4.2.5",
"@ngtools/webpack": "1.5.0",
"@angular/compiler-cli": "4.3.0",
"@angular/tsc-wrapped": "4.3.0",
"@ngtools/webpack": "1.5.1",
"@types/core-js": "0.9.35",
"@types/jasmine": "2.5.43",
"@types/mousetrap": "1.5.33",
"@types/node": "7.0.5",
"@types/react": "^15.0.35",
"@types/react-dom": "^15.5.1",
"@types/react": "15.0.38",
"@types/react-dom": "15.5.1",
"angular2-router-loader": "0.3.5",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "3.2.1",
"codelyzer": "^3.1.2",
"cpx": "1.5.0",
"css-loader": "0.28.4",
"exports-loader": "0.6.4",

4
src/Squidex/tslint.json

@ -21,10 +21,6 @@
true,
"check-accessor"
],
"member-ordering": [
true,
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [

Loading…
Cancel
Save