diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs index e31a6a55d..dd612658c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs @@ -13,12 +13,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { public string ContributorId { get; set; } - public string Role { get; set; } = Roles.Developer; + public string Role { get; set; } = Roles.Editor; public bool IsRestore { get; set; } - public bool IsInviting { get; set; } - - public bool IsCreated { get; set; } + public bool Invite { get; set; } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs index b43d4f502..962198cf4 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs @@ -32,35 +32,39 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (string.IsNullOrWhiteSpace(command.ContributorId)) { e(Not.Defined("Contributor id"), nameof(command.ContributorId)); - return; } - - var user = await users.FindByIdOrEmailAsync(command.ContributorId); - - if (user == null) + else { - throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(IAppEntity)); - } - - command.ContributorId = user.Id; + var user = await users.FindByIdOrEmailAsync(command.ContributorId); - if (!command.IsRestore) - { - if (string.Equals(command.ContributorId, command.Actor?.Identifier, StringComparison.OrdinalIgnoreCase)) + if (user == null) { - throw new DomainForbiddenException("You cannot change your own role."); + throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(IAppEntity)); } - if (contributors.TryGetValue(command.ContributorId, out var existing)) + command.ContributorId = user.Id; + + if (!command.IsRestore) { - if (existing == command.Role) + if (string.Equals(command.ContributorId, command.Actor?.Identifier, StringComparison.OrdinalIgnoreCase)) { - e(Not.New("Contributor", "role"), nameof(command.Role)); + throw new DomainForbiddenException("You cannot change your own role."); + } + + if (contributors.TryGetValue(command.ContributorId, out var role)) + { + if (role == command.Role) + { + e(Not.New("Contributor", "role"), nameof(command.Role)); + } + } + else + { + if (plan.MaxContributors > 0 && contributors.Count >= plan.MaxContributors) + { + e("You have reached the maximum number of contributors for your plan."); + } } - } - else if (plan.MaxContributors > 0 && contributors.Count >= plan.MaxContributors) - { - e("You have reached the maximum number of contributors for your plan."); } } }); diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs index 7bde0a4cd..084d1270c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs @@ -27,24 +27,26 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation public async Task HandleAsync(CommandContext context, Func next) { - if (context.Command is AssignContributor assignContributor) + if (context.Command is AssignContributor assignContributor && ShouldInvite(assignContributor)) { - if (assignContributor.IsInviting && assignContributor.ContributorId.IsEmail()) - { - assignContributor.IsCreated = await userResolver.CreateUserIfNotExists(assignContributor.ContributorId, true); - - await next(); + var created = await userResolver.CreateUserIfNotExists(assignContributor.ContributorId, true); - if (assignContributor.IsCreated && context.PlainResult is IAppEntity app) - { - context.Complete(new InvitedResult { App = app }); - } + await next(); - return; + if (created && context.PlainResult is IAppEntity app) + { + context.Complete(new InvitedResult { App = app }); } } + else + { + await next(); + } + } - await next(); + private bool ShouldInvite(AssignContributor assignContributor) + { + return assignContributor.Invite && assignContributor.ContributorId.IsEmail(); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs index 86ffb3bcf..23c80b139 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs @@ -52,8 +52,8 @@ namespace Squidex.Domain.Apps.Entities.Assets switch (command) { - case CreateAsset createRule: - return CreateReturnAsync(createRule, async c => + case CreateAsset createAsset: + return CreateReturnAsync(createAsset, async c => { GuardAsset.CanCreate(c); @@ -63,8 +63,8 @@ namespace Squidex.Domain.Apps.Entities.Assets return Snapshot; }); - case UpdateAsset updateRule: - return UpdateReturn(updateRule, c => + case UpdateAsset updateAsset: + return UpdateReturn(updateAsset, c => { GuardAsset.CanUpdate(c); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs index ede15b4d8..0df2db061 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs @@ -8,6 +8,7 @@ using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Reflection; +using Roles = Squidex.Domain.Apps.Core.Apps.Role; namespace Squidex.Areas.Api.Controllers.Apps.Models { @@ -22,7 +23,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// /// The role of the contributor. /// - public string Role { get; set; } + public string Role { get; set; } = Roles.Developer; /// /// Set to true to invite the user if he does not exist. @@ -31,7 +32,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models public AssignContributor ToCommand() { - return SimpleMapper.Map(this, new AssignContributor { IsInviting = Invite }); + return SimpleMapper.Map(this, new AssignContributor()); } } } \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.scss b/src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.scss index 8b410ef66..eae569083 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.scss @@ -1,6 +1,6 @@ @import '_vars'; @import '_mixins'; -texarea { +textarea { resize: none; } \ No newline at end of file diff --git a/src/Squidex/app/features/settings/declarations.ts b/src/Squidex/app/features/settings/declarations.ts index 1a0553935..32b8bfd08 100644 --- a/src/Squidex/app/features/settings/declarations.ts +++ b/src/Squidex/app/features/settings/declarations.ts @@ -10,6 +10,7 @@ export * from './pages/backups/pipes'; export * from './pages/clients/client.component'; export * from './pages/clients/clients-page.component'; export * from './pages/contributors/contributors-page.component'; +export * from './pages/contributors/import-contributors-dialog.component'; export * from './pages/languages/language.component'; export * from './pages/languages/languages-page.component'; export * from './pages/more/more-page.component'; diff --git a/src/Squidex/app/features/settings/module.ts b/src/Squidex/app/features/settings/module.ts index 031f1a72e..18d5347c5 100644 --- a/src/Squidex/app/features/settings/module.ts +++ b/src/Squidex/app/features/settings/module.ts @@ -21,6 +21,7 @@ import { ClientComponent, ClientsPageComponent, ContributorsPageComponent, + ImportContributorsDialogComponent, LanguageComponent, LanguagesPageComponent, MorePageComponent, @@ -203,6 +204,7 @@ const routes: Routes = [ ClientComponent, ClientsPageComponent, ContributorsPageComponent, + ImportContributorsDialogComponent, LanguageComponent, LanguagesPageComponent, MorePageComponent, diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html index f55d7a783..562999057 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html @@ -34,15 +34,7 @@
-
- -
-
- -
-
- -
+
diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.scss b/src/Squidex/app/features/settings/pages/backups/backups-page.component.scss index 7d14e7dd0..fbb752506 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.scss +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.scss @@ -1,29 +1,2 @@ @import '_vars'; -@import '_mixins'; - -$circle-size: 2.8rem; - -.backup-status { - & { - @include circle($circle-size); - line-height: $circle-size + .1rem; - text-align: center; - font-size: .4 * $circle-size; - font-weight: normal; - background: $color-border; - color: $color-dark-foreground; - vertical-align: middle; - } - - &-pending { - color: inherit; - } - - &-failed { - background: $color-theme-error; - } - - &-success { - background: $color-theme-green; - } -} \ No newline at end of file +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html index d59f1c7ca..011e94b55 100644 --- a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html +++ b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html @@ -74,6 +74,10 @@
+ + + Big team? Hide many contributors at once + @@ -92,4 +96,10 @@ + + + + + \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts index 0d5e85e6b..6a262f677 100644 --- a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts +++ b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts @@ -16,10 +16,9 @@ import { AutocompleteSource, ContributorDto, ContributorsState, + DialogModel, DialogService, RolesState, - Types, - UserDto, UsersService } from '@app/shared'; @@ -57,6 +56,8 @@ export class UsersDataSource implements AutocompleteSource { export class ContributorsPageComponent implements OnInit { public assignContributorForm = new AssignContributorForm(this.formBuilder); + public importDialog = new DialogModel(); + constructor( public readonly appsState: AppsState, public readonly contributorsState: ContributorsState, @@ -89,15 +90,7 @@ export class ContributorsPageComponent implements OnInit { const value = this.assignContributorForm.submit(); if (value) { - let user = value.user; - - if (Types.is(user, UserDto)) { - user = user.id; - } - - const requestDto = { contributorId: user, role: 'Editor', invite: true }; - - this.contributorsState.assign(requestDto) + this.contributorsState.assign(value) .subscribe(isCreated => { this.assignContributorForm.submitCompleted(); diff --git a/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.html b/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.html new file mode 100644 index 000000000..714e44347 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.html @@ -0,0 +1,43 @@ +
+ + + Import contributors + + + +
+ +
+
+ {{status.email}} +
+ +
+ + +
+
+
+ + + + +
+ + + + Emails detected: {{emails}} + + +   + +
+ + + + + +
+
diff --git a/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.scss b/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.scss new file mode 100644 index 000000000..21a9d0e8e --- /dev/null +++ b/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.scss @@ -0,0 +1,10 @@ +@import '_vars'; +@import '_mixins'; + +textarea { + resize: none; +} + +.content { + min-height: 300px; +} \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.ts b/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.ts new file mode 100644 index 000000000..170a7a244 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.ts @@ -0,0 +1,96 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, EventEmitter, Output } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { empty, of } from 'rxjs'; +import { catchError, mergeMap, tap } from 'rxjs/operators'; + +import { + ContributorsState, + ErrorDto, + ImportContributorsForm +} from '@app/shared'; + +interface ImportStatus { + email: string; + result: 'Pending' | 'Failed' | 'Success'; + resultText: string; +} + +@Component({ + selector: 'sqx-import-contributors-dialog', + styleUrls: ['./import-contributors-dialog.component.scss'], + templateUrl: './import-contributors-dialog.component.html' +}) +export class ImportContributorsDialogComponent { + @Output() + public close = new EventEmitter(); + + public importForm = new ImportContributorsForm(this.formBuilder); + public importStatus: ImportStatus[]; + + constructor( + private readonly formBuilder: FormBuilder, + private readonly contributorsState: ContributorsState + ) { + } + + public import() { + const contributors = this.importForm.submit(); + + if (contributors && contributors.length > 0) { + this.importStatus = []; + + for (let contributor of contributors) { + this.importStatus.push({ + email: contributor.contributorId, + result: 'Pending', + resultText: 'Pending' + }); + } + + of(...contributors).pipe( + mergeMap(c => + this.contributorsState.assign(c, { silent: true }).pipe( + tap(created => { + let status = this.importStatus.find(x => x.email === c.contributorId); + + if (status) { + status.resultText = getSuccess(created); + status.result = 'Success'; + } + }), + catchError((error: ErrorDto) => { + let status = this.importStatus.find(x => x.email === c.contributorId); + + if (status) { + status.resultText = getError(error); + status.result = 'Failed'; + } + + return empty(); + }) + ), 1) + ).subscribe(); + } + } + + public emitClose() { + this.close.emit(); + } +} + +function getError(error: ErrorDto): string { + return error.details[0]; +} + +function getSuccess(created: boolean | undefined): string { + return created ? + 'User has been invited and assigned.' : + 'User has been assigned'; +} diff --git a/src/Squidex/app/framework/angular/forms/form-hint.component.ts b/src/Squidex/app/framework/angular/forms/form-hint.component.ts index 5e1da1d1f..00e241b2a 100644 --- a/src/Squidex/app/framework/angular/forms/form-hint.component.ts +++ b/src/Squidex/app/framework/angular/forms/form-hint.component.ts @@ -5,14 +5,17 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; @Component({ selector: 'sqx-form-hint', template: ` - + `, changeDetection: ChangeDetectionStrategy.OnPush }) -export class FormHintComponent {} \ No newline at end of file +export class FormHintComponent { + @Input() + public class: string; +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/status-icon.component.html b/src/Squidex/app/framework/angular/status-icon.component.html new file mode 100644 index 000000000..92f203ff9 --- /dev/null +++ b/src/Squidex/app/framework/angular/status-icon.component.html @@ -0,0 +1,14 @@ + +
+ +
+
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/status-icon.component.scss b/src/Squidex/app/framework/angular/status-icon.component.scss new file mode 100644 index 000000000..54a53a174 --- /dev/null +++ b/src/Squidex/app/framework/angular/status-icon.component.scss @@ -0,0 +1,41 @@ +@import '_mixins'; +@import '_vars'; + +$circle-size-sm: 1.6rem; + +$circle-size-lg: 2.8rem; + +.status { + & { + text-align: center; + background: $color-border; + color: $color-dark-foreground; + vertical-align: middle; + } + + &.sm { + @include circle($circle-size-sm); + line-height: $circle-size-sm + .1rem; + font-size: .5 * $circle-size-sm; + font-weight: normal; + } + + &.lg { + @include circle($circle-size-lg); + line-height: $circle-size-lg + .1rem; + font-size: .5 * $circle-size-lg; + font-weight: normal; + } + + &-pending { + color: inherit; + } + + &-failed { + background: $color-theme-error; + } + + &-success { + background: $color-theme-green; + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/status-icon.component.ts b/src/Squidex/app/framework/angular/status-icon.component.ts new file mode 100644 index 000000000..0ea13974e --- /dev/null +++ b/src/Squidex/app/framework/angular/status-icon.component.ts @@ -0,0 +1,25 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +@Component({ + selector: 'sqx-status-icon', + styleUrls: ['./status-icon.component.scss'], + templateUrl: './status-icon.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class StatusIconComponent { + @Input() + public status: 'Started' | 'Failed' | 'Success' | 'Completed' | 'Failed' | 'Pending'; + + @Input() + public statusText: string; + + @Input() + public size: 'lg' | 'sm' = 'lg'; +} \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 1a1fe2b79..d6fbdd908 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -55,18 +55,19 @@ export * from './angular/routers/parent-link.directive'; export * from './angular/code.component'; export * from './angular/external-link.directive'; -export * from './angular/hover-background.directive'; export * from './angular/highlight.pipe'; +export * from './angular/hover-background.directive'; export * from './angular/ignore-scrollbar.directive'; export * from './angular/image-source.directive'; -export * from './angular/panel.component'; -export * from './angular/panel-container.directive'; export * from './angular/pager.component'; +export * from './angular/panel-container.directive'; +export * from './angular/panel.component'; export * from './angular/popup-link.directive'; export * from './angular/safe-html.pipe'; export * from './angular/scroll-active.directive'; export * from './angular/shortcut.component'; export * from './angular/sorted.directive'; +export * from './angular/status-icon.component'; export * from './angular/stop-click.directive'; export * from './angular/sync-scrolling.directive'; export * from './angular/template-wrapper.directive'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index dd6854957..03c71419e 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -82,6 +82,7 @@ import { ShortTimePipe, SortedDirective, StarsComponent, + StatusIconComponent, StopClickDirective, SyncScollingDirective, TagEditorComponent, @@ -159,6 +160,7 @@ import { ShortTimePipe, SortedDirective, StarsComponent, + StatusIconComponent, StopClickDirective, SyncScollingDirective, TagEditorComponent, @@ -230,6 +232,7 @@ import { ShortTimePipe, SortedDirective, StarsComponent, + StatusIconComponent, StopClickDirective, SyncScollingDirective, TagEditorComponent, diff --git a/src/Squidex/app/shared/state/contributors.forms.ts b/src/Squidex/app/shared/state/contributors.forms.ts index e709f5637..6cc83a631 100644 --- a/src/Squidex/app/shared/state/contributors.forms.ts +++ b/src/Squidex/app/shared/state/contributors.forms.ts @@ -6,12 +6,20 @@ */ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { map } from 'rxjs/operators'; -import { Form, hasNoValue$ } from '@app/framework'; +import { + Form, + hasNoValue$, + Types, + value$ +} from '@app/framework'; + +import { AssignContributorDto } from '../services/contributors.service'; import { UserDto } from './../services/users.service'; -export class AssignContributorForm extends Form { +export class AssignContributorForm extends Form { public hasNoUser = hasNoValue$(this.form.controls['user']); constructor(formBuilder: FormBuilder) { @@ -23,4 +31,50 @@ export class AssignContributorForm extends Form { + public numberOfEmails = value$(this.form.controls['import']).pipe(map(v => extractEmails(v).length)); + + public hasNoUser = this.numberOfEmails.pipe(map(v => v === 0)); + + constructor(formBuilder: FormBuilder) { + super(formBuilder.group({ + import: ['', + [ + Validators.required + ] + ] + })); + } + + protected transformSubmit(value: { import: string }) { + return extractEmails(value.import); + } +} + +function extractEmails(value: string) { + let result: AssignContributorDto[] = []; + + if (value) { + let emails = value.match(EMAIL_REGEX); + + if (emails) { + for (let match of emails) { + result.push({ contributorId: match, role: 'Editor', invite: true }); + } + } + } + + return result; +} + +const EMAIL_REGEX = /(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*/gim; \ No newline at end of file diff --git a/src/Squidex/app/shared/state/contributors.state.ts b/src/Squidex/app/shared/state/contributors.state.ts index cfcd6d80f..c4168c74e 100644 --- a/src/Squidex/app/shared/state/contributors.state.ts +++ b/src/Squidex/app/shared/state/contributors.state.ts @@ -94,7 +94,7 @@ export class ContributorsState extends State { shareSubscribed(this.dialogs)); } - public assign(request: AssignContributorDto): Observable { + public assign(request: AssignContributorDto, options?: { silent: boolean }): Observable { return this.contributorsService.postContributor(this.appName, request, this.version).pipe( catchError(error => { if (Types.is(error, ErrorDto) && error.statusCode === 404) { @@ -106,7 +106,7 @@ export class ContributorsState extends State { tap(({ version, payload }) => { this.replaceContributors(version, payload); }), - shareMapSubscribed(this.dialogs, x => x.payload._meta && x.payload._meta['isInvited'] === '1')); + shareMapSubscribed(this.dialogs, x => x.payload._meta && x.payload._meta['isInvited'] === '1', options)); } private replaceContributors(version: Version, payload: ContributorsPayload) { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs index 6fecf76ef..a316b110d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs @@ -146,7 +146,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AssignContributor { ContributorId = "1" }; - var contributors_1 = contributors_0.Assign("1", Role.Editor); + var contributors_1 = contributors_0.Assign("1", Role.Developer); await GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan); } @@ -159,8 +159,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new AssignContributor { ContributorId = "1" }; - var contributors_1 = contributors_0.Assign("1", Role.Editor); - var contributors_2 = contributors_1.Assign("2", Role.Editor); + var contributors_1 = contributors_0.Assign("1", Role.Developer); + var contributors_2 = contributors_1.Assign("2", Role.Developer); await GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs index 2f504edc3..9e808a16d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs @@ -20,6 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation public class InviteUserCommandMiddlewareTests { private readonly IUserResolver userResolver = A.Fake(); + private readonly IAppEntity app = Mocks.App(NamedId.Of(Guid.NewGuid(), "my-app")); private readonly ICommandBus commandBus = A.Fake(); private readonly InviteUserCommandMiddleware sut; @@ -31,19 +32,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation [Fact] public async Task Should_invite_user_and_change_result() { - var command = new AssignContributor { ContributorId = "me@email.com", IsInviting = true }; - var context = new CommandContext(command, commandBus); + var context = + new CommandContext(new AssignContributor { ContributorId = "me@email.com", Invite = true }, commandBus) + .Complete(app); A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) .Returns(true); - var result = Mocks.App(NamedId.Of(Guid.NewGuid(), "my-app")); - - context.Complete(result); - await sut.HandleAsync(context); - Assert.Same(context.Result().App, result); + Assert.Same(context.Result().App, app); A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) .MustHaveHappened(); @@ -52,34 +50,50 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation [Fact] public async Task Should_invite_user_and_not_change_result_if_not_added() { - var command = new AssignContributor { ContributorId = "me@email.com", IsInviting = true }; - var context = new CommandContext(command, commandBus); + var context = + new CommandContext(new AssignContributor { ContributorId = "me@email.com", Invite = true }, commandBus) + .Complete(app); A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) .Returns(false); - var result = Mocks.App(NamedId.Of(Guid.NewGuid(), "my-app")); - - context.Complete(result); - await sut.HandleAsync(context); - Assert.Same(context.Result(), result); + Assert.Same(context.Result(), app); A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true)) .MustHaveHappened(); } [Fact] - public async Task Should_not_calls_user_resolver_if_not_email() + public async Task Should_not_call_user_resolver_if_not_email() + { + var context = + new CommandContext(new AssignContributor { ContributorId = "123", Invite = true }, commandBus) + .Complete(app); + + await sut.HandleAsync(context); + + A.CallTo(() => userResolver.CreateUserIfNotExists(A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_not_call_user_resolver_if_not_inviting() { - var command = new AssignContributor { ContributorId = "123", IsInviting = true }; - var context = new CommandContext(command, commandBus); + var context = + new CommandContext(new AssignContributor { ContributorId = "123", Invite = false }, commandBus) + .Complete(app); await sut.HandleAsync(context); A.CallTo(() => userResolver.CreateUserIfNotExists(A.Ignored, A.Ignored)) .MustNotHaveHappened(); } + + private CommandContext Context(AssignContributor command) + { + return new CommandContext(command, commandBus); + } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexCommandMiddlewareTests.cs index 06ddb1d39..58045e36a 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexCommandMiddlewareTests.cs @@ -38,10 +38,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes [Fact] public async Task Should_add_schema_to_index_on_create() { - var command = new CreateSchema { SchemaId = schemaId.Id, Name = schemaId.Name, AppId = appId }; - var context = new CommandContext(command, commandBus); - - context.Complete(); + var context = + new CommandContext(new CreateSchema { SchemaId = schemaId.Id, Name = schemaId.Name, AppId = appId }, commandBus) + .Complete(); await sut.HandleAsync(context); diff --git a/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs b/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs index a9f8145fe..05b58b31b 100644 --- a/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs +++ b/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs @@ -38,7 +38,7 @@ namespace Squidex.Web.CommandMiddlewares .Returns(null); var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -51,7 +51,7 @@ namespace Squidex.Web.CommandMiddlewares httpContext.Request.Headers[HeaderNames.IfMatch] = "13"; var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -64,7 +64,7 @@ namespace Squidex.Web.CommandMiddlewares httpContext.Request.Headers[HeaderNames.IfMatch] = "W/13"; var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -75,7 +75,7 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_add_etag_header_to_response() { var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); context.Complete(new EntitySavedResult(17)); @@ -83,5 +83,10 @@ namespace Squidex.Web.CommandMiddlewares Assert.Equal(new StringValues("17"), httpContextAccessor.HttpContext.Response.Headers[HeaderNames.ETag]); } + + private CommandContext Ctx(ICommand command) + { + return new CommandContext(command, commandBus); + } } } diff --git a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithActorCommandMiddlewareTests.cs b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithActorCommandMiddlewareTests.cs index 8ecdb1e15..fb1ab5baa 100644 --- a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithActorCommandMiddlewareTests.cs +++ b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithActorCommandMiddlewareTests.cs @@ -37,7 +37,7 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_throw_security_exception_when_no_subject_or_client_is_found() { var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await Assert.ThrowsAsync(() => sut.HandleAsync(context)); } @@ -49,7 +49,7 @@ namespace Squidex.Web.CommandMiddlewares .Returns(null); var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -62,7 +62,7 @@ namespace Squidex.Web.CommandMiddlewares httpContext.User = CreatePrincipal(OpenIdClaims.Subject, "me"); var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -75,7 +75,7 @@ namespace Squidex.Web.CommandMiddlewares httpContext.User = CreatePrincipal(OpenIdClaims.ClientId, "my-client"); var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -88,13 +88,18 @@ namespace Squidex.Web.CommandMiddlewares httpContext.User = CreatePrincipal(OpenIdClaims.ClientId, "my-client"); var command = new CreateContent { Actor = new RefToken("subject", "me") }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); Assert.Equal(new RefToken("subject", "me"), command.Actor); } + private CommandContext Ctx(ICommand command) + { + return new CommandContext(command, commandBus); + } + private static ClaimsPrincipal CreatePrincipal(string claimType, string claimValue) { var claimsPrincipal = new ClaimsPrincipal(); diff --git a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs index 76dfb4b41..bf70f36df 100644 --- a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs +++ b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs @@ -47,7 +47,7 @@ namespace Squidex.Web.CommandMiddlewares appContext.App = null; var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await Assert.ThrowsAsync(() => sut.HandleAsync(context)); } @@ -56,7 +56,7 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_assign_app_id_and_name_to_app_command() { var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -67,7 +67,7 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_assign_app_id_to_app_self_command() { var command = new ChangePlan(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -78,7 +78,7 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_not_override_app_id() { var command = new ChangePlan { AppId = Guid.NewGuid() }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -89,11 +89,16 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_not_override_app_id_and_name() { var command = new CreateContent { AppId = NamedId.Of(Guid.NewGuid(), "other-app") }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); Assert.NotEqual(appId, command.AppId); } + + private CommandContext Ctx(ICommand command) + { + return new CommandContext(command, commandBus); + } } } diff --git a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs index e3959732f..5ebe6fe24 100644 --- a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs +++ b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs @@ -72,7 +72,7 @@ namespace Squidex.Web.CommandMiddlewares actionContext.RouteData.Values["name"] = "other-schema"; var command = new CreateContent { AppId = appId }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await Assert.ThrowsAsync(() => sut.HandleAsync(context)); } @@ -81,7 +81,7 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_do_nothing_when_route_has_no_parameter() { var command = new CreateContent(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -94,7 +94,7 @@ namespace Squidex.Web.CommandMiddlewares actionContext.RouteData.Values["name"] = schemaId.Name; var command = new CreateContent { AppId = appId }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -107,7 +107,7 @@ namespace Squidex.Web.CommandMiddlewares actionContext.RouteData.Values["name"] = schemaId.Id; var command = new CreateContent { AppId = appId }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -120,7 +120,7 @@ namespace Squidex.Web.CommandMiddlewares actionContext.RouteData.Values["name"] = schemaId.Name; var command = new UpdateSchema(); - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -131,7 +131,7 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_not_override_schema_id() { var command = new CreateSchema { SchemaId = Guid.NewGuid() }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); @@ -142,11 +142,16 @@ namespace Squidex.Web.CommandMiddlewares public async Task Should_not_override_schema_id_and_name() { var command = new CreateContent { SchemaId = NamedId.Of(Guid.NewGuid(), "other-schema") }; - var context = new CommandContext(command, commandBus); + var context = Ctx(command); await sut.HandleAsync(context); Assert.NotEqual(appId, command.AppId); } + + private CommandContext Ctx(ICommand command) + { + return new CommandContext(command, commandBus); + } } }