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