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 { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
@ -16,7 +17,6 @@ import {
ApiUrlConfig, ApiUrlConfig,
AppComponentBase, AppComponentBase,
AppsStoreService, AppsStoreService,
AuthService,
NotificationService NotificationService
} from 'shared'; } from 'shared';
@ -31,8 +31,8 @@ export class GraphQLPageComponent extends AppComponentBase implements OnInit {
public graphiQLContainer: ElementRef; public graphiQLContainer: ElementRef;
constructor(apps: AppsStoreService, notifications: NotificationService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly authService: AuthService, private readonly apiUrl: ApiUrlConfig,
private readonly apiUrl: ApiUrlConfig private readonly http: HttpClient
) { ) {
super(notifications, apps); super(notifications, apps);
} }
@ -48,7 +48,7 @@ export class GraphQLPageComponent extends AppComponentBase implements OnInit {
private request(params: any) { private request(params: any) {
return this.appNameOnce() 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(); .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 { Observable } from 'rxjs/Observable';
import { catchError } from './http-utils';
import { pretifyError } from './http-extensions-impl';
declare module 'rxjs/Observable' { declare module 'rxjs/Observable' {
interface Observable<T> { 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-change.directive';
export * from './angular/focus-on-init.directive'; export * from './angular/focus-on-init.directive';
export * from './angular/geolocation-editor.component'; 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/image-source.directive';
export * from './angular/indeterminate-value.directive'; export * from './angular/indeterminate-value.directive';
export * from './angular/json-editor.component'; export * from './angular/json-editor.component';

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

@ -6,9 +6,9 @@
*/ */
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core'; import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { import {
@ -68,7 +68,7 @@ import {
@NgModule({ @NgModule({
imports: [ imports: [
HttpModule, HttpClientModule,
FormsModule, FormsModule,
CommonModule, CommonModule,
RouterModule, RouterModule,
@ -163,7 +163,7 @@ import {
TitleComponent, TitleComponent,
ToggleComponent, ToggleComponent,
UserReportComponent, UserReportComponent,
HttpModule, HttpClientModule,
FormsModule, FormsModule,
CommonModule, CommonModule,
RouterModule, 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-schema.guard';
export * from './guards/resolve-user.guard'; export * from './guards/resolve-user.guard';
export * from './interceptors/auth.interceptor';
export * from './services/app-contributors.service'; export * from './services/app-contributors.service';
export * from './services/app-clients.service'; export * from './services/app-clients.service';
export * from './services/app-languages.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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core'; import { ModuleWithProviders, NgModule } from '@angular/core';
import { DndModule } from 'ng2-dnd'; import { DndModule } from 'ng2-dnd';
import { ProgressHttpModule } from 'angular-progress-http';
import { SqxFrameworkModule } from 'framework'; import { SqxFrameworkModule } from 'framework';
@ -21,6 +21,7 @@ import {
AppMustExistGuard, AppMustExistGuard,
AssetComponent, AssetComponent,
AssetsService, AssetsService,
AuthInterceptor,
AuthService, AuthService,
ContentsService, ContentsService,
EventConsumersService, EventConsumersService,
@ -56,7 +57,6 @@ import {
@NgModule({ @NgModule({
imports: [ imports: [
ProgressHttpModule,
DndModule, DndModule,
SqxFrameworkModule SqxFrameworkModule
], ],
@ -122,7 +122,12 @@ export class SqxSharedModule {
UserManagementService, UserManagementService,
UsersProviderService, UsersProviderService,
UsersService, 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Http, Response, ResponseOptions } from '@angular/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { Observable } from 'rxjs'; import { inject, TestBed } from '@angular/core/testing';
import { It, IMock, Mock, Times } from 'typemoq';
import { import {
AccessTokenDto, AccessTokenDto,
ApiUrlConfig, ApiUrlConfig,
AppClientDto, AppClientDto,
AppClientsService, AppClientsService,
AuthService,
CreateAppClientDto, CreateAppClientDto,
UpdateAppClientDto, UpdateAppClientDto,
Version Version
} from './../'; } from './../';
describe('AppClientsService', () => { describe('AppClientsService', () => {
let authService: IMock<AuthService>;
let appClientsService: AppClientsService;
let version = new Version('1'); let version = new Version('1');
let http: IMock<Http>;
beforeEach(() => { beforeEach(() => {
http = Mock.ofType(Http); TestBed.configureTestingModule({
imports: [
authService = Mock.ofType(AuthService); HttpClientTestingModule
appClientsService = new AppClientsService(authService.object, new ApiUrlConfig('http://service/p/'), http.object); ],
providers: [
AppClientsService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
}); });
it('should make get request to get app clients', () => { afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/clients', version)) httpMock.verify();
.returns(() => Observable.of( }));
new Response(
new ResponseOptions({ it('should make get request to get app clients',
body: [ inject([AppClientsService, HttpTestingController], (appClientsService: AppClientsService, httpMock: HttpTestingController) => {
{
id: 'client1',
name: 'Client 1',
secret: 'secret1'
},
{
id: 'client2',
name: 'Client 2',
secret: 'secret2'
}
]
})
)
))
.verifiable(Times.once());
let clients: AppClientDto[] | null = null; let clients: AppClientDto[] | null = null;
appClientsService.getClients('my-app', version).subscribe(result => { appClientsService.getClients('my-app', version).subscribe(result => {
clients = 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( expect(clients).toEqual(
[ [
new AppClientDto('client1', 'Client 1', 'secret1'), new AppClientDto('client1', 'Client 1', 'secret1'),
new AppClientDto('client2', 'Client 2', 'secret2') 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'); 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; let client: AppClientDto | null = null;
appClientsService.postClient('my-app', dto, version).subscribe(result => { appClientsService.postClient('my-app', dto, version).subscribe(result => {
client = 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( expect(client).toEqual(
new AppClientDto('client1', 'Client 1', 'secret1')); 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'); const dto = new UpdateAppClientDto('Client 1 New');
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/clients/client1', dto, version)) appClientsService.updateClient('my-app', 'client1', dto, version).subscribe();
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
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', () => { req.flush({});
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());
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 req = httpMock.expectOne('http://service/p/api/apps/my-app/clients/client1');
const body = 'grant_type=client_credentials&scope=squidex-api&client_id=my-app:myClientId&client_secret=mySecret';
http.setup(x => x.post('http://service/p/identity-server/connect/token', body, It.isAny())) expect(req.request.method).toEqual('DELETE');
.returns(() => Observable.of( expect(req.request.headers.get('If-Match')).toEqual('1');
new Response(
new ResponseOptions({ req.flush({});
body: { }));
access_token: 'token1', token_type: 'type1'
} it('should make form request to create token',
}) inject([AppClientsService, HttpTestingController], (appClientsService: AppClientsService, httpMock: HttpTestingController) => {
)
))
.verifiable(Times.once());
let accessTokenDto: AccessTokenDto | null = null; let accessTokenDto: AccessTokenDto | null = null;
@ -150,9 +130,16 @@ describe('AppClientsService', () => {
accessTokenDto = result; 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( expect(accessTokenDto).toEqual(
new AccessTokenDto('token1', 'type1')); 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig, Version } from 'framework'; import {
import { AuthService } from './auth.service'; ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppClientDto { export class AppClientDto {
constructor( constructor(
@ -48,17 +51,15 @@ export class AccessTokenDto {
@Injectable() @Injectable()
export class AppClientsService { export class AppClientsService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig, private readonly apiUrl: ApiUrlConfig
private readonly http: Http
) { ) {
} }
public getClients(appName: string, version?: Version): Observable<AppClientDto[]> { public getClients(appName: string, version?: Version): Observable<AppClientDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return this.authService.authGet(url, version) return HTTP.getVersioned(this.http, url, version)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -69,51 +70,51 @@ export class AppClientsService {
item.secret); 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> { public postClient(appName: string, dto: CreateAppClientDto, version?: Version): Observable<AppClientDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return this.authService.authPost(url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => response.json())
.map(response => { .map(response => {
return new AppClientDto( return new AppClientDto(
response.id, response.id,
response.name, response.name,
response.secret); 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> { public updateClient(appName: string, id: string, dto: UpdateAppClientDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return this.authService.authPut(url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.catchError('Failed to revoke client. Please reload.'); .pretifyError('Failed to revoke client. Please reload.');
} }
public deleteClient(appName: string, id: string, version?: Version): Observable<any> { public deleteClient(appName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to revoke client. Please reload.'); .pretifyError('Failed to revoke client. Please reload.');
} }
public createToken(appName: string, client: AppClientDto): Observable<AccessTokenDto> { public createToken(appName: string, client: AppClientDto): Observable<AccessTokenDto> {
const options = new RequestOptions({ const options = {
headers: new Headers({ headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded' '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 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'); const url = this.apiUrl.buildUrl('identity-server/connect/token');
return this.http.post(url, body, options) return this.http.post(url, body, options)
.map(response => response.json()) .map((response: any) => {
.map(response => new AccessTokenDto(response.access_token, response.token_type)) return new AccessTokenDto(response.access_token, response.token_type);
.catchError('Failed to create token. Please retry.'); })
.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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Response, ResponseOptions } from '@angular/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { Observable } from 'rxjs'; import { inject, TestBed } from '@angular/core/testing';
import { IMock, Mock, Times } from 'typemoq';
import { import {
ApiUrlConfig, ApiUrlConfig,
AppContributorDto, AppContributorDto,
AppContributorsDto, AppContributorsDto,
AppContributorsService, AppContributorsService,
AuthService,
Version Version
} from './../'; } from './../';
describe('AppContributorsService', () => { describe('AppContributorsService', () => {
let authService: IMock<AuthService>;
let appContributorsService: AppContributorsService;
let version = new Version('1'); let version = new Version('1');
beforeEach(() => { beforeEach(() => {
authService = Mock.ofType(AuthService); TestBed.configureTestingModule({
appContributorsService = new AppContributorsService(authService.object, new ApiUrlConfig('http://service/p/')); imports: [
HttpClientTestingModule
],
providers: [
AppContributorsService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
}); });
it('should make get request to get app contributors', () => { afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/contributors', version)) httpMock.verify();
.returns(() => Observable.of( }));
new Response(
new ResponseOptions({ it('should make get request to get app contributors',
body: { inject([AppContributorsService, HttpTestingController], (appContributorsService: AppContributorsService, httpMock: HttpTestingController) => {
contributors: [
{
contributorId: '123',
permission: 'Owner'
},
{
contributorId: '456',
permission: 'Owner'
}
],
maxContributors: 100
}
})
)
))
.verifiable(Times.once());
let contributors: AppContributorsDto | null = null; let contributors: AppContributorsDto | null = null;
appContributorsService.getContributors('my-app', version).subscribe(result => { appContributorsService.getContributors('my-app', version).subscribe(result => {
contributors = 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( expect(contributors).toEqual(
new AppContributorsDto([ new AppContributorsDto([
new AppContributorDto('123', 'Owner'), new AppContributorDto('123', 'Owner'),
new AppContributorDto('456', 'Owner') new AppContributorDto('456', 'Owner')
], 100)); ], 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'); const dto = new AppContributorDto('123', 'Owner');
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/contributors', dto, version)) appContributorsService.postContributor('my-app', dto, version).subscribe();
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
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', () => { req.flush({});
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());
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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig, Version } from 'framework'; import {
import { AuthService } from './auth.service'; ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppContributorDto { export class AppContributorDto {
constructor( constructor(
@ -32,7 +36,7 @@ export class AppContributorsDto {
@Injectable() @Injectable()
export class AppContributorsService { export class AppContributorsService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -40,8 +44,7 @@ export class AppContributorsService {
public getContributors(appName: string, version?: Version): Observable<AppContributorsDto> { public getContributors(appName: string, version?: Version): Observable<AppContributorsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return this.authService.authGet(url, version) return HTTP.getVersioned(this.http, url, version)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response.contributors; const items: any[] = response.contributors;
@ -53,20 +56,20 @@ export class AppContributorsService {
}), }),
response.maxContributors); 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> { public postContributor(appName: string, dto: AppContributorDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return this.authService.authPost(url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.catchError('Failed to add contributors. Please reload.'); .pretifyError('Failed to add contributors. Please reload.');
} }
public deleteContributor(appName: string, contributorId: string, version?: Version): Observable<any> { public deleteContributor(appName: string, contributorId: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to delete contributors. Please reload.'); .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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Response, ResponseOptions } from '@angular/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { Observable } from 'rxjs'; import { inject, TestBed } from '@angular/core/testing';
import { IMock, Mock, Times } from 'typemoq';
import { import {
AddAppLanguageDto, AddAppLanguageDto,
ApiUrlConfig, ApiUrlConfig,
AppLanguageDto, AppLanguageDto,
AppLanguagesService, AppLanguagesService,
AuthService,
UpdateAppLanguageDto, UpdateAppLanguageDto,
Version Version
} from './../'; } from './../';
describe('AppLanguagesService', () => { describe('AppLanguagesService', () => {
let authService: IMock<AuthService>;
let appLanguagesService: AppLanguagesService;
let version = new Version('1'); let version = new Version('1');
beforeEach(() => { beforeEach(() => {
authService = Mock.ofType(AuthService); TestBed.configureTestingModule({
appLanguagesService = new AppLanguagesService(authService.object, new ApiUrlConfig('http://service/p/')); imports: [
HttpClientTestingModule
],
providers: [
AppLanguagesService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
}); });
it('should make get request to get app languages', () => { afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/languages', version)) httpMock.verify();
.returns(() => Observable.of( }));
new Response(
new ResponseOptions({ it('should make get request to get app languages',
body: [ inject([AppLanguagesService, HttpTestingController], (appLanguagesService: AppLanguagesService, httpMock: HttpTestingController) => {
{
iso2Code: 'en',
englishName: 'English',
isMaster: true,
isOptional: true,
fallback: ['de', 'en']
},
{
iso2Code: 'it',
englishName: 'Italian'
}
]
})
)
))
.verifiable(Times.once());
let languages: AppLanguageDto[] | null = null; let languages: AppLanguageDto[] | null = null;
appLanguagesService.getLanguages('my-app', version).subscribe(result => { appLanguagesService.getLanguages('my-app', version).subscribe(result => {
languages = 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( expect(languages).toEqual(
[ [
new AppLanguageDto('en', 'English', true, true, ['de', 'en']), new AppLanguageDto('en', 'English', true, true, ['de', 'en']),
new AppLanguageDto('it', 'Italian', false, false, []) 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'); 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; let language: AppLanguageDto | null = null;
appLanguagesService.postLanguages('my-app', dto, version).subscribe(result => { appLanguagesService.postLanguages('my-app', dto, version).subscribe(result => {
language = 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( expect(language).toEqual(
new AppLanguageDto('de', 'German', false, false, [])); 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, []); const dto = new UpdateAppLanguageDto(true, true, []);
authService.setup(x => x.authPut('http://service/p/api/apps/my-app/languages/de', dto, version)) appLanguagesService.updateLanguage('my-app', 'de', dto, version).subscribe();
.returns(() => Observable.of(
new Response(
new ResponseOptions()
)
))
.verifiable(Times.once());
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', () => { req.flush({});
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());
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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig, Version } from 'framework'; import {
import { AuthService } from './auth.service'; ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppLanguageDto { export class AppLanguageDto {
constructor( constructor(
@ -43,7 +47,7 @@ export class UpdateAppLanguageDto {
@Injectable() @Injectable()
export class AppLanguagesService { export class AppLanguagesService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -51,8 +55,7 @@ export class AppLanguagesService {
public getLanguages(appName: string, version?: Version): Observable<AppLanguageDto[]> { public getLanguages(appName: string, version?: Version): Observable<AppLanguageDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return this.authService.authGet(url, version) return HTTP.getVersioned(this.http, url, version)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -65,14 +68,13 @@ export class AppLanguagesService {
item.fallback || []); 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> { public postLanguages(appName: string, dto: AddAppLanguageDto, version?: Version): Observable<AppLanguageDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return this.authService.authPost(url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => response.json())
.map(response => { .map(response => {
return new AppLanguageDto( return new AppLanguageDto(
response.iso2Code, response.iso2Code,
@ -81,20 +83,20 @@ export class AppLanguagesService {
response.isOptional === true, response.isOptional === true,
response.fallback || []); 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> { public updateLanguage(appName: string, languageCode: string, dto: UpdateAppLanguageDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return this.authService.authPut(url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.catchError('Failed to change language. Please reload.'); .pretifyError('Failed to change language. Please reload.');
} }
public deleteLanguage(appName: string, languageCode: string, version?: Version): Observable<any> { public deleteLanguage(appName: string, languageCode: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to add language. Please reload.'); .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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Response, ResponseOptions } from '@angular/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { Observable } from 'rxjs'; import { inject, TestBed } from '@angular/core/testing';
import { IMock, Mock, Times } from 'typemoq';
import { import {
ApiUrlConfig, ApiUrlConfig,
AppDto, AppDto,
AppsService, AppsService,
AuthService,
CreateAppDto, CreateAppDto,
DateTime, DateTime,
EntityCreatedDto EntityCreatedDto
} from './../'; } from './../';
describe('AppsService', () => { describe('AppsService', () => {
let authService: IMock<AuthService>;
let appsService: AppsService;
beforeEach(() => { beforeEach(() => {
authService = Mock.ofType(AuthService); TestBed.configureTestingModule({
appsService = new AppsService(authService.object, new ApiUrlConfig('http://service/p/')); imports: [
HttpClientTestingModule
],
providers: [
AppsService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
}); });
it('should make get request to get apps', () => { afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
authService.setup(x => x.authGet('http://service/p/api/apps')) httpMock.verify();
.returns(() => Observable.of( }));
new Response(
new ResponseOptions({ it('should make get request to get apps',
body: [ inject([AppsService, HttpTestingController], (appsService: AppsService, httpMock: HttpTestingController) => {
{
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());
let apps: AppDto[] | null = null; let apps: AppDto[] | null = null;
appsService.getApps().subscribe(result => { appsService.getApps().subscribe(result => {
apps = 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([ expect(apps).toEqual([
new AppDto('123', 'name1', 'Owner', DateTime.parseISO('2016-01-01'), DateTime.parseISO('2016-02-02')), 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')) 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'); 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; let created: EntityCreatedDto | null = null;
appsService.postApp(dto).subscribe(result => { appsService.postApp(dto).subscribe(result => {
created = 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -13,11 +14,10 @@ import 'framework/angular/http-extensions';
import { import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP,
EntityCreatedDto EntityCreatedDto
} from 'framework'; } from 'framework';
import { AuthService } from './auth.service';
export class AppDto { export class AppDto {
constructor( constructor(
public readonly id: string, public readonly id: string,
@ -39,7 +39,7 @@ export class CreateAppDto {
@Injectable() @Injectable()
export class AppsService { export class AppsService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -47,8 +47,7 @@ export class AppsService {
public getApps(): Observable<AppDto[]> { public getApps(): Observable<AppDto[]> {
const url = this.apiUrl.buildUrl('/api/apps'); const url = this.apiUrl.buildUrl('/api/apps');
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -61,17 +60,16 @@ export class AppsService {
DateTime.parseISO(item.lastModified)); DateTime.parseISO(item.lastModified));
}); });
}) })
.catchError('Failed to load apps. Please reload.'); .pretifyError('Failed to load apps. Please reload.');
} }
public postApp(dto: CreateAppDto): Observable<EntityCreatedDto> { public postApp(dto: CreateAppDto): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl('api/apps'); const url = this.apiUrl.buildUrl('api/apps');
return this.authService.authPost(url, dto) return HTTP.postVersioned(this.http, url, dto)
.map(response => response.json())
.map(response => { .map(response => {
return new EntityCreatedDto(response.id); 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient, HttpEventType, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Headers } from '@angular/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ProgressHttp } from 'angular-progress-http';
import { import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP,
Version Version
} from 'framework'; } from 'framework';
import { AuthService } from './auth.service';
export class AssetsDto { export class AssetsDto {
constructor( constructor(
public readonly total: number, public readonly total: number,
@ -83,9 +81,8 @@ export class AssetReplacedDto {
@Injectable() @Injectable()
export class AssetsService { export class AssetsService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig, private readonly apiUrl: ApiUrlConfig
private readonly http: ProgressHttp
) { ) {
} }
@ -111,8 +108,7 @@ export class AssetsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?${fullQuery}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?${fullQuery}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response.items; const items: any[] = response.items;
@ -133,38 +129,40 @@ export class AssetsService {
new Version(item.version.toString())); 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> { public uploadFile(appName: string, file: File): Observable<number | AssetCreatedDto> {
return new Observable<number | AssetCreatedDto>(subscriber => { return new Observable<number | AssetCreatedDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets`);
const content = new FormData(); const req = new HttpRequest('POST', url, getFormData(file), {
const headers = new Headers({ reportProgress: true
'Authorization': `${this.authService.user!.user.token_type} ${this.authService.user!.user.access_token}`
}); });
content.append('file', file); this.http.request(req)
.pretifyError('Failed to upload asset. Please reload.')
this.http.withUploadProgressListener(progress => subscriber.next(progress.percentage)) .subscribe(event => {
.post(url, content, { headers }) if (event.type === HttpEventType.UploadProgress) {
.map(response => response.json()) const percentDone = Math.round(100 * event.loaded / event.total);
.map(response => {
return new AssetCreatedDto( subscriber.next(percentDone);
response.id, } else if (event instanceof HttpResponse) {
response.fileName, const response = event.body;
response.fileSize,
response.fileVersion, const dto = new AssetCreatedDto(
response.mimeType, response.id,
response.isImage, response.fileName,
response.pixelWidth, response.fileSize,
response.pixelHeight, response.fileVersion,
new Version(response.version.toString())); response.mimeType,
}) response.isImage,
.catchError('Failed to upload asset. Please reload.') response.pixelWidth,
.subscribe(value => { response.pixelHeight,
subscriber.next(value); new Version(response.version.toString()));
subscriber.next(dto);
}
}, err => { }, err => {
subscriber.error(err); subscriber.error(err);
}, () => { }, () => {
@ -176,7 +174,7 @@ export class AssetsService {
public getAsset(appName: string, id: string, version?: Version): Observable<AssetDto> { public getAsset(appName: string, id: string, version?: Version): Observable<AssetDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`); 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 => response.json())
.map(response => { .map(response => {
return new AssetDto( return new AssetDto(
@ -194,40 +192,41 @@ export class AssetsService {
response.pixelHeight, response.pixelHeight,
new Version(response.version.toString())); 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> { public replaceFile(appName: string, id: string, file: File, version?: Version): Observable<number | AssetReplacedDto> {
return new Observable<number | AssetReplacedDto>(subscriber => { return new Observable<number | AssetReplacedDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}/content`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}/content`);
const content = new FormData(); const req = new HttpRequest('POST', url, file, {
const headers = new Headers({ headers: new HttpHeaders({
'Authorization': `${this.authService.user!.user.token_type} ${this.authService.user!.user.access_token}` 'If-Match': version.value
}),
reportProgress: true
}); });
if (version && version.value.length > 0) { this.http.request(req)
headers.append('If-Match', version.value); .pretifyError('Failed to replace asset. Please reload.')
} .subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
content.append('file', file); const percentDone = Math.round(100 * event.loaded / event.total);
this.http.withUploadProgressListener(progress => subscriber.next(progress.percentage)) subscriber.next(percentDone);
.put(url, content, { headers }) } else if (event instanceof HttpResponse) {
.map(response => response.json()) const response = event.body;
.map(response => {
return new AssetReplacedDto( const dto = new AssetReplacedDto(
response.fileSize, response.fileSize,
response.fileVersion, response.fileVersion,
response.mimeType, response.mimeType,
response.isImage, response.isImage,
response.pixelWidth, response.pixelWidth,
response.pixelHeight, response.pixelHeight,
new Version(response.version.toString())); new Version(response.version.toString()));
})
.catchError('Failed to replace asset. Please reload.') subscriber.next(dto);
.subscribe(value => { }
subscriber.next(value);
}, err => { }, err => {
subscriber.error(err); subscriber.error(err);
}, () => { }, () => {
@ -239,14 +238,22 @@ export class AssetsService {
public putAsset(appName: string, id: string, dto: UpdateAssetDto, version?: Version): Observable<any> { public putAsset(appName: string, id: string, dto: UpdateAssetDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return this.authService.authPut(url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.catchError('Failed to delete asset. Please reload.'); .pretifyError('Failed to delete asset. Please reload.');
} }
public deleteAsset(appName: string, id: string, version?: Version): Observable<any> { public deleteAsset(appName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to delete asset. Please reload.'); .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 { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions, Response } from '@angular/http';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { import {
@ -15,7 +14,7 @@ import {
UserManager UserManager
} from 'oidc-client'; } from 'oidc-client';
import { ApiUrlConfig, Version } from 'framework'; import { ApiUrlConfig } from 'framework';
export class Profile { export class Profile {
public get id(): string { public get id(): string {
@ -34,6 +33,14 @@ export class Profile {
return this.user.profile['role'] && this.user.profile['role'].toUpperCase() === 'ADMINISTRATOR'; 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 { public get token(): string {
return `subject:${this.id}`; return `subject:${this.id}`;
} }
@ -65,9 +72,7 @@ export class AuthService {
return this.isAuthenticatedChangedPublished$; return this.isAuthenticatedChangedPublished$;
} }
constructor(apiUrl: ApiUrlConfig, constructor(apiUrl: ApiUrlConfig) {
private readonly http: Http
) {
if (!apiUrl) { if (!apiUrl) {
return; return;
} }
@ -166,78 +171,4 @@ export class AuthService {
return resultPromise; 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -13,11 +14,10 @@ import 'framework/angular/http-extensions';
import { import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
HTTP,
Version Version
} from 'framework'; } from 'framework';
import { AuthService } from './auth.service';
export class ContentsDto { export class ContentsDto {
constructor( constructor(
public readonly total: number, public readonly total: number,
@ -43,7 +43,7 @@ export class ContentDto {
@Injectable() @Injectable()
export class ContentsService { export class ContentsService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig 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}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?nonPublished=true&hidden=true${fullQuery}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response.items; const items: any[] = response.items;
@ -92,14 +91,13 @@ export class ContentsService {
new Version(item.version.toString())); 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> { public getContent(appName: string, schemaName: string, id: string, version?: Version): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}?hidden=true`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}?hidden=true`);
return this.authService.authGet(url, version) return HTTP.getVersioned(this.http, url, version)
.map(response => response.json())
.map(response => { .map(response => {
return new ContentDto( return new ContentDto(
response.id, response.id,
@ -111,14 +109,13 @@ export class ContentsService {
response.data, response.data,
new Version(response.version.toString())); 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> { 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}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`);
return this.authService.authPost(url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => response.json())
.map(response => { .map(response => {
return new ContentDto( return new ContentDto(
response.id, response.id,
@ -130,34 +127,34 @@ export class ContentsService {
response.data, response.data,
new Version(response.version.toString())); 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> { public putContent(appName: string, schemaName: string, id: string, dto: any, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return this.authService.authPut(url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.catchError('Failed to update content. Please reload.'); .pretifyError('Failed to update content. Please reload.');
} }
public publishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> { public publishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/publish`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/publish`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to publish content. Please reload.'); .pretifyError('Failed to publish content. Please reload.');
} }
public unpublishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> { public unpublishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/unpublish`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/unpublish`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to unpublish content. Please reload.'); .pretifyError('Failed to unpublish content. Please reload.');
} }
public deleteContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> { public deleteContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to delete content. Please reload.'); .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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig } from 'framework'; import { ApiUrlConfig, HTTP } from 'framework';
import { AuthService } from './auth.service';
export class EventConsumerDto { export class EventConsumerDto {
constructor( constructor(
@ -27,7 +27,7 @@ export class EventConsumerDto {
@Injectable() @Injectable()
export class EventConsumersService { export class EventConsumersService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -35,8 +35,7 @@ export class EventConsumersService {
public getEventConsumers(): Observable<EventConsumerDto[]> { public getEventConsumers(): Observable<EventConsumerDto[]> {
const url = this.apiUrl.buildUrl('/api/event-consumers'); const url = this.apiUrl.buildUrl('/api/event-consumers');
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -49,27 +48,27 @@ export class EventConsumersService {
item.position); item.position);
}); });
}) })
.catchError('Failed to load event consumers. Please reload.'); .pretifyError('Failed to load event consumers. Please reload.');
} }
public startEventConsumer(name: string): Observable<any> { public startEventConsumer(name: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/start`); const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/start`);
return this.authService.authPut(url, {}) return HTTP.putVersioned(this.http, url, {})
.catchError('Failed to start event consumer. Please reload.'); .pretifyError('Failed to start event consumer. Please reload.');
} }
public stopEventConsumer(name: string): Observable<any> { public stopEventConsumer(name: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/stop`); const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/stop`);
return this.authService.authPut(url, {}) return HTTP.putVersioned(this.http, url, {})
.catchError('Failed to stop event consumer. Please reload.'); .pretifyError('Failed to stop event consumer. Please reload.');
} }
public resetEventConsumer(name: string): Observable<any> { public resetEventConsumer(name: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/reset`); const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/reset`);
return this.authService.authPut(url, {}) return HTTP.putVersioned(this.http, url, {})
.catchError('Failed to reset event consumer. Please reload.'); .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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Injectable() @Injectable()
export class HelpService { export class HelpService {
constructor( 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`; const url = `https://api.gitbook.com/book/squidex/squidex/contents/${helpPage}.json`;
return this.http.get(url) return this.http.get(url)
.map(response => response.json()) .map((response: any) => {
.map(response => {
const result: string[] = []; const result: string[] = [];
for (let section of response.sections) { 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig, DateTime } from 'framework'; import {
import { AuthService } from './auth.service'; ApiUrlConfig,
HTTP,
DateTime
} from 'framework';
export class HistoryEventDto { export class HistoryEventDto {
constructor( constructor(
@ -26,7 +30,7 @@ export class HistoryEventDto {
@Injectable() @Injectable()
export class HistoryService { export class HistoryService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -34,8 +38,7 @@ export class HistoryService {
public getHistory(appName: string, channel: string): Observable<HistoryEventDto[]> { public getHistory(appName: string, channel: string): Observable<HistoryEventDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/history?channel=${channel}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/history?channel=${channel}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -47,6 +50,6 @@ export class HistoryService {
DateTime.parseISO_UTC(item.created)); 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig } from 'framework'; import { ApiUrlConfig, HTTP } from 'framework';
import { AuthService } from './auth.service';
export class LanguageDto { export class LanguageDto {
constructor( constructor(
@ -24,7 +24,7 @@ export class LanguageDto {
@Injectable() @Injectable()
export class LanguageService { export class LanguageService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -32,8 +32,7 @@ export class LanguageService {
public getLanguages(): Observable<LanguageDto[]> { public getLanguages(): Observable<LanguageDto[]> {
const url = this.apiUrl.buildUrl('api/languages'); const url = this.apiUrl.buildUrl('api/languages');
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -43,6 +42,6 @@ export class LanguageService {
item.englishName); 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig, Version } from 'framework'; import {
import { AuthService } from './auth.service'; ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class AppPlansDto { export class AppPlansDto {
constructor( constructor(
@ -46,7 +50,7 @@ export class ChangePlanDto {
@Injectable() @Injectable()
export class PlansService { export class PlansService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -54,8 +58,7 @@ export class PlansService {
public getPlans(appName: string, version?: Version): Observable<AppPlansDto> { public getPlans(appName: string, version?: Version): Observable<AppPlansDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plans`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/plans`);
return this.authService.authGet(url, version) return HTTP.getVersioned(this.http, url, version)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response.plans; const items: any[] = response.plans;
@ -74,13 +77,13 @@ export class PlansService {
item.maxContributors); 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> { public putPlan(appName: string, dto: ChangePlanDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`);
return this.authService.authPut(url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.catchError('Failed to change plan. Please reload.'); .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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ValidatorFn, Validators } from '@angular/forms'; import { ValidatorFn, Validators } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -15,12 +16,11 @@ import {
ApiUrlConfig, ApiUrlConfig,
DateTime, DateTime,
EntityCreatedDto, EntityCreatedDto,
HTTP,
ValidatorsEx, ValidatorsEx,
Version Version
} from 'framework'; } from 'framework';
import { AuthService } from './auth.service';
export const fieldTypes: string[] = [ export const fieldTypes: string[] = [
'Assets', 'Assets',
'Boolean', 'Boolean',
@ -483,7 +483,7 @@ export class CreateSchemaDto {
@Injectable() @Injectable()
export class SchemasService { export class SchemasService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -491,8 +491,7 @@ export class SchemasService {
public getSchemas(appName: string): Observable<SchemaDto[]> { public getSchemas(appName: string): Observable<SchemaDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -510,14 +509,13 @@ export class SchemasService {
new Version(item.version.toString())); 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> { public getSchema(appName: string, id: string, version?: Version): Observable<SchemaDetailsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${id}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const fields = response.fields.map((item: any) => { const fields = response.fields.map((item: any) => {
const propertiesDto = const propertiesDto =
@ -547,105 +545,103 @@ export class SchemasService {
new Version(response.version.toString()), new Version(response.version.toString()),
fields); 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> { public postSchema(appName: string, dto: CreateSchemaDto, version?: Version): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return this.authService.authPost(url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => response.json())
.map(response => { .map(response => {
return new EntityCreatedDto(response.id); 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> { public postField(appName: string, schemaName: string, dto: AddFieldDto, version?: Version): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`);
return this.authService.authPost(url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => response.json())
.map(response => { .map(response => {
return new EntityCreatedDto(response.id); 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> { public putSchema(appName: string, schemaName: string, dto: UpdateSchemaDto, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`);
return this.authService.authPut(url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.catchError('Failed to update schema. Please reload.'); .pretifyError('Failed to update schema. Please reload.');
} }
public putFieldOrdering(appName: string, schemaName: string, dto: number[], version?: Version): Observable<any> { public putFieldOrdering(appName: string, schemaName: string, dto: number[], version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/ordering`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/ordering`);
return this.authService.authPut(url, { fieldIds: dto }, version) return HTTP.putVersioned(this.http, url, { fieldIds: dto }, version)
.catchError('Failed to reorder fields. Please reload.'); .pretifyError('Failed to reorder fields. Please reload.');
} }
public publishSchema(appName: string, schemaName: string, version?: Version): Observable<any> { public publishSchema(appName: string, schemaName: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to publish schema. Please reload.'); .pretifyError('Failed to publish schema. Please reload.');
} }
public unpublishSchema(appName: string, schemaName: string, version?: Version): Observable<any> { public unpublishSchema(appName: string, schemaName: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/unpublish`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/unpublish`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to unpublish schema. Please reload.'); .pretifyError('Failed to unpublish schema. Please reload.');
} }
public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto, version?: Version): Observable<any> { 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}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`);
return this.authService.authPut(url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.catchError('Failed to update field. Please reload.'); .pretifyError('Failed to update field. Please reload.');
} }
public enableField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { 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`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/enable`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to enable field. Please reload.'); .pretifyError('Failed to enable field. Please reload.');
} }
public disableField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { 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`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/disable`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to disable field. Please reload.'); .pretifyError('Failed to disable field. Please reload.');
} }
public showField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { 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`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to show field. Please reload.'); .pretifyError('Failed to show field. Please reload.');
} }
public hideField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { 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`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/hide`);
return this.authService.authPut(url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.catchError('Failed to hide field. Please reload.'); .pretifyError('Failed to hide field. Please reload.');
} }
public deleteField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public deleteField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to delete field. Please reload.'); .pretifyError('Failed to delete field. Please reload.');
} }
public deleteSchema(appName: string, schemaName: string, version?: Version): Observable<any> { public deleteSchema(appName: string, schemaName: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to delete schema. Please reload.'); .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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig, DateTime } from 'framework'; import {
import { AuthService } from './auth.service'; ApiUrlConfig,
DateTime,
HTTP
} from 'framework';
export class CallsUsageDto { export class CallsUsageDto {
constructor( constructor(
@ -50,7 +54,7 @@ export class CurrentCallsDto {
@Injectable() @Injectable()
export class UsagesService { export class UsagesService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -58,26 +62,27 @@ export class UsagesService {
public getMonthCalls(app: string): Observable<CurrentCallsDto> { public getMonthCalls(app: string): Observable<CurrentCallsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/month`); const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/month`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json()) .map(response => {
.map(response => new CurrentCallsDto(response.count, response.maxAllowed)) return new CurrentCallsDto(response.count, response.maxAllowed);
.catchError('Failed to load monthly api calls. Please reload.'); })
.pretifyError('Failed to load monthly api calls. Please reload.');
} }
public getTodayStorage(app: string): Observable<CurrentStorageDto> { public getTodayStorage(app: string): Observable<CurrentStorageDto> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/today`); const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/today`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json()) .map(response => {
.map(response => new CurrentStorageDto(response.size, response.maxAllowed)) return new CurrentStorageDto(response.size, response.maxAllowed);
.catchError('Failed to load todays storage size. Please reload.'); })
.pretifyError('Failed to load todays storage size. Please reload.');
} }
public getCallsUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<CallsUsageDto[]> { 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')}`); 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) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -88,14 +93,13 @@ export class UsagesService {
item.averageMs); 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[]> { 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')}`); 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) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -106,6 +110,6 @@ export class UsagesService {
item.size); 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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig } from 'framework'; import { ApiUrlConfig, HTTP } from 'framework';
import { AuthService } from './auth.service';
export class UsersDto { export class UsersDto {
constructor( constructor(
@ -61,7 +61,7 @@ export class UserDto {
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -69,8 +69,7 @@ export class UsersService {
public getUsers(query?: string): Observable<UserDto[]> { public getUsers(query?: string): Observable<UserDto[]> {
const url = this.apiUrl.buildUrl(`api/users?query=${query || ''}`); const url = this.apiUrl.buildUrl(`api/users?query=${query || ''}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -83,14 +82,13 @@ export class UsersService {
item.isLocked); item.isLocked);
}); });
}) })
.catchError('Failed to load users. Please reload.'); .pretifyError('Failed to load users. Please reload.');
} }
public getUser(id: string): Observable<UserDto> { public getUser(id: string): Observable<UserDto> {
const url = this.apiUrl.buildUrl(`api/users/${id}`); const url = this.apiUrl.buildUrl(`api/users/${id}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
return new UserDto( return new UserDto(
response.id, response.id,
@ -99,14 +97,14 @@ export class UsersService {
response.pictureUrl, response.pictureUrl,
response.isLocked); response.isLocked);
}) })
.catchError('Failed to load user. Please reload.'); .pretifyError('Failed to load user. Please reload.');
} }
} }
@Injectable() @Injectable()
export class UserManagementService { export class UserManagementService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -114,8 +112,7 @@ export class UserManagementService {
public getUsers(take: number, skip: number, query?: string): Observable<UsersDto> { public getUsers(take: number, skip: number, query?: string): Observable<UsersDto> {
const url = this.apiUrl.buildUrl(`api/user-management?take=${take}&skip=${skip}&query=${query || ''}`); const url = this.apiUrl.buildUrl(`api/user-management?take=${take}&skip=${skip}&query=${query || ''}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response.items; const items: any[] = response.items;
@ -130,14 +127,13 @@ export class UserManagementService {
return new UsersDto(response.total, users); 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> { public getUser(id: string): Observable<UserDto> {
const url = this.apiUrl.buildUrl(`api/user-management/${id}`); const url = this.apiUrl.buildUrl(`api/user-management/${id}`);
return this.authService.authGet(url) return HTTP.getVersioned(this.http, url)
.map(response => response.json())
.map(response => { .map(response => {
return new UserDto( return new UserDto(
response.id, response.id,
@ -146,36 +142,37 @@ export class UserManagementService {
response.pictureUrl, response.pictureUrl,
response.isLocked); response.isLocked);
}) })
.catchError('Failed to load user. Please reload.'); .pretifyError('Failed to load user. Please reload.');
} }
public postUser(dto: CreateUserDto): Observable<UserDto> { public postUser(dto: CreateUserDto): Observable<UserDto> {
const url = this.apiUrl.buildUrl('api/user-management'); const url = this.apiUrl.buildUrl('api/user-management');
return this.authService.authPost(url, dto) return HTTP.postVersioned(this.http, url, dto)
.map(response => response.json()) .map(response => {
.map(response => new UserCreatedDto(response.id, response.pictureUrl)) return new UserCreatedDto(response.id, response.pictureUrl);
.catchError('Failed to create user. Please reload.'); })
.pretifyError('Failed to create user. Please reload.');
} }
public putUser(id: string, dto: UpdateUserDto): Observable<any> { public putUser(id: string, dto: UpdateUserDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/user-management/${id}`); const url = this.apiUrl.buildUrl(`api/user-management/${id}`);
return this.authService.authPut(url, dto) return HTTP.putVersioned(this.http, url, dto)
.catchError('Failed to update user. Please reload.'); .pretifyError('Failed to update user. Please reload.');
} }
public lockUser(id: string): Observable<any> { public lockUser(id: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/user-management/${id}/lock`); const url = this.apiUrl.buildUrl(`api/user-management/${id}/lock`);
return this.authService.authPut(url, {}) return HTTP.putVersioned(this.http, url, {})
.catchError('Failed to load users. Please retry.'); .pretifyError('Failed to load users. Please retry.');
} }
public unlockUser(id: string): Observable<any> { public unlockUser(id: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/user-management/${id}/unlock`); const url = this.apiUrl.buildUrl(`api/user-management/${id}/unlock`);
return this.authService.authPut(url, {}) return HTTP.putVersioned(this.http, url, {})
.catchError('Failed to load users. Please retry.'); .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 * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import 'framework/angular/http-extensions'; import 'framework/angular/http-extensions';
import { ApiUrlConfig, Version } from 'framework'; import {
import { AuthService } from './auth.service'; ApiUrlConfig,
HTTP,
Version
} from 'framework';
export class WebhookDto { export class WebhookDto {
constructor( constructor(
@ -46,7 +50,7 @@ export class CreateWebhookDto {
@Injectable() @Injectable()
export class WebhooksService { export class WebhooksService {
constructor( constructor(
private readonly authService: AuthService, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
} }
@ -54,8 +58,7 @@ export class WebhooksService {
public getWebhooks(appName: string, version?: Version): Observable<WebhookDto[]> { public getWebhooks(appName: string, version?: Version): Observable<WebhookDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`);
return this.authService.authGet(url, version) return HTTP.getVersioned(this.http, url, version)
.map(response => response.json())
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -72,26 +75,25 @@ export class WebhooksService {
item.lastDumps); 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> { public postWebhook(appName: string, schemaName: string, dto: CreateWebhookDto, version?: Version): Observable<WebhookCreatedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks`);
return this.authService.authPost(url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.map(response => response.json())
.map(response => { .map(response => {
return new WebhookCreatedDto( return new WebhookCreatedDto(
response.id, response.id,
response.sharedSecret); 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> { public deleteWebhook(appName: string, schemaName: string, id: string, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/webhooks/${id}`);
return this.authService.authDelete(url, version) return HTTP.deleteVersioned(this.http, url, version)
.catchError('Failed to delete webhook. Please reload.'); .pretifyError('Failed to delete webhook. Please reload.');
} }
} }

35
src/Squidex/package.json

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

4
src/Squidex/tslint.json

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

Loading…
Cancel
Save