mirror of https://github.com/Squidex/squidex.git
61 changed files with 1421 additions and 62 deletions
@ -0,0 +1,22 @@ |
|||
// ==========================================================================
|
|||
// AppClientKeyCreated.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Events.Apps |
|||
{ |
|||
[TypeName("AppClientKeyCreated")] |
|||
public sealed class AppClientKeyCreated : IEvent |
|||
{ |
|||
public string ClientKey { get; set; } |
|||
|
|||
public DateTime ExpiresUtc { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// ==========================================================================
|
|||
// AppClientKeyRevoked.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Events.Apps |
|||
{ |
|||
[TypeName("AppClientKeyRevoked")] |
|||
public sealed class AppClientKeyRevoked : IEvent |
|||
{ |
|||
public string ClientKey { get; set; } |
|||
|
|||
public DateTime ExpiresUtc { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// AppLanguagesConfigured.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Events.Apps |
|||
{ |
|||
[TypeName("AppLanguagesConfigured")] |
|||
public sealed class AppLanguagesConfigured : IEvent |
|||
{ |
|||
public List<Language> Languages { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// ==========================================================================
|
|||
// EnrichWithTimestampHandler.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Commands |
|||
{ |
|||
public sealed class EnrichWithTimestampHandler : ICommandHandler |
|||
{ |
|||
private readonly Func<DateTime> timestamp; |
|||
|
|||
public EnrichWithTimestampHandler() |
|||
: this(() => DateTime.UtcNow) |
|||
{ |
|||
} |
|||
|
|||
public EnrichWithTimestampHandler(Func<DateTime> timestamp) |
|||
{ |
|||
Guard.NotNull(timestamp, nameof(timestamp)); |
|||
|
|||
this.timestamp = timestamp; |
|||
} |
|||
|
|||
public Task<bool> HandleAsync(CommandContext context) |
|||
{ |
|||
var timestampCommand = context.Command as ITimestampCommand; |
|||
|
|||
if (timestampCommand != null) |
|||
{ |
|||
timestampCommand.Timestamp = timestamp(); |
|||
} |
|||
|
|||
return Task.FromResult(false); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// ITimestampCommand.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Commands |
|||
{ |
|||
public interface ITimestampCommand : ICommand |
|||
{ |
|||
DateTime Timestamp { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// =========================================================================
|
|||
// LanguageConverter.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Newtonsoft.Json; |
|||
|
|||
namespace Squidex.Infrastructure.Json |
|||
{ |
|||
public sealed class LanguageConverter : JsonConverter |
|||
{ |
|||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) |
|||
{ |
|||
var language = value as Language; |
|||
|
|||
if (language != null) |
|||
{ |
|||
writer.WriteValue(language.Iso2Code); |
|||
} |
|||
else |
|||
{ |
|||
writer.WriteNull(); |
|||
} |
|||
} |
|||
|
|||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) |
|||
{ |
|||
return reader.TokenType == JsonToken.Null ? null : Language.GetLanguage((string) reader.Value); |
|||
} |
|||
|
|||
public override bool CanConvert(Type objectType) |
|||
{ |
|||
return objectType == typeof(Language); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// IAppClientKeyEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Read.Apps |
|||
{ |
|||
public interface IAppClientKeyEntity |
|||
{ |
|||
string ClientKey { get; } |
|||
|
|||
DateTime ExpiresUtc { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// ==========================================================================
|
|||
// MongoAppClientKeyEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Read.Apps; |
|||
|
|||
namespace Squidex.Store.MongoDb.Apps |
|||
{ |
|||
public class MongoAppClientKeyEntity : IAppClientKeyEntity |
|||
{ |
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public string ClientKey { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public DateTime ExpiresUtc { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// =========================================================================
|
|||
// ClientKeyGenerator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
|
|||
namespace Squidex.Write.Apps |
|||
{ |
|||
public class ClientKeyGenerator |
|||
{ |
|||
public virtual string GenerateKey() |
|||
{ |
|||
return Sha256(Guid.NewGuid().ToString()); |
|||
} |
|||
|
|||
private static string Sha256(string input) |
|||
{ |
|||
using (var sha = SHA256.Create()) |
|||
{ |
|||
var bytes = Encoding.UTF8.GetBytes(input); |
|||
var hash = sha.ComputeHash(bytes); |
|||
|
|||
return Convert.ToBase64String(hash); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// ConfigureLanguages.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Write.Apps.Commands |
|||
{ |
|||
public sealed class ConfigureLanguages : AppAggregateCommand, IValidatable |
|||
{ |
|||
public List<Language> Languages { get; set; } |
|||
|
|||
public void Validate(IList<ValidationError> errors) |
|||
{ |
|||
if (Languages == null || Languages.Count == 0) |
|||
{ |
|||
errors.Add(new ValidationError("Languages need at least one element.", nameof(Languages))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// CreateClientKey.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
|
|||
namespace Squidex.Write.Apps.Commands |
|||
{ |
|||
public sealed class CreateClientKey : AppAggregateCommand, ITimestampCommand, IValidatable |
|||
{ |
|||
public string ClientKey { get; set; } |
|||
|
|||
public DateTime Timestamp { get; set; } |
|||
|
|||
public void Validate(IList<ValidationError> errors) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(ClientKey)) |
|||
{ |
|||
errors.Add(new ValidationError("Client key is not assigned", nameof(ClientKey))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// RevokeClientKey.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Write.Apps.Commands |
|||
{ |
|||
public class RevokeClientKey : AppAggregateCommand, IValidatable |
|||
{ |
|||
public string ClientKey { get; set; } |
|||
|
|||
public void Validate(IList<ValidationError> errors) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(ClientKey)) |
|||
{ |
|||
errors.Add(new ValidationError("Client key is not assigned", nameof(ClientKey))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// ==========================================================================
|
|||
// AppClientKeysController.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Modules.Api.Apps.Models; |
|||
using Squidex.Pipeline; |
|||
using Squidex.Read.Apps.Services; |
|||
using Squidex.Write.Apps; |
|||
using Squidex.Write.Apps.Commands; |
|||
|
|||
namespace Squidex.Modules.Api.Apps |
|||
{ |
|||
[Authorize(Roles = "app-owner")] |
|||
[ApiExceptionFilter] |
|||
[ServiceFilter(typeof(AppFilterAttribute))] |
|||
public class AppClientKeysController : ControllerBase |
|||
{ |
|||
private readonly IAppProvider appProvider; |
|||
private readonly ClientKeyGenerator keyGenerator; |
|||
|
|||
public AppClientKeysController(ICommandBus commandBus, IAppProvider appProvider, ClientKeyGenerator keyGenerator) |
|||
: base(commandBus) |
|||
{ |
|||
this.appProvider = appProvider; |
|||
this.keyGenerator = keyGenerator; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("apps/{app}/client-keys/")] |
|||
public async Task<IActionResult> GetContributors(string app) |
|||
{ |
|||
var entity = await appProvider.FindAppByNameAsync(app); |
|||
|
|||
if (entity == null) |
|||
{ |
|||
return NotFound(); |
|||
} |
|||
|
|||
var model = entity.ClientKeys.Select(x => SimpleMapper.Map(x, new ClientKeyDto())).ToList(); |
|||
|
|||
return Ok(model); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("apps/{app}/client-keys/")] |
|||
public async Task<IActionResult> PostClientKey() |
|||
{ |
|||
var clientKey = keyGenerator.GenerateKey(); |
|||
|
|||
await CommandBus.PublishAsync(new CreateClientKey { ClientKey = clientKey }); |
|||
|
|||
return Ok(new ClientKeyCreatedDto { ClientKey = clientKey }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// ==========================================================================
|
|||
// AppLanguagesController.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Modules.Api.Apps.Models; |
|||
using Squidex.Pipeline; |
|||
using Squidex.Read.Apps.Services; |
|||
using Squidex.Write.Apps.Commands; |
|||
|
|||
namespace Squidex.Modules.Api.Apps |
|||
{ |
|||
[Authorize(Roles = "app-owner")] |
|||
[ApiExceptionFilter] |
|||
[ServiceFilter(typeof(AppFilterAttribute))] |
|||
public class AppLanguagesController : ControllerBase |
|||
{ |
|||
private readonly IAppProvider appProvider; |
|||
|
|||
public AppLanguagesController(ICommandBus commandBus, IAppProvider appProvider) |
|||
: base(commandBus) |
|||
{ |
|||
this.appProvider = appProvider; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("apps/{app}/languages/")] |
|||
public async Task<IActionResult> GetContributors(string app) |
|||
{ |
|||
var entity = await appProvider.FindAppByNameAsync(app); |
|||
|
|||
if (entity == null) |
|||
{ |
|||
return NotFound(); |
|||
} |
|||
|
|||
var model = entity.Languages.Select(x => SimpleMapper.Map(x, new LanguageDto())).ToList(); |
|||
|
|||
return Ok(model); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("apps/{app}/languages/")] |
|||
public async Task<IActionResult> PostLanguages([FromBody] ConfigureLanguagesDto model) |
|||
{ |
|||
await CommandBus.PublishAsync(SimpleMapper.Map(model, new ConfigureLanguages())); |
|||
|
|||
return Ok(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
// ==========================================================================
|
|||
// ClientKeyCreatedDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Modules.Api.Apps.Models |
|||
{ |
|||
public sealed class ClientKeyCreatedDto |
|||
{ |
|||
public string ClientKey { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// ClientKeyDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Modules.Api.Apps.Models |
|||
{ |
|||
public sealed class ClientKeyDto |
|||
{ |
|||
public string ClientKey { get; set; } |
|||
|
|||
public DateTime ExpiresUtc { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// ConfigureLanguagesDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Modules.Api.Apps.Models |
|||
{ |
|||
public class ConfigureLanguagesDto |
|||
{ |
|||
public List<Language> Languages { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// LanguageDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Modules.Api |
|||
{ |
|||
public class LanguageDto |
|||
{ |
|||
public string Iso2Code { get; set; } |
|||
|
|||
public string EnglishName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/* |
|||
* 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,76 @@ |
|||
/* |
|||
* 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, |
|||
AppLanguagesService, |
|||
AuthService, |
|||
LanguageDto, |
|||
} from './../'; |
|||
|
|||
describe('AppLanguagesService', () => { |
|||
let authService: TypeMoq.Mock<AuthService>; |
|||
let appLanguagesService: AppLanguagesService; |
|||
|
|||
beforeEach(() => { |
|||
authService = TypeMoq.Mock.ofType(AuthService); |
|||
appLanguagesService = new AppLanguagesService(authService.object, new ApiUrlConfig('http://service/p/')); |
|||
}); |
|||
|
|||
it('should make get request with auth service to get app languages', () => { |
|||
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/languages')) |
|||
.returns(() => Observable.of( |
|||
new Ng2Http.Response( |
|||
new Ng2Http.ResponseOptions({ |
|||
body: [{ |
|||
iso2Code: 'de', |
|||
englishName: 'German' |
|||
}, { |
|||
iso2Code: 'en', |
|||
englishName: 'English' |
|||
}] |
|||
}) |
|||
) |
|||
)) |
|||
.verifiable(TypeMoq.Times.once()); |
|||
|
|||
let languages: LanguageDto[] = null; |
|||
|
|||
appLanguagesService.getLanguages('my-app').subscribe(result => { |
|||
languages = result; |
|||
}).unsubscribe(); |
|||
|
|||
expect(languages).toEqual( |
|||
[ |
|||
new LanguageDto('de', 'German'), |
|||
new LanguageDto('en', 'English'), |
|||
]); |
|||
|
|||
authService.verifyAll(); |
|||
}); |
|||
|
|||
it('should make post request to configure languages', () => { |
|||
const languages = ['de', 'en']; |
|||
|
|||
authService.setup(x => x.authPost('http://service/p/api/apps/my-app/languages', TypeMoq.It.is(y => y['languages'] === languages))) |
|||
.returns(() => Observable.of( |
|||
new Ng2Http.Response( |
|||
new Ng2Http.ResponseOptions() |
|||
) |
|||
)) |
|||
.verifiable(TypeMoq.Times.once()); |
|||
|
|||
appLanguagesService.postLanguages('my-app', languages); |
|||
|
|||
authService.verifyAll(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,40 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import * as Ng2 from '@angular/core'; |
|||
|
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { ApiUrlConfig } from 'framework'; |
|||
import { AuthService } from './auth.service'; |
|||
import { LanguageDto } from './languages.service'; |
|||
|
|||
@Ng2.Injectable() |
|||
export class AppLanguagesService { |
|||
constructor( |
|||
private readonly authService: AuthService, |
|||
private readonly apiUrl: ApiUrlConfig |
|||
) { |
|||
} |
|||
|
|||
public getLanguages(appName: string): Observable<LanguageDto[]> { |
|||
return this.authService.authGet(this.apiUrl.buildUrl(`api/apps/${appName}/languages`)) |
|||
.map(response => { |
|||
const body: any[] = response.json(); |
|||
|
|||
return body.map(item => { |
|||
return new LanguageDto( |
|||
item.iso2Code, |
|||
item.englishName); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public postLanguages(appName: string, languageCodes: string[]): Observable<any> { |
|||
return this.authService.authPost(this.apiUrl.buildUrl(`api/apps/${appName}/languages`), { languages: languageCodes }); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
@import '_mixins'; |
|||
@import '_vars'; |
|||
|
|||
$color-drag-border: #000; |
|||
|
|||
.dnd { |
|||
&-drag { |
|||
&-start, |
|||
&-enter { |
|||
@include opacity(.6); |
|||
} |
|||
|
|||
&-start, |
|||
&-over, |
|||
&-enter { |
|||
border: 2px dashed $color-drag-border; |
|||
} |
|||
} |
|||
|
|||
&-sortable-drag { |
|||
@include opacity(.6); |
|||
border: 2px dashed $color-drag-border; |
|||
} |
|||
} |
|||
@ -1,3 +1,4 @@ |
|||
@import '_bootstrap.scss'; |
|||
@import '_layout.scss'; |
|||
@import '_completer.scss'; |
|||
@import '_lib-completer.scss'; |
|||
@import '_lib-dnd.scss'; |
|||
@ -0,0 +1,51 @@ |
|||
// ==========================================================================
|
|||
// EnrichWithTimestampHandlerTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Commands |
|||
{ |
|||
public sealed class EnrichWithTimestampHandlerTests |
|||
{ |
|||
private sealed class NormalCommand : AggregateCommand |
|||
{ |
|||
} |
|||
|
|||
private sealed class TimestampCommand : AggregateCommand, ITimestampCommand |
|||
{ |
|||
public DateTime Timestamp { get; set; } |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_set_timestamp_when_is_timestamp_command() |
|||
{ |
|||
var utc = DateTime.Today; |
|||
var sut = new EnrichWithTimestampHandler(() => utc); |
|||
|
|||
var command = new TimestampCommand(); |
|||
|
|||
var result = await sut.HandleAsync(new CommandContext(command)); |
|||
|
|||
Assert.False(result); |
|||
Assert.Equal(utc, command.Timestamp); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_do_nothing_for_normal_command() |
|||
{ |
|||
var utc = DateTime.Today; |
|||
var sut = new EnrichWithTimestampHandler(() => utc); |
|||
|
|||
var result = await sut.HandleAsync(new CommandContext(new NormalCommand())); |
|||
|
|||
Assert.False(result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// =========================================================================
|
|||
// ClientKeyGeneratorTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Write.Apps; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Write.Tests.Apps |
|||
{ |
|||
public class ClientKeyGeneratorTests |
|||
{ |
|||
private readonly ClientKeyGenerator sut = new ClientKeyGenerator(); |
|||
|
|||
[Fact] |
|||
public void Should_create_very_long_client_key() |
|||
{ |
|||
var key = sut.GenerateKey(); |
|||
|
|||
Assert.Equal(44, key.Length); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue