Browse Source

Allow to invite users.

pull/337/head
Sebastian Stehle 7 years ago
parent
commit
54ff6fd875
  1. 50
      src/Squidex.Domain.Apps.Entities/Apps/InviteCommandMiddleware.cs
  2. 16
      src/Squidex.Domain.Apps.Entities/Apps/InvitedResult.cs
  3. 16
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs
  4. 22
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Scripts.cs
  5. 21
      src/Squidex.Domain.Users/DefaultUserResolver.cs
  6. 6
      src/Squidex.Infrastructure/StringExtensions.cs
  7. 2
      src/Squidex.Shared/Users/IUserResolver.cs
  8. 1
      src/Squidex/Areas/Api/Controllers/ApiController.cs
  9. 14
      src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  10. 7
      src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs
  11. 9
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorAssignedDto.cs
  12. 8
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  13. 3
      src/Squidex/Config/Domain/EntitiesServices.cs
  14. 6
      src/Squidex/Pipeline/ApiPermissionAttribute.cs
  15. 1
      src/Squidex/Squidex.csproj
  16. 15
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  17. 5
      src/Squidex/app/shared/services/app-contributors.service.spec.ts
  18. 17
      src/Squidex/app/shared/services/app-contributors.service.ts
  19. 10
      src/Squidex/app/shared/state/contributors.state.spec.ts
  20. 13
      src/Squidex/app/shared/state/contributors.state.ts
  21. 11
      tests/Squidex.Infrastructure.Tests/StringExtensionsTests.cs

50
src/Squidex.Domain.Apps.Entities/Apps/InviteCommandMiddleware.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class InviteCommandMiddleware : ICommandMiddleware
{
private readonly IUserResolver userResolver;
public InviteCommandMiddleware(IUserResolver userResolver)
{
Guard.NotNull(userResolver, nameof(userResolver));
this.userResolver = userResolver;
}
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.Command is AssignContributor assignContributor)
{
if (assignContributor.IsInviting && assignContributor.ContributorId.IsEmail())
{
var isInvited = await userResolver.CreateUserIfNotExists(assignContributor.ContributorId);
await next();
if (isInvited && context.Result<object>() is EntityCreatedResult<string> id)
{
context.Complete(new InvitedResult { Id = id });
}
return;
}
}
await next();
}
}
}

16
src/Squidex.Domain.Apps.Entities/Apps/InvitedResult.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class InvitedResult
{
public EntityCreatedResult<string> Id { get; set; }
}
}

16
src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs

@ -20,14 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
public sealed class CreateBlogCommandMiddleware : ICommandMiddleware public sealed class CreateBlogCommandMiddleware : ICommandMiddleware
{ {
private const string TemplateName = "Blog"; private const string TemplateName = "Blog";
private const string SlugScript = @"
var data = ctx.data;
if (data.title && data.title.iv) {
data.slug = { iv: slugify(data.title.iv) };
}
replace(data);";
public async Task HandleAsync(CommandContext context, Func<Task> next) public async Task HandleAsync(CommandContext context, Func<Task> next)
{ {
@ -129,8 +121,8 @@ replace(data);";
await publish(new ConfigureScripts await publish(new ConfigureScripts
{ {
SchemaId = schemaId.Id, SchemaId = schemaId.Id,
ScriptCreate = SlugScript, ScriptCreate = Scripts.Slug,
ScriptUpdate = SlugScript ScriptUpdate = Scripts.Slug
}); });
return schemaId; return schemaId;
@ -163,8 +155,8 @@ replace(data);";
await publish(new ConfigureScripts await publish(new ConfigureScripts
{ {
SchemaId = schemaId.Id, SchemaId = schemaId.Id,
ScriptCreate = SlugScript, ScriptCreate = Scripts.Slug,
ScriptUpdate = SlugScript ScriptUpdate = Scripts.Slug
}); });
return schemaId; return schemaId;

22
src/Squidex.Domain.Apps.Entities/Apps/Templates/Scripts.cs

@ -0,0 +1,22 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public static class Scripts
{
public const string Slug =
@"var data = ctx.data;
if (data.title && data.title.iv) {
data.slug = { iv: slugify(data.title.iv) };
replace(data);
}
";
}
}

21
src/Squidex.Domain.Users/DefaultUserResolver.cs

@ -28,6 +28,27 @@ namespace Squidex.Domain.Users
this.userFactory = userFactory; this.userFactory = userFactory;
} }
public async Task<bool> CreateUserIfNotExists(string email)
{
var user = userFactory.Create(email);
try
{
var result = await userManager.CreateAsync(user);
if (result.Succeeded)
{
await userManager.UpdateAsync(user, new UserValues { DisplayName = email });
}
return result.Succeeded;
}
catch
{
return false;
}
}
public async Task<IUser> FindByIdOrEmailAsync(string idOrEmail) public async Task<IUser> FindByIdOrEmailAsync(string idOrEmail)
{ {
if (userFactory.IsId(idOrEmail)) if (userFactory.IsId(idOrEmail))

6
src/Squidex.Infrastructure/StringExtensions.cs

@ -17,6 +17,7 @@ namespace Squidex.Infrastructure
{ {
private const char NullChar = (char)0; private const char NullChar = (char)0;
private static readonly Regex SlugRegex = new Regex("^[a-z0-9]+(\\-[a-z0-9]+)*$", RegexOptions.Compiled); private static readonly Regex SlugRegex = new Regex("^[a-z0-9]+(\\-[a-z0-9]+)*$", RegexOptions.Compiled);
private static readonly Regex EmailRegex = new Regex("^[a-zA-Z0-9.!#$%&’*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$", RegexOptions.Compiled);
private static readonly Regex PropertyNameRegex = new Regex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.Compiled); private static readonly Regex PropertyNameRegex = new Regex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.Compiled);
private static readonly Dictionary<char, string> LowerCaseDiacritics; private static readonly Dictionary<char, string> LowerCaseDiacritics;
private static readonly Dictionary<char, string> Diacritics = new Dictionary<char, string> private static readonly Dictionary<char, string> Diacritics = new Dictionary<char, string>
@ -314,6 +315,11 @@ namespace Squidex.Infrastructure
return value != null && SlugRegex.IsMatch(value); return value != null && SlugRegex.IsMatch(value);
} }
public static bool IsEmail(this string value)
{
return value != null && EmailRegex.IsMatch(value);
}
public static bool IsPropertyName(this string value) public static bool IsPropertyName(this string value)
{ {
return value != null && PropertyNameRegex.IsMatch(value); return value != null && PropertyNameRegex.IsMatch(value);

2
src/Squidex.Shared/Users/IUserResolver.cs

@ -12,6 +12,8 @@ namespace Squidex.Shared.Users
{ {
public interface IUserResolver public interface IUserResolver
{ {
Task<bool> CreateUserIfNotExists(string email);
Task<IUser> FindByIdOrEmailAsync(string idOrEmail); Task<IUser> FindByIdOrEmailAsync(string idOrEmail);
Task<List<IUser>> QueryByEmailAsync(string email); Task<List<IUser>> QueryByEmailAsync(string email);

1
src/Squidex/Areas/Api/Controllers/ApiController.cs

@ -16,6 +16,7 @@ using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers namespace Squidex.Areas.Api.Controllers
{ {
[Area("Api")] [Area("Api")]
[ApiController]
[ApiExceptionFilter] [ApiExceptionFilter]
[ApiModelValidation(false)] [ApiModelValidation(false)]
public abstract class ApiController : Controller public abstract class ApiController : Controller

14
src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -73,8 +74,17 @@ namespace Squidex.Areas.Api.Controllers.Apps
var command = request.ToCommand(); var command = request.ToCommand();
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);
var result = context.Result<EntityCreatedResult<string>>(); var result = context.Result<object>();
var response = ContributorAssignedDto.FromId(result.IdOrValue); var response = (ContributorAssignedDto)null;
if (result is EntityCreatedResult<string> idOrValue)
{
response = ContributorAssignedDto.FromId(idOrValue.IdOrValue, false);
}
else if (result is InvitedResult invited)
{
response = ContributorAssignedDto.FromId(invited.Id.IdOrValue, true);
}
return Ok(response); return Ok(response);
} }

7
src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs

@ -24,9 +24,14 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// </summary> /// </summary>
public string Role { get; set; } public string Role { get; set; }
/// <summary>
/// Set to true to invite the user if he does not exist.
/// </summary>
public bool Invite { get; set; }
public AssignContributor ToCommand() public AssignContributor ToCommand()
{ {
return SimpleMapper.Map(this, new AssignContributor()); return SimpleMapper.Map(this, new AssignContributor { IsInviting = Invite });
} }
} }
} }

9
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorAssignedDto.cs

@ -17,9 +17,14 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
[Required] [Required]
public string ContributorId { get; set; } public string ContributorId { get; set; }
public static ContributorAssignedDto FromId(string id) /// <summary>
/// Indicates if the user was created.
/// </summary>
public bool WasInvited { get; set; }
public static ContributorAssignedDto FromId(string id, bool wasInvited)
{ {
return new ContributorAssignedDto { ContributorId = id }; return new ContributorAssignedDto { ContributorId = id, WasInvited = wasInvited };
} }
} }
} }

8
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -22,6 +22,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Areas.Api.Controllers.Contents namespace Squidex.Areas.Api.Controllers.Contents
{ {
@ -239,6 +240,13 @@ namespace Squidex.Areas.Api.Controllers.Contents
{ {
await contentQuery.ThrowIfSchemaNotExistsAsync(Context(), name); await contentQuery.ThrowIfSchemaNotExistsAsync(Context(), name);
var publishPermission = Permissions.ForApp(Permissions.AppContentsPublish, app, name);
if (publish && !User.Permissions().Includes(publishPermission))
{
return new StatusCodeResult(123);
}
var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish }; var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish };
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);

3
src/Squidex/Config/Domain/EntitiesServices.cs

@ -160,6 +160,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<EnrichWithSchemaIdCommandMiddleware>() services.AddSingletonAs<EnrichWithSchemaIdCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
services.AddSingletonAs<InviteCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<AssetCommandMiddleware>() services.AddSingletonAs<AssetCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();

6
src/Squidex/Pipeline/ApiPermissionAttribute.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using IdentityServer4.AccessTokenValidation; using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -38,10 +37,7 @@ namespace Squidex.Pipeline
{ {
if (permissionIds.Length > 0) if (permissionIds.Length > 0)
{ {
var set = new PermissionSet( var set = context.HttpContext.User.Permissions();
context.HttpContext.User.FindAll(SquidexClaimTypes.Permissions)
.Select(x => x.Value)
.Select(x => new Permission(x)));
var hasPermission = false; var hasPermission = false;

1
src/Squidex/Squidex.csproj

@ -68,7 +68,6 @@
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Api.Analyzers" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.1.0" /> <PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.1.0" />

15
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -13,9 +13,11 @@ import { filter, onErrorResumeNext, withLatestFrom } from 'rxjs/operators';
import { import {
AppContributorDto, AppContributorDto,
AppsState, AppsState,
AssignContributorDto,
AssignContributorForm, AssignContributorForm,
AutocompleteSource, AutocompleteSource,
ContributorsState, ContributorsState,
DialogService,
RolesState, RolesState,
Types, Types,
UserDto, UserDto,
@ -61,6 +63,7 @@ export class ContributorsPageComponent implements OnInit {
public readonly contributorsState: ContributorsState, public readonly contributorsState: ContributorsState,
public readonly rolesState: RolesState, public readonly rolesState: RolesState,
public readonly usersDataSource: UsersDataSource, public readonly usersDataSource: UsersDataSource,
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder
) { ) {
} }
@ -80,7 +83,7 @@ export class ContributorsPageComponent implements OnInit {
} }
public changeRole(contributor: AppContributorDto, role: string) { public changeRole(contributor: AppContributorDto, role: string) {
this.contributorsState.assign(new AppContributorDto(contributor.contributorId, role)).pipe(onErrorResumeNext()).subscribe(); this.contributorsState.assign(new AssignContributorDto(contributor.contributorId, role)).pipe(onErrorResumeNext()).subscribe();
} }
public assignContributor() { public assignContributor() {
@ -93,11 +96,15 @@ export class ContributorsPageComponent implements OnInit {
user = user.id; user = user.id;
} }
const requestDto = new AppContributorDto(user, 'Editor'); const requestDto = new AssignContributorDto(user, 'Editor', true);
this.contributorsState.assign(requestDto) this.contributorsState.assign(requestDto)
.subscribe(() => { .subscribe(wasInvited => {
this.assignContributorForm.submitCompleted(); this.assignContributorForm.submitCompleted({});
if (wasInvited) {
this.dialogs.notifyInfo('A new user with the entered email address has been created and assigned as contributor.');
}
}, error => { }, error => {
this.assignContributorForm.submitFailed(error); this.assignContributorForm.submitFailed(error);
}); });

5
src/Squidex/app/shared/services/app-contributors.service.spec.ts

@ -14,6 +14,7 @@ import {
AppContributorDto, AppContributorDto,
AppContributorsDto, AppContributorsDto,
AppContributorsService, AppContributorsService,
AssignContributorDto,
ContributorAssignedDto, ContributorAssignedDto,
Version Version
} from './../'; } from './../';
@ -80,7 +81,7 @@ describe('AppContributorsService', () => {
it('should make post request to assign contributor', it('should make post request to assign contributor',
inject([AppContributorsService, HttpTestingController], (appContributorsService: AppContributorsService, httpMock: HttpTestingController) => { inject([AppContributorsService, HttpTestingController], (appContributorsService: AppContributorsService, httpMock: HttpTestingController) => {
const dto = new AppContributorDto('123', 'Owner'); const dto = new AssignContributorDto('123', 'Owner');
let contributorAssignedDto: ContributorAssignedDto; let contributorAssignedDto: ContributorAssignedDto;
@ -93,7 +94,7 @@ describe('AppContributorsService', () => {
expect(req.request.method).toEqual('POST'); expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toEqual(version.value); expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({ contributorId: '123' }); req.flush({ contributorId: '123', wasInvited: true });
expect(contributorAssignedDto!.contributorId).toEqual('123'); expect(contributorAssignedDto!.contributorId).toEqual('123');
})); }));

17
src/Squidex/app/shared/services/app-contributors.service.ts

@ -30,6 +30,16 @@ export class AppContributorsDto extends Model {
} }
} }
export class AssignContributorDto extends Model {
constructor(
public readonly contributorId: string,
public readonly role: string,
public readonly invite = false
) {
super();
}
}
export class AppContributorDto extends Model { export class AppContributorDto extends Model {
constructor( constructor(
public readonly contributorId: string, public readonly contributorId: string,
@ -41,7 +51,8 @@ export class AppContributorDto extends Model {
export class ContributorAssignedDto { export class ContributorAssignedDto {
constructor( constructor(
public readonly contributorId: string public readonly contributorId: string,
public readonly wasInvited: boolean
) { ) {
} }
} }
@ -75,14 +86,14 @@ export class AppContributorsService {
pretifyError('Failed to load contributors. Please reload.')); pretifyError('Failed to load contributors. Please reload.'));
} }
public postContributor(appName: string, dto: AppContributorDto, version: Version): Observable<Versioned<ContributorAssignedDto>> { public postContributor(appName: string, dto: AssignContributorDto, version: Version): Observable<Versioned<ContributorAssignedDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.postVersioned(this.http, url, dto, version).pipe( return HTTP.postVersioned(this.http, url, dto, version).pipe(
map(response => { map(response => {
const body: any = response.payload.body; const body: any = response.payload.body;
const result = new ContributorAssignedDto(body.contributorId); const result = new ContributorAssignedDto(body.contributorId, body.wasInvited);
return new Versioned(response.version, result); return new Versioned(response.version, result);
}), }),

10
src/Squidex/app/shared/state/contributors.state.spec.ts

@ -13,12 +13,14 @@ import {
AppContributorsDto, AppContributorsDto,
AppContributorsService, AppContributorsService,
AppsState, AppsState,
AssignContributorDto,
AuthService, AuthService,
ContributorsState, ContributorsState,
DialogService, DialogService,
Version, Version,
Versioned Versioned
} from '@app/shared'; } from '@app/shared';
import { ContributorAssignedDto } from '../services/app-contributors.service';
describe('ContributorsState', () => { describe('ContributorsState', () => {
const app = 'my-app'; const app = 'my-app';
@ -82,10 +84,10 @@ describe('ContributorsState', () => {
it('should add contributor to snapshot when assigned', () => { it('should add contributor to snapshot when assigned', () => {
const newContributor = new AppContributorDto('id3', 'Developer'); const newContributor = new AppContributorDto('id3', 'Developer');
const request = new AppContributorDto('mail2stehle@gmail.com', 'Developer'); const request = new AssignContributorDto('mail2stehle@gmail.com', 'Developer');
contributorsService.setup(x => x.postContributor(app, request, version)) contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => of(new Versioned<AppContributorDto>(newVersion, newContributor))); .returns(() => of(new Versioned<ContributorAssignedDto>(newVersion, new ContributorAssignedDto('id3', true))));
contributorsState.assign(request).subscribe(); contributorsState.assign(request).subscribe();
@ -102,10 +104,10 @@ describe('ContributorsState', () => {
it('should update contributor in snapshot when assigned and already added', () => { it('should update contributor in snapshot when assigned and already added', () => {
const newContributor = new AppContributorDto('id2', 'Owner'); const newContributor = new AppContributorDto('id2', 'Owner');
const request = new AppContributorDto('mail2stehle@gmail.com', 'Owner'); const request = new AssignContributorDto('mail2stehle@gmail.com', 'Owner');
contributorsService.setup(x => x.postContributor(app, request, version)) contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => of(new Versioned<AppContributorDto>(newVersion, newContributor))); .returns(() => of(new Versioned<ContributorAssignedDto>(newVersion, new ContributorAssignedDto('id2', true))));
contributorsState.assign(request).subscribe(); contributorsState.assign(request).subscribe();

13
src/Squidex/app/shared/state/contributors.state.ts

@ -19,7 +19,12 @@ import {
Version Version
} from '@app/framework'; } from '@app/framework';
import { AppContributorDto, AppContributorsService } from './../services/app-contributors.service'; import {
AppContributorDto,
AppContributorsService,
AssignContributorDto
} from './../services/app-contributors.service';
import { AuthService } from './../services/auth.service'; import { AuthService } from './../services/auth.service';
import { AppsState } from './apps.state'; import { AppsState } from './apps.state';
@ -95,12 +100,14 @@ export class ContributorsState extends State<Snapshot> {
notify(this.dialogs)); notify(this.dialogs));
} }
public assign(request: AppContributorDto): Observable<any> { public assign(request: AssignContributorDto): Observable<boolean> {
return this.appContributorsService.postContributor(this.appName, request, this.version).pipe( return this.appContributorsService.postContributor(this.appName, request, this.version).pipe(
tap(dto => { map(dto => {
const contributors = this.updateContributors(dto.payload.contributorId, request.role, dto.version); const contributors = this.updateContributors(dto.payload.contributorId, request.role, dto.version);
this.replaceContributors(contributors, dto.version); this.replaceContributors(contributors, dto.version);
return dto.payload.wasInvited;
}), }),
catchError(error => { catchError(error => {
if (Types.is(error, ErrorDto) && error.statusCode === 404) { if (Types.is(error, ErrorDto) && error.statusCode === 404) {

11
tests/Squidex.Infrastructure.Tests/StringExtensionsTests.cs

@ -12,6 +12,17 @@ namespace Squidex.Infrastructure
{ {
public class StringExtensionsTests public class StringExtensionsTests
{ {
[Theory]
[InlineData(null, false)]
[InlineData("", false)]
[InlineData("name", false)]
[InlineData("name@@web.de", false)]
[InlineData("name@web.de", true)]
public void Should_check_email(string email, bool isEmail)
{
Assert.Equal(isEmail, email.IsEmail());
}
[Theory] [Theory]
[InlineData(null)] [InlineData(null)]
[InlineData("")] [InlineData("")]

Loading…
Cancel
Save