mirror of https://github.com/Squidex/squidex.git
44 changed files with 1217 additions and 89 deletions
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// AppContributorRemoved.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Events.Apps |
|||
{ |
|||
[TypeName("AppContributorRemoved")] |
|||
public class AppContributorRemoved : IEvent |
|||
{ |
|||
public string ContributorId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// =========================================================================
|
|||
// Language.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public sealed class Language |
|||
{ |
|||
private readonly string iso2Code; |
|||
private readonly string englishName; |
|||
private static readonly Dictionary<string, Language> allLanguages = new Dictionary<string, Language>(); |
|||
|
|||
static Language() |
|||
{ |
|||
var resourceAssembly = typeof(Language).GetTypeInfo().Assembly; |
|||
var resourceStream = resourceAssembly.GetManifestResourceStream("Squidex.Infrastructure.language-codes.csv"); |
|||
|
|||
using (var reader = new StreamReader(resourceStream, Encoding.UTF8)) |
|||
{ |
|||
reader.ReadLine(); |
|||
|
|||
while (!reader.EndOfStream) |
|||
{ |
|||
var languageLine = reader.ReadLine(); |
|||
var languageIso2Code = languageLine.Substring(1, 2); |
|||
var languageEnglishName = languageLine.Substring(6, languageLine.Length - 7); |
|||
|
|||
allLanguages[languageIso2Code] = new Language(languageIso2Code, languageEnglishName); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static Language GetLanguage(string iso2Code) |
|||
{ |
|||
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code)); |
|||
|
|||
try |
|||
{ |
|||
return allLanguages[iso2Code]; |
|||
} |
|||
catch (KeyNotFoundException) |
|||
{ |
|||
throw new NotSupportedException($"Language {iso2Code} is not supported"); |
|||
} |
|||
} |
|||
|
|||
public static IEnumerable<Language> AllLanguages |
|||
{ |
|||
get { return allLanguages.Values; } |
|||
} |
|||
|
|||
public string EnglishName |
|||
{ |
|||
get { return englishName; } |
|||
} |
|||
|
|||
public string Iso2Code |
|||
{ |
|||
get { return iso2Code; } |
|||
} |
|||
|
|||
private Language(string iso2Code, string englishName) |
|||
{ |
|||
this.iso2Code = iso2Code; |
|||
|
|||
this.englishName = englishName; |
|||
} |
|||
} |
|||
} |
|||
|
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// IUserEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Read.Users |
|||
{ |
|||
public interface IUserEntity |
|||
{ |
|||
string Id { get; } |
|||
|
|||
string Email { get; } |
|||
|
|||
string PictureUrl { get; } |
|||
|
|||
string DisplayName { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// IUserRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Read.Users.Repositories |
|||
{ |
|||
public interface IUserRepository |
|||
{ |
|||
Task<List<IUserEntity>> FindUsersByEmail(string email); |
|||
|
|||
Task<IUserEntity> FindUserByIdAsync(string id); |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// ==========================================================================
|
|||
// MongoUserEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Identity.MongoDB; |
|||
using Squidex.Infrastructure.Security; |
|||
using Squidex.Read.Users; |
|||
|
|||
namespace Squidex.Store.MongoDb.Users |
|||
{ |
|||
public class MongoUserEntity : IUserEntity |
|||
{ |
|||
private readonly IdentityUser inner; |
|||
|
|||
public string Id |
|||
{ |
|||
get { return inner.Id; } |
|||
} |
|||
|
|||
public string Email |
|||
{ |
|||
get { return inner.Email; } |
|||
} |
|||
|
|||
public string DisplayName |
|||
{ |
|||
get { return inner.Claims.Find(x => x.Type == ExtendedClaimTypes.SquidexDisplayName)?.Value; } |
|||
} |
|||
|
|||
public string PictureUrl |
|||
{ |
|||
get { return inner.Claims.Find(x => x.Type == ExtendedClaimTypes.SquidexPictureUrl)?.Value; } |
|||
} |
|||
|
|||
public MongoUserEntity(IdentityUser inner) |
|||
{ |
|||
this.inner = inner; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// ==========================================================================
|
|||
// MongoUserRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.AspNetCore.Identity.MongoDB; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Read.Users; |
|||
using Squidex.Read.Users.Repositories; |
|||
|
|||
namespace Squidex.Store.MongoDb.Users |
|||
{ |
|||
public sealed class MongoUserRepository : IUserRepository |
|||
{ |
|||
private readonly UserManager<IdentityUser> userManager; |
|||
|
|||
public MongoUserRepository(UserManager<IdentityUser> userManager) |
|||
{ |
|||
Guard.NotNull(userManager, nameof(userManager)); |
|||
|
|||
this.userManager = userManager; |
|||
} |
|||
|
|||
public Task<List<IUserEntity>> FindUsersByEmail(string email) |
|||
{ |
|||
var users = userManager.Users.Where(x => x.NormalizedEmail.Contains(email)).Take(10).ToList(); |
|||
|
|||
return Task.FromResult(users.Select(x => (IUserEntity)new MongoUserEntity(x)).ToList()); |
|||
} |
|||
|
|||
public async Task<IUserEntity> FindUserByIdAsync(string id) |
|||
{ |
|||
var user = await userManager.FindByIdAsync(id); |
|||
|
|||
return user != null ? new MongoUserEntity(user) : null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// ==========================================================================
|
|||
// AppAggregateCommand.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
|
|||
namespace Squidex.Write |
|||
{ |
|||
public class AppAggregateCommand : AggregateCommand, IAppCommand |
|||
{ |
|||
Guid IAppCommand.AppId |
|||
{ |
|||
get |
|||
{ |
|||
return AggregateId; |
|||
} |
|||
set |
|||
{ |
|||
AggregateId = value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// ==========================================================================
|
|||
// AssignContributor.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Core.Apps; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Write.Apps.Commands |
|||
{ |
|||
public class AssignContributor : AppAggregateCommand, IValidatable |
|||
{ |
|||
public string ContributorId { get; set; } |
|||
|
|||
public PermissionLevel Permission { get; set; } |
|||
|
|||
public void Validate(IList<ValidationError> errors) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(ContributorId)) |
|||
{ |
|||
errors.Add(new ValidationError("Contributor id not assigned", nameof (ContributorId))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// RemoveContributor.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 RemoveContributor : AppAggregateCommand, IValidatable |
|||
{ |
|||
public string ContributorId { get; set; } |
|||
|
|||
public void Validate(IList<ValidationError> errors) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(ContributorId)) |
|||
{ |
|||
errors.Add(new ValidationError("Contributor id not assigned", nameof(ContributorId))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
// ==========================================================================
|
|||
// AppContributorsController.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.Repositories; |
|||
using Squidex.Write.Apps.Commands; |
|||
|
|||
namespace Squidex.Modules.Api.Apps |
|||
{ |
|||
[Authorize] |
|||
[ApiExceptionFilter] |
|||
[ServiceFilter(typeof(AppFilterAttribute))] |
|||
[Route("apps/{app}")] |
|||
public class AppContributorsController : ControllerBase |
|||
{ |
|||
private readonly IAppRepository appRepository; |
|||
|
|||
public AppContributorsController(ICommandBus commandBus, IAppRepository appRepository) |
|||
: base(commandBus) |
|||
{ |
|||
this.appRepository = appRepository; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("contributors")] |
|||
public async Task<IActionResult> GetContributors(string app) |
|||
{ |
|||
var entity = await appRepository.FindAppByNameAsync(app); |
|||
|
|||
if (entity == null) |
|||
{ |
|||
return NotFound(); |
|||
} |
|||
|
|||
var model = entity.Contributors.Select(x => SimpleMapper.Map(x, new AppContributorDto())).ToList(); |
|||
|
|||
return Ok(model); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Route("contributors")] |
|||
public async Task<IActionResult> PutContributor([FromBody] AssignContributorDto model) |
|||
{ |
|||
await CommandBus.PublishAsync(SimpleMapper.Map(model, new AssignContributor())); |
|||
|
|||
return Ok(); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("contributors/{contributorId}")] |
|||
public async Task<IActionResult> PutContributor(string contributorId) |
|||
{ |
|||
await CommandBus.PublishAsync(new RemoveContributor { ContributorId = contributorId }); |
|||
|
|||
return Ok(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// =========================================================================
|
|||
// AppContributorDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Core.Apps; |
|||
|
|||
namespace Squidex.Modules.Api.Apps.Models |
|||
{ |
|||
public sealed class AppContributorDto |
|||
{ |
|||
public string ContributorId { get; set; } |
|||
|
|||
public PermissionLevel Permission { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// PutContributorDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Core.Apps; |
|||
|
|||
namespace Squidex.Modules.Api.Apps.Models |
|||
{ |
|||
public class AssignContributorDto |
|||
{ |
|||
public string ContributorId { get; set; } |
|||
|
|||
public PermissionLevel Permission { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// =========================================================================
|
|||
// LanguagesController.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Modules.Api.Languages |
|||
{ |
|||
[Authorize] |
|||
[ApiExceptionFilter] |
|||
public class LanguagesController : Controller |
|||
{ |
|||
[HttpGet] |
|||
[Route("languages/")] |
|||
public IActionResult GetLanguages() |
|||
{ |
|||
var model = Language.AllLanguages.ToList(); |
|||
|
|||
return Ok(model); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// =========================================================================
|
|||
// UserDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Modules.Api.Users.Models |
|||
{ |
|||
public sealed class UserDto |
|||
{ |
|||
public string Id { get; set; } |
|||
|
|||
public string ProfileUrl { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// =========================================================================
|
|||
// UsersController.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.Reflection; |
|||
using Squidex.Modules.Api.Users.Models; |
|||
using Squidex.Pipeline; |
|||
using Squidex.Read.Users.Repositories; |
|||
|
|||
namespace Squidex.Modules.Api.Users |
|||
{ |
|||
[Authorize] |
|||
[ApiExceptionFilter] |
|||
public class UsersController : Controller |
|||
{ |
|||
private readonly IUserRepository userRepository; |
|||
|
|||
public UsersController(IUserRepository userRepository) |
|||
{ |
|||
this.userRepository = userRepository; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("users")] |
|||
public async Task<IActionResult> GetUsers(string email) |
|||
{ |
|||
var entities = await userRepository.FindUsersByEmail(email); |
|||
|
|||
var model = entities.Select(x => SimpleMapper.Map(x, new UserDto())).ToList(); |
|||
|
|||
return Ok(model); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("users/{id}/")] |
|||
public async Task<IActionResult> GetUser(string id) |
|||
{ |
|||
var entity = await userRepository.FindUserByIdAsync(id); |
|||
|
|||
if (entity == null) |
|||
{ |
|||
return NotFound(); |
|||
} |
|||
|
|||
var model = SimpleMapper.Map(entity, new UserDto()); |
|||
|
|||
return Ok(model); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
// =========================================================================
|
|||
// LanguageTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public class LanguageTests |
|||
{ |
|||
[Theory] |
|||
[InlineData("")] |
|||
[InlineData(" ")] |
|||
public void Should_throw_if_getting_by_empty_key(string key) |
|||
{ |
|||
Assert.Throws<ArgumentException>(() => Language.GetLanguage(key)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_getting_by_null_key() |
|||
{ |
|||
Assert.Throws<ArgumentNullException>(() => Language.GetLanguage(null)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_if_getting_by_unsupported_language() |
|||
{ |
|||
Assert.Throws<NotSupportedException>(() => Language.GetLanguage("xy")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_provide_all_languages() |
|||
{ |
|||
Assert.True(Language.AllLanguages.Count() > 100); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData("de", "German")] |
|||
[InlineData("en", "English")] |
|||
[InlineData("sv", "Swedish")] |
|||
[InlineData("zh", "Chinese")] |
|||
public void Should_provide_correct_english_name(string key, string englishName) |
|||
{ |
|||
var language = Language.GetLanguage(key); |
|||
|
|||
Assert.Equal(key, language.Iso2Code); |
|||
Assert.Equal(englishName, language.EnglishName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// ==========================================================================
|
|||
// HandlerTestBase.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Moq; |
|||
using Squidex.Infrastructure.CQRS; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
|
|||
namespace Squidex.Write.Tests.Utils |
|||
{ |
|||
public abstract class HandlerTestBase<T> where T : DomainObject |
|||
{ |
|||
private readonly Mock<IDomainObjectFactory> domainObjectFactory = new Mock<IDomainObjectFactory>(); |
|||
private readonly Mock<IDomainObjectRepository> domainObjectRepository = new Mock<IDomainObjectRepository>(); |
|||
private readonly Guid id = Guid.NewGuid(); |
|||
|
|||
protected Guid Id |
|||
{ |
|||
get { return id; } |
|||
} |
|||
|
|||
protected Mock<IDomainObjectFactory> DomainObjectFactory |
|||
{ |
|||
get { return domainObjectFactory; } |
|||
} |
|||
|
|||
protected Mock<IDomainObjectRepository> DomainObjectRepository |
|||
{ |
|||
get { return domainObjectRepository; } |
|||
} |
|||
|
|||
public async Task TestCreate(T domainObject, Func<T, Task> action, bool succeeded = true) |
|||
{ |
|||
domainObjectFactory.Setup(x => x.CreateNew(typeof(T), id)).Returns(domainObject).Verifiable(); |
|||
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable(); |
|||
|
|||
await action(domainObject); |
|||
|
|||
if (succeeded) |
|||
{ |
|||
domainObjectFactory.VerifyAll(); |
|||
domainObjectRepository.VerifyAll(); |
|||
} |
|||
} |
|||
|
|||
public async Task TestUpdate(T domainObject, Func<T, Task> action, bool succeeded = true) |
|||
{ |
|||
domainObjectRepository.Setup(x => x.GetByIdAsync<T>(domainObject.Id, int.MaxValue)).Returns(Task.FromResult(domainObject)).Verifiable(); |
|||
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable(); |
|||
|
|||
await action(domainObject); |
|||
|
|||
if (succeeded) |
|||
{ |
|||
domainObjectRepository.VerifyAll(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue