mirror of https://github.com/Squidex/squidex.git
83 changed files with 1468 additions and 521 deletions
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// AppClient.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Write.Apps |
|||
{ |
|||
public sealed class AppClient |
|||
{ |
|||
public string ClientName { get; } |
|||
|
|||
public string ClientSecret { get; } |
|||
|
|||
public DateTime ExpiresUtc { get; } |
|||
|
|||
public AppClient(string name, string secret, DateTime expiresUtc) |
|||
{ |
|||
Guard.NotNullOrEmpty(name, nameof(name)); |
|||
Guard.NotNullOrEmpty(secret, nameof(secret)); |
|||
|
|||
ClientName = name; |
|||
ClientSecret = secret; |
|||
|
|||
ExpiresUtc = expiresUtc; |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// ==========================================================================
|
|||
// CreateFieldDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace Squidex.Modules.Api.Schemas.Models |
|||
{ |
|||
public class CreateFieldDto |
|||
{ |
|||
[JsonProperty("$type")] |
|||
public string Type { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public JObject Properties { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// ==========================================================================
|
|||
// FieldDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Runtime.Serialization; |
|||
using Newtonsoft.Json; |
|||
using NJsonSchema.Converters; |
|||
using Squidex.Modules.Api.Schemas.Models.Fields; |
|||
|
|||
namespace Squidex.Modules.Api.Schemas.Models |
|||
{ |
|||
[JsonConverter(typeof(JsonInheritanceConverter), "fieldType")] |
|||
[KnownType(typeof(NumberField))] |
|||
[KnownType(typeof(StringField))] |
|||
public class FieldDto |
|||
{ |
|||
/// <summary>
|
|||
/// The name of the field. Must be unique within the schema.
|
|||
/// </summary>
|
|||
[Required] |
|||
[RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")]
|
|||
public string Name { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Optional label for the editor.
|
|||
/// </summary>
|
|||
[StringLength(100)] |
|||
public string Label { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Hints to describe the schema.
|
|||
/// </summary>
|
|||
[StringLength(1000)] |
|||
public string Hints { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Placeholder to show when no value has been entered.
|
|||
/// </summary>
|
|||
[StringLength(100)] |
|||
public string Placeholder { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Indicates if the field is required.
|
|||
/// </summary>
|
|||
public bool IsRequired { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// NumberField.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Modules.Api.Schemas.Models.Fields |
|||
{ |
|||
public class NumberField : FieldDto |
|||
{ |
|||
/// <summary>
|
|||
/// The default value for the field value.
|
|||
/// </summary>
|
|||
public double? DefaultValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The maximum allowed value for the field value.
|
|||
/// </summary>
|
|||
public double? MaxValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The minimum allowed value for the field value.
|
|||
/// </summary>
|
|||
public double? MinValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The allowed values for the field value.
|
|||
/// </summary>
|
|||
public double[] AllowedValues { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// ==========================================================================
|
|||
// StringField.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Modules.Api.Schemas.Models.Fields |
|||
{ |
|||
public sealed class StringField : FieldDto |
|||
{ |
|||
/// <summary>
|
|||
/// The default value for the field value.
|
|||
/// </summary>
|
|||
public string DefaultValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The pattern to enforce a specific format for the field value.
|
|||
/// </summary>
|
|||
public string Pattern { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The minimum allowed length for the field value.
|
|||
/// </summary>
|
|||
public int? MinLength { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The maximum allowed length for the field value.
|
|||
/// </summary>
|
|||
public int? MaxLength { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The allowed values for the field value.
|
|||
/// </summary>
|
|||
public double[] AllowedValues { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// ==========================================================================
|
|||
// SchemaDetailsDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Squidex.Modules.Api.Schemas.Models |
|||
{ |
|||
public sealed class SchemaDetailsDto |
|||
{ |
|||
/// <summary>
|
|||
/// The id of the schema.
|
|||
/// </summary>
|
|||
public Guid Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The name of the schema. Unique within the app.
|
|||
/// </summary>
|
|||
[Required] |
|||
[RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")]
|
|||
public string Name { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The list of fields.
|
|||
/// </summary>
|
|||
[Required] |
|||
public List<FieldDto> Fields { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Optional label for the editor.
|
|||
/// </summary>
|
|||
[StringLength(100)] |
|||
public string Label { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Hints to describe the schema.
|
|||
/// </summary>
|
|||
[StringLength(1000)] |
|||
public string Hints { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The date and time when the schema has been creaed.
|
|||
/// </summary>
|
|||
public DateTime Created { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The date and time when the schema has been modified last.
|
|||
/// </summary>
|
|||
public DateTime LastModified { get; set; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// UpdateFieldDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace Squidex.Modules.Api.Schemas.Models |
|||
{ |
|||
public class UpdateFieldDto |
|||
{ |
|||
public JObject Properties { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
<div class="layout"> |
|||
<div class="layout-left"> |
|||
<sqx-left-menu></sqx-left-menu> |
|||
</div> |
|||
<div class="layout-middle"> |
|||
<div class="layout-middle-header"> |
|||
<h1> |
|||
<i class="layout-title-icon icon-settings"></i> Clients |
|||
</h1> |
|||
</div> |
|||
<div class="layout-middle-content"> |
|||
<div class="card"> |
|||
<div class="card-block"> |
|||
<div class="clients-empty" *ngIf="appClients && appClients.length === 0"> |
|||
No client created yet. |
|||
</div> |
|||
|
|||
<table class="table table-borderless-top table-fixed clients-table"> |
|||
<colgroup> |
|||
<col style="width: 100%" /> |
|||
<col style="width: 160px" /> |
|||
</colgroup> |
|||
|
|||
<tr *ngFor="let client of appClients"> |
|||
<td> |
|||
<table class="table table-middle table-sm table-borderless table-fixed client-info"> |
|||
<colgroup> |
|||
<col style="width: 160px; text-align: right;" /> |
|||
<col style="width: 100%" /> |
|||
</colgroup> |
|||
<tr> |
|||
<td>Client Name:</td> |
|||
<td> |
|||
<input readonly class="form-control" [attr.value]="fullAppName(client)" /> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<td>Client Secret:</td> |
|||
<td> |
|||
<input readonly class="form-control" [attr.value]="client.clientSecret" /> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<td>Expires:</td> |
|||
<td class="client-expires"> |
|||
{{client.expiresUtc}} |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
|
|||
</td> |
|||
<td> |
|||
<button class="btn btn-block btn-danger client-delete" (click)="revokeClient(client)">Revoke</button> |
|||
<button class="btn btn-block btn-default">Create Token</button> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
</div> |
|||
<div class="card-footer"> |
|||
<div *ngIf="creationError" [@fade]> |
|||
<div class="form-error"> |
|||
{{creationError}} |
|||
</div> |
|||
</div> |
|||
|
|||
<form class="form-inline" [formGroup]="createForm" (submit)="attachClient()"> |
|||
<div class="errors-box" *ngIf="createForm.get('name').invalid && createForm.get('name').dirty"> |
|||
<div class="errors"> |
|||
<span *ngIf="createForm.get('name').hasError('required')"> |
|||
Name is required. |
|||
</span> |
|||
<span *ngIf="createForm.get('name').hasError('maxlength')"> |
|||
Name can not have more than 40 characters. |
|||
</span> |
|||
<span *ngIf="createForm.get('name').hasError('pattern')"> |
|||
Name can contain lower case letters (a-z), numbers and dashes (not at the end). |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<input type="text" class="form-control" id="app-name" formControlName="name" placeholder="Enter client name" /> |
|||
</div> |
|||
|
|||
<button type="submit" class="btn btn-success" [disabled]="createForm.invalid">Create Client</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,44 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.layout-title-icon { |
|||
color: $color-section-settings; |
|||
} |
|||
|
|||
.card { |
|||
& { |
|||
max-width: 700px; |
|||
} |
|||
|
|||
&-block { |
|||
padding-left: .5rem; |
|||
padding-right: .5rem; |
|||
} |
|||
} |
|||
|
|||
.clients { |
|||
&-empty { |
|||
color: $color-empty; |
|||
font-size: 1.2rem; |
|||
font-weight: lighter; |
|||
padding: .2rem 1rem; |
|||
} |
|||
|
|||
&-table { |
|||
margin: 0; |
|||
} |
|||
} |
|||
|
|||
.client { |
|||
&-delete { |
|||
margin-top: .3rem; |
|||
} |
|||
|
|||
&-info { |
|||
margin: 0; |
|||
} |
|||
|
|||
&-expires { |
|||
padding: .3rem .8rem; |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Ng2 from '@angular/core'; |
|||
import * as Ng2Forms from '@angular/forms'; |
|||
|
|||
import { |
|||
AppsStoreService, |
|||
AppClientDto, |
|||
AppClientCreateDto, |
|||
AppClientsService, |
|||
fadeAnimation, |
|||
TitleService |
|||
} from 'shared'; |
|||
|
|||
@Ng2.Component({ |
|||
selector: 'sqx-clients-page', |
|||
styles, |
|||
template, |
|||
animations: [ |
|||
fadeAnimation() |
|||
] |
|||
}) |
|||
export class ClientsPageComponent implements Ng2.OnInit { |
|||
private appSubscription: any | null = null; |
|||
private appName: string | null = null; |
|||
|
|||
public appClients: AppClientDto[]; |
|||
|
|||
public creationError = ''; |
|||
public createForm = |
|||
this.formBuilder.group({ |
|||
name: ['', |
|||
[ |
|||
Ng2Forms.Validators.required, |
|||
Ng2Forms.Validators.maxLength(40), |
|||
Ng2Forms.Validators.pattern('[a-z0-9]+(\-[a-z0-9]+)*') |
|||
]] |
|||
}); |
|||
|
|||
constructor( |
|||
private readonly titles: TitleService, |
|||
private readonly appsStore: AppsStoreService, |
|||
private readonly appClientsService: AppClientsService, |
|||
private readonly formBuilder: Ng2Forms.FormBuilder |
|||
) { |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.appSubscription = |
|||
this.appsStore.selectedApp.subscribe(app => { |
|||
if (app) { |
|||
this.appName = app.name; |
|||
|
|||
this.titles.setTitle('{appName} | Settings | Clients', { appName: app.name }); |
|||
|
|||
this.appClientsService.getClients(app.name).subscribe(clients => { |
|||
this.appClients = clients; |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public ngOnDestroy() { |
|||
this.appSubscription.unsubscribe(); |
|||
} |
|||
|
|||
public fullAppName(client: AppClientDto): string { |
|||
return this.appName + ':' + client.clientName; |
|||
} |
|||
|
|||
public revokeClient(client: AppClientDto) { |
|||
this.appClientsService.deleteClient(this.appName, client.clientName).subscribe(); |
|||
|
|||
this.appClients.splice(this.appClients.indexOf(client), 1); |
|||
} |
|||
|
|||
public attachClient() { |
|||
this.createForm.markAsDirty(); |
|||
|
|||
if (this.createForm.valid) { |
|||
this.createForm.disable(); |
|||
|
|||
const dto = new AppClientCreateDto(this.createForm.controls['name'].value); |
|||
|
|||
this.appClientsService.postClient(this.appName, dto) |
|||
.subscribe(client => { |
|||
this.reset(); |
|||
this.appClients.push(client); |
|||
}, error => { |
|||
this.reset(); |
|||
this.creationError = error; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private reset() { |
|||
this.createForm.reset(); |
|||
this.createForm.enable(); |
|||
this.creationError = ''; |
|||
} |
|||
} |
|||
|
|||
@ -1,40 +0,0 @@ |
|||
<div class="layout"> |
|||
<div class="layout-left"> |
|||
<sqx-left-menu></sqx-left-menu> |
|||
</div> |
|||
<div class="layout-middle"> |
|||
<div class="layout-middle-header"> |
|||
<h1> |
|||
<i class="layout-title-icon icon-settings"></i> Credentials |
|||
</h1> |
|||
</div> |
|||
<div class="layout-middle-content"> |
|||
<div class="card"> |
|||
<div class="card-block"> |
|||
<table class="table table-borderless table-fixed"> |
|||
<colgroup> |
|||
<col style="width: 100%" /> |
|||
<col style="width: 160px" /> |
|||
<col style="width: 120px" /> |
|||
</colgroup> |
|||
|
|||
<tr *ngFor="let clientKey of appClientKeys"> |
|||
<td> |
|||
<input readonly class="form-control" [ngModel]="clientKey.clientKey" /> |
|||
</td> |
|||
<td> |
|||
<button class="btn btn-block btn-default">Create Token</button> |
|||
</td> |
|||
<td> |
|||
<button class="btn btn-block btn-danger">Revoke</button> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
</div> |
|||
<div class="card-footer"> |
|||
<button type="submit" class="btn btn-primary" (click)="createClientKey()">Create Client Key</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -1,17 +0,0 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.layout-title-icon { |
|||
color: $color-section-settings; |
|||
} |
|||
|
|||
.card { |
|||
& { |
|||
max-width: 700px; |
|||
} |
|||
|
|||
&-block { |
|||
padding-left: .5rem; |
|||
padding-right: .5rem; |
|||
} |
|||
} |
|||
@ -1,60 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Ng2 from '@angular/core'; |
|||
|
|||
import { |
|||
AppsStoreService, |
|||
AppClientKeyDto, |
|||
AppClientKeysService, |
|||
TitleService |
|||
} from 'shared'; |
|||
|
|||
@Ng2.Component({ |
|||
selector: 'sqx-credentials-page', |
|||
styles, |
|||
template |
|||
}) |
|||
export class CredentialsPageComponent implements Ng2.OnInit { |
|||
private appSubscription: any | null = null; |
|||
private appName: string | null = null; |
|||
|
|||
public appClientKeys: AppClientKeyDto[] = []; |
|||
|
|||
constructor( |
|||
private readonly titles: TitleService, |
|||
private readonly appsStore: AppsStoreService, |
|||
private readonly appClientKeysService: AppClientKeysService |
|||
) { |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.appSubscription = |
|||
this.appsStore.selectedApp.subscribe(app => { |
|||
if (app) { |
|||
this.appName = app.name; |
|||
|
|||
this.titles.setTitle('{appName} | Settings | Credentials', { appName: app.name }); |
|||
|
|||
this.appClientKeysService.getClientKeys(app.name).subscribe(clientKeys => { |
|||
this.appClientKeys = clientKeys; |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public ngOnDestroy() { |
|||
this.appSubscription.unsubscribe(); |
|||
} |
|||
|
|||
public createClientKey() { |
|||
this.appClientKeysService.postClientKey(this.appName).subscribe(clientKey => { |
|||
this.appClientKeys.push(clientKey); |
|||
}) |
|||
} |
|||
} |
|||
|
|||
@ -1,52 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Ng2 from '@angular/core'; |
|||
import * as Ng2Http from '@angular/http'; |
|||
|
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { ApiUrlConfig, DateTime } from 'framework'; |
|||
import { AuthService } from './auth.service'; |
|||
|
|||
export class AppClientKeyDto { |
|||
constructor( |
|||
public readonly clientKey: string, |
|||
public readonly expiresUtc: DateTime |
|||
) { |
|||
} |
|||
} |
|||
|
|||
@Ng2.Injectable() |
|||
export class AppClientKeysService { |
|||
constructor( |
|||
private readonly authService: AuthService, |
|||
private readonly apiUrl: ApiUrlConfig, |
|||
private readonly http: Ng2Http.Http |
|||
) { |
|||
} |
|||
|
|||
public getClientKeys(appName: string): Observable<AppClientKeyDto[]> { |
|||
return this.authService.authGet(this.apiUrl.buildUrl(`api/apps/${appName}/client-keys`)) |
|||
.map(response => { |
|||
const body: any[] = response.json(); |
|||
|
|||
return body.map(item => { |
|||
return new AppClientKeyDto(item.clientKey, DateTime.parseISO_UTC(item.expiresUtc)); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public postClientKey(appName: string): Observable<AppClientKeyDto> { |
|||
return this.authService.authPost(this.apiUrl.buildUrl(`api/apps/${appName}/client-keys`), {}) |
|||
.map(response => { |
|||
const body = response.json(); |
|||
|
|||
return new AppClientKeyDto(body.clientKey, DateTime.now().addYears(1)); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as TypeMoq from 'typemoq'; |
|||
import * as Ng2Http from '@angular/http'; |
|||
|
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { |
|||
ApiUrlConfig, |
|||
AppClientDto, |
|||
AppClientsService, |
|||
AuthService, |
|||
AppClientCreateDto, |
|||
DateTime |
|||
} from './../'; |
|||
|
|||
describe('AppClientsService', () => { |
|||
let authService: TypeMoq.Mock<AuthService>; |
|||
let appClientsService: AppClientsService; |
|||
|
|||
beforeEach(() => { |
|||
authService = TypeMoq.Mock.ofType(AuthService); |
|||
appClientsService = new AppClientsService(authService.object, new ApiUrlConfig('http://service/p/')); |
|||
}); |
|||
|
|||
it('should make get request with auth service to get app clients', () => { |
|||
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/clients')) |
|||
.returns(() => Observable.of( |
|||
new Ng2Http.Response( |
|||
new Ng2Http.ResponseOptions({ |
|||
body: [{ |
|||
clientName: 'client1', |
|||
clientSecret: 'secret1', |
|||
expiresUtc: '2016-12-12T10:10' |
|||
}, { |
|||
clientName: 'client2', |
|||
clientSecret: 'secret2', |
|||
expiresUtc: '2016-11-11T10:10' |
|||
}] |
|||
}) |
|||
) |
|||
)) |
|||
.verifiable(TypeMoq.Times.once()); |
|||
|
|||
let clients: AppClientDto[] = null; |
|||
|
|||
appClientsService.getClients('my-app').subscribe(result => { |
|||
clients = result; |
|||
}).unsubscribe(); |
|||
|
|||
expect(clients).toEqual( |
|||
[ |
|||
new AppClientDto('client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10')), |
|||
new AppClientDto('client2', 'secret2', DateTime.parseISO_UTC('2016-11-11T10:10')), |
|||
]); |
|||
|
|||
authService.verifyAll(); |
|||
}); |
|||
|
|||
it('should make post request to create client', () => { |
|||
const createClient = new AppClientCreateDto('client1'); |
|||
|
|||
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/clients', TypeMoq.It.is(c => c === createClient))) |
|||
.returns(() => Observable.of( |
|||
new Ng2Http.Response( |
|||
new Ng2Http.ResponseOptions({ |
|||
body: { |
|||
clientName: 'client1', |
|||
clientSecret: 'secret1', |
|||
expiresUtc: '2016-12-12T10:10' |
|||
} |
|||
}) |
|||
) |
|||
)) |
|||
.verifiable(TypeMoq.Times.once()); |
|||
|
|||
let client: AppClientDto = null; |
|||
|
|||
appClientsService.postClient('my-app', createClient).subscribe(result => { |
|||
client = result; |
|||
}); |
|||
|
|||
expect(client).toEqual( |
|||
new AppClientDto('client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10'))); |
|||
|
|||
authService.verifyAll(); |
|||
}); |
|||
|
|||
it('should make delete request to remove client', () => { |
|||
authService.setup(x => x.authDelete('http://service/p/api/apps/my-app/clients/client1')) |
|||
.returns(() => Observable.of( |
|||
new Ng2Http.Response( |
|||
new Ng2Http.ResponseOptions() |
|||
) |
|||
)) |
|||
.verifiable(TypeMoq.Times.once()); |
|||
|
|||
appClientsService.deleteClient('my-app', 'client1'); |
|||
|
|||
authService.verifyAll(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,67 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Ng2 from '@angular/core'; |
|||
|
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { ApiUrlConfig, DateTime } from 'framework'; |
|||
import { AuthService } from './auth.service'; |
|||
|
|||
export class AppClientDto { |
|||
constructor( |
|||
public readonly clientName: string, |
|||
public readonly clientSecret: string, |
|||
public readonly expiresUtc: DateTime |
|||
) { |
|||
} |
|||
} |
|||
|
|||
export class AppClientCreateDto { |
|||
constructor( |
|||
public readonly clientName: string |
|||
) { |
|||
} |
|||
} |
|||
|
|||
@Ng2.Injectable() |
|||
export class AppClientsService { |
|||
constructor( |
|||
private readonly authService: AuthService, |
|||
private readonly apiUrl: ApiUrlConfig |
|||
) { |
|||
} |
|||
|
|||
public getClients(appName: string): Observable<AppClientDto[]> { |
|||
return this.authService.authGet(this.apiUrl.buildUrl(`api/apps/${appName}/clients`)) |
|||
.map(response => response.json()) |
|||
.map(response => { |
|||
const items: any[] = response; |
|||
|
|||
return items.map(item => { |
|||
return new AppClientDto(item.clientName, item.clientSecret, DateTime.parseISO_UTC(item.expiresUtc)); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public postClient(appName: string, client: AppClientCreateDto): Observable<AppClientDto> { |
|||
return this.authService.authPost(this.apiUrl.buildUrl(`api/apps/${appName}/clients`), client) |
|||
.map(response => response.json()) |
|||
.map(response => new AppClientDto(response.clientName, response.clientSecret, DateTime.parseISO_UTC(response.expiresUtc))) |
|||
.catch(response => { |
|||
if (response.status === 400) { |
|||
return Observable.throw('An client with the same name already exists.'); |
|||
} else { |
|||
return Observable.throw('A new client could not be created.'); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public deleteClient(appName: string, name: string): Observable<any> { |
|||
return this.authService.authDelete(this.apiUrl.buildUrl(`api/apps/${appName}/clients/${name}`)); |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
// ==========================================================================
|
|||
// CommandContextTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Commands |
|||
{ |
|||
public class CommandContextTests |
|||
{ |
|||
private readonly MockupCommand command = new MockupCommand(); |
|||
|
|||
private sealed class MockupCommand : ICommand |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_instantiate_and_provide_command() |
|||
{ |
|||
var sut = new CommandContext(command); |
|||
|
|||
Assert.Equal(command, sut.Command); |
|||
Assert.Null(sut.Exception); |
|||
Assert.False(sut.IsSucceeded); |
|||
Assert.False(sut.IsHandled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_provide_exception_when_failed() |
|||
{ |
|||
var exc = new InvalidOperationException(); |
|||
var sut = new CommandContext(command); |
|||
|
|||
sut.Fail(exc); |
|||
|
|||
Assert.Equal(exc, sut.Exception); |
|||
Assert.False(sut.IsSucceeded); |
|||
Assert.True(sut.IsHandled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_be_handled_when_succeeded() |
|||
{ |
|||
var sut = new CommandContext(command); |
|||
|
|||
sut.Succeed(); |
|||
|
|||
Assert.Null(sut.Exception); |
|||
Assert.True(sut.IsSucceeded); |
|||
Assert.True(sut.IsHandled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Shoud_not_change_status_when_already_succeeded() |
|||
{ |
|||
var sut = new CommandContext(command); |
|||
|
|||
sut.Succeed(Guid.NewGuid()); |
|||
sut.Fail(new Exception()); |
|||
|
|||
Assert.Null(sut.Exception); |
|||
Assert.True(sut.IsHandled); |
|||
Assert.True(sut.IsSucceeded); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_provide_result_valid_when_succeeded_with_value() |
|||
{ |
|||
var guid = Guid.NewGuid(); |
|||
var sut = new CommandContext(command); |
|||
|
|||
sut.Succeed(guid); |
|||
|
|||
Assert.Equal(guid, sut.Result<Guid>()); |
|||
Assert.True(sut.IsSucceeded); |
|||
Assert.True(sut.IsHandled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Shoud_not_change_status_when_already_failed() |
|||
{ |
|||
var sut = new CommandContext(command); |
|||
|
|||
sut.Fail(new Exception()); |
|||
sut.Succeed(Guid.NewGuid()); |
|||
|
|||
Assert.NotNull(sut.Exception); |
|||
Assert.True(sut.IsHandled); |
|||
Assert.False(sut.IsSucceeded); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue