diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 71ab5c4f9..cf9d4950d 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -422,6 +422,8 @@ "contents.tableHeaders.nextStatus": "Next Status", "contents.tableHeaders.status": "Status", "contents.tableHeaders.version": "Version", + "contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?", + "contents.unpublishReferrerConfirmTitle": "Unpublish content", "contents.unsavedChangesText": "You have unsaved changes. Do you want to load them now?", "contents.unsavedChangesTitle": "Unsaved changes", "contents.updated": "Content updated successfully.", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index fa93e54b2..efe762f51 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -422,6 +422,8 @@ "contents.tableHeaders.nextStatus": "Stato successivo", "contents.tableHeaders.status": "Stato", "contents.tableHeaders.version": "Versione", + "contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?", + "contents.unpublishReferrerConfirmTitle": "Unpublish content", "contents.unsavedChangesText": "Non hai salvato le modifiche. Vuoi salvarle adesso?", "contents.unsavedChangesTitle": "Modifiche non salvate", "contents.updated": "Contenuto aggiornato con successo.", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index f9adc2599..4dede1bec 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -422,6 +422,8 @@ "contents.tableHeaders.nextStatus": "Volgende status", "contents.tableHeaders.status": "Status", "contents.tableHeaders.version": "Versie", + "contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?", + "contents.unpublishReferrerConfirmTitle": "Unpublish content", "contents.unsavedChangesText": "Je hebt niet-opgeslagen wijzigingen. Wil je ze nu laden?", "contents.unsavedChangesTitle": "Niet-opgeslagen wijzigingen", "contents.updated": "Inhoud succesvol bijgewerkt.", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 71ab5c4f9..dd313a50f 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -366,8 +366,10 @@ "contents.deleteConfirmTitle": "Delete content", "contents.deleteFailed": "Failed to delete content. Please reload.", "contents.deleteManyConfirmText": "Do you really want to delete the selected content items?", - "contents.deleteReferrerConfirmText": "The content is referenced by another content item.\n\nDo you really want to delete the content?", + "contents.deleteReferrerConfirmText": "The content is referenced by another content item.\n\nDo you really want to delete this content?", "contents.deleteReferrerConfirmTitle": "Delete content", + "contents.unpublishReferrerConfirmText": "The content is referenced by another published content item.\n\nDo you really want to unpublish this content?", + "contents.unpublishReferrerConfirmTitle": "Unpublish content", "contents.deleteVersionConfirmText": "Do you really want to delete this version?", "contents.deleteVersionFailed": "Failed to delete version. Please reload.", "contents.draftNew": "New Draft", diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs index 54907913e..5c8332459 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs @@ -30,6 +30,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents private readonly QueryContentsByIds queryContentsById; private readonly QueryContentsByQuery queryContentsByQuery; private readonly QueryIdsAsync queryIdsAsync; + private readonly QueryReferrersAsync queryReferrersAsync; public MongoContentCollectionPublished(IMongoDatabase database, IAppProvider appProvider, ITextIndex indexer, DataConverter converter) : base(database) @@ -37,6 +38,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents queryContentAsync = new QueryContent(converter); queryContentsById = new QueryContentsByIds(converter, appProvider); queryContentsByQuery = new QueryContentsByQuery(converter, indexer, appProvider); + queryReferrersAsync = new QueryReferrersAsync(); queryIdsAsync = new QueryIdsAsync(appProvider); } @@ -63,6 +65,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await queryContentAsync.PrepareAsync(collection, ct); await queryContentsById.PrepareAsync(collection, ct); await queryContentsByQuery.PrepareAsync(collection, ct); + await queryReferrersAsync.PrepareAsync(collection, ct); await queryIdsAsync.PrepareAsync(collection, ct); } @@ -114,6 +117,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents } } + public async Task HasReferrersAsync(DomainId appId, DomainId contentId) + { + using (Profiler.TraceMethod()) + { + return await queryReferrersAsync.DoAsync(appId, contentId); + } + } + public Task UpsertVersionedAsync(DomainId documentId, long oldVersion, MongoContentEntity entity) { return Collection.UpsertVersionedAsync(documentId, oldVersion, entity.Version, entity); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 915026a91..aafb095e4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -120,6 +120,18 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents } } + public Task HasReferrersAsync(DomainId appId, DomainId contentId, SearchScope scope) + { + if (scope == SearchScope.All) + { + return collectionAll.HasReferrersAsync(appId, contentId); + } + else + { + return collectionPublished.HasReferrersAsync(appId, contentId); + } + } + public Task ResetScheduledAsync(DomainId documentId) { return collectionAll.ResetScheduledAsync(documentId); @@ -135,11 +147,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return collectionAll.QueryIdsAsync(appId, schemaId, filterNode); } - public Task HasReferrersAsync(DomainId appId, DomainId contentId) - { - return collectionAll.HasReferrersAsync(appId, contentId); - } - public IEnumerable> GetInternalCollections() { yield return collectionAll.GetInternalCollection(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index 00081f65d..058f7d50f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -112,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case AssignContributor assignContributor: return UpdateReturnAsync(assignContributor, async c => { - await GuardAppContributors.CanAssign(Snapshot.Contributors, Snapshot.Roles, c, userResolver, GetPlan()); + await GuardAppContributors.CanAssign(c, Snapshot, userResolver, GetPlan()); AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId)); @@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case RemoveContributor removeContributor: return UpdateReturn(removeContributor, c => { - GuardAppContributors.CanRemove(Snapshot.Contributors, c); + GuardAppContributors.CanRemove(c, Snapshot); RemoveContributor(c); @@ -132,7 +132,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case AttachClient attachClient: return UpdateReturn(attachClient, c => { - GuardAppClients.CanAttach(Snapshot.Clients, c); + GuardAppClients.CanAttach(c, Snapshot); AttachClient(c); @@ -142,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case UpdateClient updateClient: return UpdateReturn(updateClient, c => { - GuardAppClients.CanUpdate(Snapshot.Clients, c, Snapshot.Roles); + GuardAppClients.CanUpdate(c, Snapshot); UpdateClient(c); @@ -152,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case RevokeClient revokeClient: return UpdateReturn(revokeClient, c => { - GuardAppClients.CanRevoke(Snapshot.Clients, c); + GuardAppClients.CanRevoke(c, Snapshot); RevokeClient(c); @@ -172,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case UpdateWorkflow updateWorkflow: return UpdateReturn(updateWorkflow, c => { - GuardAppWorkflows.CanUpdate(Snapshot.Workflows, c); + GuardAppWorkflows.CanUpdate(c, Snapshot); UpdateWorkflow(c); @@ -182,7 +182,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case DeleteWorkflow deleteWorkflow: return UpdateReturn(deleteWorkflow, c => { - GuardAppWorkflows.CanDelete(Snapshot.Workflows, c); + GuardAppWorkflows.CanDelete(c, Snapshot); DeleteWorkflow(c); @@ -192,7 +192,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case AddLanguage addLanguage: return UpdateReturn(addLanguage, c => { - GuardAppLanguages.CanAdd(Snapshot.Languages, c); + GuardAppLanguages.CanAdd(c, Snapshot); AddLanguage(c); @@ -202,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case RemoveLanguage removeLanguage: return UpdateReturn(removeLanguage, c => { - GuardAppLanguages.CanRemove(Snapshot.Languages, c); + GuardAppLanguages.CanRemove(c, Snapshot); RemoveLanguage(c); @@ -212,7 +212,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case UpdateLanguage updateLanguage: return UpdateReturn(updateLanguage, c => { - GuardAppLanguages.CanUpdate(Snapshot.Languages, c); + GuardAppLanguages.CanUpdate(c, Snapshot); UpdateLanguage(c); @@ -222,7 +222,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case AddRole addRole: return UpdateReturn(addRole, c => { - GuardAppRoles.CanAdd(Snapshot.Roles, c); + GuardAppRoles.CanAdd(c, Snapshot); AddRole(c); @@ -232,7 +232,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case DeleteRole deleteRole: return UpdateReturn(deleteRole, c => { - GuardAppRoles.CanDelete(Snapshot.Roles, c, Snapshot.Contributors, Snapshot.Clients); + GuardAppRoles.CanDelete(c, Snapshot); DeleteRole(c); @@ -242,7 +242,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case UpdateRole updateRole: return UpdateReturn(updateRole, c => { - GuardAppRoles.CanUpdate(Snapshot.Roles, c); + GuardAppRoles.CanUpdate(c, Snapshot); UpdateRole(c); @@ -252,7 +252,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case AddPattern addPattern: return UpdateReturn(addPattern, c => { - GuardAppPatterns.CanAdd(Snapshot.Patterns, c); + GuardAppPatterns.CanAdd(c, Snapshot); AddPattern(c); @@ -262,7 +262,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case DeletePattern deletePattern: return UpdateReturn(deletePattern, c => { - GuardAppPatterns.CanDelete(Snapshot.Patterns, c); + GuardAppPatterns.CanDelete(c, Snapshot); DeletePattern(c); @@ -272,7 +272,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case UpdatePattern updatePattern: return UpdateReturn(updatePattern, c => { - GuardAppPatterns.CanUpdate(Snapshot.Patterns, c); + GuardAppPatterns.CanUpdate(c, Snapshot); UpdatePattern(c); @@ -282,7 +282,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case ChangePlan changePlan: return UpdateReturnAsync(changePlan, async c => { - GuardApp.CanChangePlan(c, Snapshot.Plan, appPlansProvider); + GuardApp.CanChangePlan(c, Snapshot, appPlansProvider); if (c.FromCallback) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs index f952b63a4..fb92f11f2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Infrastructure; @@ -53,10 +52,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards Guard.NotNull(command, nameof(command)); } - public static void CanChangePlan(ChangePlan command, AppPlan? plan, IAppPlansProvider appPlans) + public static void CanChangePlan(ChangePlan command, IAppEntity app, IAppPlansProvider appPlans) { Guard.NotNull(command, nameof(command)); + var plan = app.Plan; + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.PlanId)) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs index ba43cdb14..1ccca8a7c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs @@ -15,10 +15,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppClients { - public static void CanAttach(AppClients clients, AttachClient command) + public static void CanAttach(AttachClient command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var clients = app.Clients; + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Id)) @@ -32,10 +34,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanRevoke(AppClients clients, RevokeClient command) + public static void CanRevoke(RevokeClient command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var clients = app.Clients; + GetClientOrThrow(clients, command.Id); Validate.It(e => @@ -47,10 +51,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanUpdate(AppClients clients, UpdateClient command, Roles roles) + public static void CanUpdate(UpdateClient command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var clients = app.Clients; + GetClientOrThrow(clients, command.Id); Validate.It(e => @@ -60,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards e(Not.Defined("Clientd"), nameof(command.Id)); } - if (command.Role != null && !roles.Contains(command.Role)) + if (command.Role != null && !app.Roles.Contains(command.Role)) { e(Not.Valid(nameof(command.Role)), nameof(command.Role)); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs index bcab9abfa..7edd25feb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs @@ -20,13 +20,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppContributors { - public static Task CanAssign(AppContributors contributors, Roles roles, AssignContributor command, IUserResolver users, IAppLimitsPlan plan) + public static Task CanAssign(AssignContributor command, IAppEntity app, IUserResolver users, IAppLimitsPlan plan) { Guard.NotNull(command, nameof(command)); + var contributors = app.Contributors; + return Validate.It(async e => { - if (!roles.Contains(command.Role)) + if (!app.Roles.Contains(command.Role)) { e(Not.Valid(nameof(command.Role)), nameof(command.Role)); } @@ -63,10 +65,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanRemove(AppContributors contributors, RemoveContributor command) + public static void CanRemove(RemoveContributor command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var contributors = app.Contributors; + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.ContributorId)) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs index ae1041a8c..ec3f66b45 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs @@ -15,10 +15,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppLanguages { - public static void CanAdd(LanguagesConfig languages, AddLanguage command) + public static void CanAdd(AddLanguage command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var languages = app.Languages; + Validate.It(e => { var language = command.Language; @@ -34,10 +36,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanRemove(LanguagesConfig languages, RemoveLanguage command) + public static void CanRemove(RemoveLanguage command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var languages = app.Languages; + Validate.It(e => { var language = command.Language; @@ -58,10 +62,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanUpdate(LanguagesConfig languages, UpdateLanguage command) + public static void CanUpdate(UpdateLanguage command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var languages = app.Languages; + Validate.It(e => { var language = command.Language; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs index 673bb6acc..a20fefe8f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs @@ -4,9 +4,9 @@ // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== + using System; using System.Linq; -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; @@ -16,10 +16,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppPatterns { - public static void CanAdd(AppPatterns patterns, AddPattern command) + public static void CanAdd(AddPattern command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var patterns = app.Patterns; + Validate.It(e => { if (command.PatternId == DomainId.Empty) @@ -53,20 +55,24 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanDelete(AppPatterns patterns, DeletePattern command) + public static void CanDelete(DeletePattern command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var patterns = app.Patterns; + if (!patterns.ContainsKey(command.PatternId)) { throw new DomainObjectNotFoundException(command.PatternId.ToString()); } } - public static void CanUpdate(AppPatterns patterns, UpdatePattern command) + public static void CanUpdate(UpdatePattern command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var patterns = app.Patterns; + if (!patterns.ContainsKey(command.PatternId)) { throw new DomainObjectNotFoundException(command.PatternId.ToString()); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs index 69c203cfd..fb214760b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs @@ -17,10 +17,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppRoles { - public static void CanAdd(Roles roles, AddRole command) + public static void CanAdd(AddRole command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var roles = app.Roles; + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Name)) @@ -34,10 +36,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanDelete(Roles roles, DeleteRole command, AppContributors contributors, AppClients clients) + public static void CanDelete(DeleteRole command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var roles = app.Roles; + CheckRoleExists(roles, command.Name); Validate.It(e => @@ -51,22 +55,24 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards e(T.Get("apps.roles.defaultRoleNotRemovable")); } - if (clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase))) + if (app.Clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase))) { e(T.Get("apps.roles.usedRoleByClientsNotRemovable")); } - if (contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase))) + if (app.Contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase))) { e(T.Get("apps.roles.usedRoleByContributorsNotRemovable")); } }); } - public static void CanUpdate(Roles roles, UpdateRole command) + public static void CanUpdate(UpdateRole command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var roles = app.Roles; + CheckRoleExists(roles, command.Name); Validate.It(e => diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs index 33e314782..2eec80367 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs @@ -28,10 +28,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanUpdate(Workflows workflows, UpdateWorkflow command) + public static void CanUpdate(UpdateWorkflow command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var workflows = app.Workflows; + CheckWorkflowExists(workflows, command.WorkflowId); Validate.It(e => @@ -90,10 +92,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards }); } - public static void CanDelete(Workflows workflows, DeleteWorkflow command) + public static void CanDelete(DeleteWorkflow command, IAppEntity app) { Guard.NotNull(command, nameof(command)); + var workflows = app.Workflows; + CheckWorkflowExists(workflows, command.WorkflowId); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs index 37e23bb62..a1cd5ccb3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs @@ -20,7 +20,6 @@ using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; -using Squidex.Infrastructure.Translations; using Squidex.Log; using IAssetTagService = Squidex.Domain.Apps.Core.Tags.ITagService; @@ -102,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Assets case MoveAsset moveAsset: return UpdateReturnAsync(moveAsset, async c => { - await GuardAsset.CanMove(c, assetQuery, Snapshot.ParentId); + await GuardAsset.CanMove(c, Snapshot, assetQuery); Move(c); @@ -111,17 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Assets case DeleteAsset deleteAsset: return UpdateAsync(deleteAsset, async c => { - GuardAsset.CanDelete(c); - - if (c.CheckReferrers) - { - var hasReferrer = await contentRepository.HasReferrersAsync(Snapshot.AppId.Id, c.AssetId); - - if (hasReferrer) - { - throw new DomainException(T.Get("assets.referenced")); - } - } + await GuardAsset.CanDelete(c, Snapshot, contentRepository); await assetTags.NormalizeTagsAsync(Snapshot.AppId.Id, TagGroups.Assets, null, Snapshot.Tags); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs index fd9c502f5..2558007a8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Assets case MoveAssetFolder moveAssetFolder: return UpdateReturnAsync(moveAssetFolder, async c => { - await GuardAssetFolder.CanMove(c, assetQuery, Snapshot.Id, Snapshot.ParentId); + await GuardAssetFolder.CanMove(c, Snapshot, assetQuery); Move(c); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs index ab65e9ef5..86b104577 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; @@ -30,13 +32,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards }); } - public static Task CanMove(MoveAsset command, IAssetQueryService assetQuery, DomainId oldParentId) + public static Task CanMove(MoveAsset command, IAssetEntity asset, IAssetQueryService assetQuery) { Guard.NotNull(command, nameof(command)); return Validate.It(async e => { - if (command.ParentId != oldParentId) + if (command.ParentId != asset.ParentId) { await CheckPathAsync(command.AppId.Id, command.ParentId, assetQuery, e); } @@ -48,9 +50,19 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards Guard.NotNull(command, nameof(command)); } - public static void CanDelete(DeleteAsset command) + public static async Task CanDelete(DeleteAsset command, IAssetEntity asset, IContentRepository contentRepository) { Guard.NotNull(command, nameof(command)); + + if (command.CheckReferrers) + { + var hasReferrer = await contentRepository.HasReferrersAsync(asset.AppId.Id, asset.Id, SearchScope.All); + + if (hasReferrer) + { + throw new DomainException(T.Get("assets.referenced")); + } + } } private static async Task CheckPathAsync(DomainId appId, DomainId parentId, IAssetQueryService assetQuery, AddValidation e) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs index 21aa6665d..36197dd60 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs @@ -43,15 +43,15 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards }); } - public static Task CanMove(MoveAssetFolder command, IAssetQueryService assetQuery, DomainId id, DomainId oldParentId) + public static Task CanMove(MoveAssetFolder command, IAssetFolderEntity assetFolder, IAssetQueryService assetQuery) { Guard.NotNull(command, nameof(command)); return Validate.It(async e => { - if (command.ParentId != oldParentId) + if (command.ParentId != assetFolder.ParentId) { - await CheckPathAsync(command.AppId.Id, command.ParentId, assetQuery, id, e); + await CheckPathAsync(command.AppId.Id, command.ParentId, assetQuery, assetFolder.Id, e); } }); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs index 33203c387..13a702239 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs @@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Comments case UpdateComment updateComment: return Upsert(updateComment, c => { - GuardComments.CanUpdate(Key, events, c); + GuardComments.CanUpdate(c, Key, events); Update(c); @@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Comments case DeleteComment deleteComment: return Upsert(deleteComment, c => { - GuardComments.CanDelete(Key, events, c); + GuardComments.CanDelete(c, Key, events); Delete(c); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs index f2d65df15..f357faf23 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards }); } - public static void CanUpdate(string commentsId, List> events, UpdateComment command) + public static void CanUpdate(UpdateComment command, string commentsId, List> events) { Guard.NotNull(command, nameof(command)); @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards }); } - public static void CanDelete(string commentsId, List> events, DeleteComment command) + public static void CanDelete(DeleteComment command, string commentsId, List> events) { Guard.NotNull(command, nameof(command)); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs index 733ad09ee..ebd868345 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs @@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands public DomainId? StatusJobId { get; set; } + public bool CheckReferrers { get; set; } + public bool DoNotValidate { get; set; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index 185c25347..d5468dece 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { await LoadContext(c.AppId, c.SchemaId, c, c.OptimizeValidation); - await GuardContent.CanCreate(context.Schema, context.Workflow, c); + await GuardContent.CanCreate(c, context.Workflow, context.Schema); var status = await context.GetInitialStatusAsync(); @@ -155,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case UpdateContent updateContent: return UpdateReturnAsync(updateContent, async c => { - await GuardContent.CanUpdate(Snapshot, context.Workflow, c); + await GuardContent.CanUpdate(c, Snapshot, context.Workflow); return await UpdateAsync(c, x => c.Data, false); }); @@ -163,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case PatchContent patchContent: return UpdateReturnAsync(patchContent, async c => { - await GuardContent.CanPatch(Snapshot, context.Workflow, c); + await GuardContent.CanPatch(c, Snapshot, context.Workflow); return await UpdateAsync(c, c.Data.MergeInto, true); }); @@ -175,7 +175,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c); - await GuardContent.CanChangeStatus(context.Schema, Snapshot, context.Workflow, c); + await GuardContent.CanChangeStatus(c, Snapshot, context.Workflow, context.Repository, context.Schema); if (c.DueTime.HasValue) { @@ -234,7 +234,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c); - await GuardContent.CanDelete(context.Schema, Snapshot, context.Repository, c); + await GuardContent.CanDelete(c, Snapshot, context.Repository, context.Schema); if (!c.DoNotScript) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index c1d30b0a8..411422417 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -11,7 +11,6 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; @@ -21,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { public static class GuardContent { - public static async Task CanCreate(ISchemaEntity schema, IContentWorkflow contentWorkflow, CreateContent command) + public static async Task CanCreate(CreateContent command, IContentWorkflow contentWorkflow, ISchemaEntity schema) { Guard.NotNull(command, nameof(command)); @@ -41,7 +40,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards }); } - public static async Task CanUpdate(ContentState content, IContentWorkflow contentWorkflow, UpdateContent command) + public static async Task CanUpdate(UpdateContent command, + IContentEntity content, + IContentWorkflow contentWorkflow) { Guard.NotNull(command, nameof(command)); @@ -53,7 +54,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards await ValidateCanUpdate(content, contentWorkflow, command.User); } - public static async Task CanPatch(ContentState content, IContentWorkflow contentWorkflow, PatchContent command) + public static async Task CanPatch(PatchContent command, + IContentEntity content, + IContentWorkflow contentWorkflow) { Guard.NotNull(command, nameof(command)); @@ -65,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards await ValidateCanUpdate(content, contentWorkflow, command.User); } - public static void CanDeleteDraft(DeleteContentDraft command, ContentState content) + public static void CanDeleteDraft(DeleteContentDraft command, IContentEntity content) { Guard.NotNull(command, nameof(command)); @@ -75,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards } } - public static void CanCreateDraft(CreateContentDraft command, ContentState content) + public static void CanCreateDraft(CreateContentDraft command, IContentEntity content) { Guard.NotNull(command, nameof(command)); @@ -85,13 +88,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards } } - public static Task CanChangeStatus(ISchemaEntity schema, ContentState content, IContentWorkflow contentWorkflow, ChangeContentStatus command) + public static Task CanChangeStatus(ChangeContentStatus command, + IContentEntity content, + IContentWorkflow contentWorkflow, + IContentRepository contentRepository, + ISchemaEntity schema) { Guard.NotNull(command, nameof(command)); if (schema.SchemaDef.IsSingleton) { - if (content.NewVersion == null || command.Status != Status.Published) + if (content.NewStatus == null || command.Status != Status.Published) { throw new DomainException(T.Get("contents.singletonNotChangeable")); } @@ -101,9 +108,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards return Validate.It(async e => { - if (!await contentWorkflow.CanMoveToAsync(content, content.EditingStatus, command.Status, command.User)) + var status = content.NewStatus ?? content.Status; + + if (!await contentWorkflow.CanMoveToAsync(content, status, command.Status, command.User)) { - e(T.Get("contents.statusTransitionNotAllowed", new { oldStatus = content.EditingStatus, newStatus = command.Status }), nameof(command.Status)); + var values = new { oldStatus = status, newStatus = command.Status }; + + e(T.Get("contents.statusTransitionNotAllowed", values), nameof(command.Status)); + } + + if (content.Status == Status.Published && command.CheckReferrers) + { + var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, command.ContentId, SearchScope.Published); + + if (hasReferrer) + { + throw new DomainException(T.Get("contents.referenced")); + } } if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant()) @@ -113,7 +134,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards }); } - public static async Task CanDelete(ISchemaEntity schema, ContentState content, IContentRepository contentRepository, DeleteContent command) + public static async Task CanDelete(DeleteContent command, + IContentEntity content, + IContentRepository contentRepository, + ISchemaEntity schema) { Guard.NotNull(command, nameof(command)); @@ -124,7 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards if (command.CheckReferrers) { - var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, command.ContentId); + var hasReferrer = await contentRepository.HasReferrersAsync(content.AppId.Id, command.ContentId, SearchScope.All); if (hasReferrer) { @@ -141,11 +165,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards } } - private static async Task ValidateCanUpdate(ContentState content, IContentWorkflow contentWorkflow, ClaimsPrincipal user) + private static async Task ValidateCanUpdate(IContentEntity content, IContentWorkflow contentWorkflow, ClaimsPrincipal user) { - if (!await contentWorkflow.CanUpdateAsync(content, content.EditingStatus, user)) + var status = content.NewStatus ?? content.Status; + + if (!await contentWorkflow.CanUpdateAsync(content, status, user)) { - throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status = content.EditingStatus })); + throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index b598dcf3f..15fdaed5d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories Task FindContentAsync(IAppEntity app, ISchemaEntity schema, DomainId id, SearchScope scope); - Task HasReferrersAsync(DomainId appId, DomainId contentId); + Task HasReferrersAsync(DomainId appId, DomainId contentId, SearchScope scope); Task ResetScheduledAsync(DomainId documentId); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs index 44e2e2c24..64e8f2f54 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards }); } - public static Task CanUpdate(UpdateRule command, DomainId appId, IAppProvider appProvider) + public static Task CanUpdate(UpdateRule command, IRuleEntity rule, IAppProvider appProvider) { Guard.NotNull(command, nameof(command)); @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (command.Trigger != null) { - var errors = await RuleTriggerValidator.ValidateAsync(appId, command.Trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(rule.AppId.Id, command.Trigger, appProvider); errors.Foreach((x, _) => x.AddTo(e)); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs index 9477e9e11..a27f84163 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs @@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Rules case UpdateRule updateRule: return UpdateReturnAsync(updateRule, async c => { - await GuardRule.CanUpdate(c, Snapshot.AppId.Id, appProvider); + await GuardRule.CanUpdate(c, Snapshot, appProvider); Update(c); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ChangeStatusDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ChangeStatusDto.cs index 4a456f068..a22931c29 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ChangeStatusDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ChangeStatusDto.cs @@ -9,6 +9,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Validation; namespace Squidex.Areas.Api.Controllers.Contents.Models @@ -26,9 +27,14 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models /// public Instant? DueTime { get; set; } + /// + /// True to check referrers of this content. + /// + public bool CheckReferrers { get; set; } + public ChangeContentStatus ToCommand(DomainId id) { - return new ChangeContentStatus { ContentId = id, Status = Status, DueTime = DueTime }; + return SimpleMapper.Map(this, new ChangeContentStatus { ContentId = id }); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs index 5f376d735..eb7e3b06a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Apps.Commands; @@ -27,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AttachClient(); - ValidationAssert.Throws(() => GuardAppClients.CanAttach(clients_0, command), + ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App(clients_0)), new ValidationError("Client ID is required.", "Id")); } @@ -38,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("android", "secret"); - ValidationAssert.Throws(() => GuardAppClients.CanAttach(clients_1, command), + ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App(clients_1)), new ValidationError("A client with the same id already exists.")); } @@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("android", "secret"); - GuardAppClients.CanAttach(clients_1, command); + GuardAppClients.CanAttach(command, App(clients_1)); } [Fact] @@ -57,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RevokeClient(); - ValidationAssert.Throws(() => GuardAppClients.CanRevoke(clients_0, command), + ValidationAssert.Throws(() => GuardAppClients.CanRevoke(command, App(clients_0)), new ValidationError("Client ID is required.", "Id")); } @@ -66,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RevokeClient { Id = "ios" }; - Assert.Throws(() => GuardAppClients.CanRevoke(clients_0, command)); + Assert.Throws(() => GuardAppClients.CanRevoke(command, App(clients_0))); } [Fact] @@ -76,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); - GuardAppClients.CanRevoke(clients_1, command); + GuardAppClients.CanRevoke(command, App(clients_1)); } [Fact] @@ -84,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateClient { Name = "iOS" }; - ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_0, command, Roles.Empty), + ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_0)), new ValidationError("Client ID is required.", "Id")); } @@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateClient { Id = "ios", Name = "iOS" }; - Assert.Throws(() => GuardAppClients.CanUpdate(clients_0, command, Roles.Empty)); + Assert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_0))); } [Fact] @@ -103,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); - ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_1, command, roles), + ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)), new ValidationError("Role is not a valid value.", "Role")); } @@ -114,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); - ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_1, command, roles), + ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)), new ValidationError("ApiCallsLimit must be greater or equal to 0.", "ApiCallsLimit")); } @@ -125,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); - ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_1, command, roles), + ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)), new ValidationError("ApiTrafficLimit must be greater or equal to 0.", "ApiTrafficLimit")); } @@ -136,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); - GuardAppClients.CanUpdate(clients_1, command, roles); + GuardAppClients.CanUpdate(command, App(clients_1)); } [Fact] @@ -146,7 +147,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); - GuardAppClients.CanUpdate(clients_1, command, roles); + GuardAppClients.CanUpdate(command, App(clients_1)); } [Fact] @@ -156,7 +157,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); - GuardAppClients.CanUpdate(clients_1, command, roles); + GuardAppClients.CanUpdate(command, App(clients_1)); + } + + private IAppEntity App(AppClients clients) + { + var app = A.Fake(); + + A.CallTo(() => app.Clients) + .Returns(clients); + A.CallTo(() => app.Roles) + .Returns(roles); + + return app; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs index 9daa7316c..7cf3a8d42 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AssignContributor(); - await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan), + await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan), new ValidationError("Contributor ID or email is required.", "ContributorId")); } @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AssignContributor { ContributorId = "1", Role = "Invalid" }; - await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan), + await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan), new ValidationError("Role is not a valid value.", "Role")); } @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var contributors_1 = contributors_0.Assign("1", Role.Owner); - await GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan); + await GuardAppContributors.CanAssign(command, App(contributors_1), users, appPlan); } [Fact] @@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var contributors_1 = contributors_0.Assign("1", Role.Owner); - await GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan); + await GuardAppContributors.CanAssign(command, App(contributors_1), users, appPlan); } [Fact] @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AssignContributor { ContributorId = "notfound", Role = Role.Owner }; - await Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan)); + await Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan)); } [Fact] @@ -99,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AssignContributor { ContributorId = "3", Role = Role.Editor, Actor = new RefToken("user", "3") }; - await Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan)); + await Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan)); } [Fact] @@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var contributors_1 = contributors_0.Assign("1", Role.Owner); var contributors_2 = contributors_1.Assign("2", Role.Editor); - await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan), + await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_2), users, appPlan), new ValidationError("You have reached the maximum number of contributors for your plan.")); } @@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new AssignContributor { ContributorId = "1" }; - await GuardAppContributors.CanAssign(contributors_0, roles, command, users, appPlan); + await GuardAppContributors.CanAssign(command, App(contributors_0), users, appPlan); } [Fact] @@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var contributors_1 = contributors_0.Assign("1", Role.Developer); - await GuardAppContributors.CanAssign(contributors_1, roles, command, users, appPlan); + await GuardAppContributors.CanAssign(command, App(contributors_1), users, appPlan); } [Fact] @@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards 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); + await GuardAppContributors.CanAssign(command, App(contributors_2), users, appPlan); } [Fact] @@ -163,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var contributors_1 = contributors_0.Assign("1", Role.Editor); var contributors_2 = contributors_1.Assign("2", Role.Editor); - await GuardAppContributors.CanAssign(contributors_2, roles, command, users, appPlan); + await GuardAppContributors.CanAssign(command, App(contributors_2), users, appPlan); } [Fact] @@ -171,7 +171,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RemoveContributor(); - ValidationAssert.Throws(() => GuardAppContributors.CanRemove(contributors_0, command), + ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App(contributors_0)), new ValidationError("Contributor ID or email is required.", "ContributorId")); } @@ -180,7 +180,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RemoveContributor { ContributorId = "1" }; - Assert.Throws(() => GuardAppContributors.CanRemove(contributors_0, command)); + Assert.Throws(() => GuardAppContributors.CanRemove(command, App(contributors_0))); } [Fact] @@ -191,7 +191,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var contributors_1 = contributors_0.Assign("1", Role.Owner); var contributors_2 = contributors_1.Assign("2", Role.Editor); - ValidationAssert.Throws(() => GuardAppContributors.CanRemove(contributors_2, command), + ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App(contributors_2)), new ValidationError("Cannot remove the only owner.")); } @@ -203,7 +203,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var contributors_1 = contributors_0.Assign("1", Role.Owner); var contributors_2 = contributors_1.Assign("2", Role.Owner); - GuardAppContributors.CanRemove(contributors_2, command); + GuardAppContributors.CanRemove(command, App(contributors_2)); + } + + private IAppEntity App(AppContributors contributors) + { + var app = A.Fake(); + + A.CallTo(() => app.Contributors) + .Returns(contributors); + A.CallTo(() => app.Roles) + .Returns(roles); + + return app; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs index 701b2d43b..9961e6cab 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Apps.Commands; @@ -24,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddLanguage(); - ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App(languages)), new ValidationError("Language code is required.", "Language")); } @@ -33,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddLanguage { Language = Language.EN }; - ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App(languages)), new ValidationError("Language has already been added.")); } @@ -42,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddLanguage { Language = Language.IT }; - GuardAppLanguages.CanAdd(languages, command); + GuardAppLanguages.CanAdd(command, App(languages)); } [Fact] @@ -50,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RemoveLanguage(); - ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages)), new ValidationError("Language code is required.", "Language")); } @@ -59,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RemoveLanguage { Language = Language.IT }; - Assert.Throws(() => GuardAppLanguages.CanRemove(languages, command)); + Assert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages))); } [Fact] @@ -67,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RemoveLanguage { Language = Language.EN }; - ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages)), new ValidationError("Master language cannot be removed.")); } @@ -76,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new RemoveLanguage { Language = Language.DE }; - GuardAppLanguages.CanRemove(languages, command); + GuardAppLanguages.CanRemove(command, App(languages)); } [Fact] @@ -84,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage(); - ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), new ValidationError("Language code is required.", "Language")); } @@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage { Language = Language.EN, IsOptional = true }; - ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), new ValidationError("Master language cannot be made optional.", "IsMaster")); } @@ -102,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage { Language = Language.EN, Fallback = new[] { Language.DE } }; - ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), new ValidationError("Master language cannot have fallback languages.", "Fallback")); } @@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.IT } }; - ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages, command), + ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), new ValidationError("App does not have fallback language 'Italian'.", "Fallback")); } @@ -120,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage { Language = Language.IT }; - Assert.Throws(() => GuardAppLanguages.CanUpdate(languages, command)); + Assert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages))); } [Fact] @@ -128,7 +129,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.EN } }; - GuardAppLanguages.CanUpdate(languages, command); + GuardAppLanguages.CanUpdate(command, App(languages)); + } + + private static IAppEntity App(LanguagesConfig languages) + { + var app = A.Fake(); + + A.CallTo(() => app.Languages) + .Returns(languages); + + return app; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs index b5ce9d144..1f6501111 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Apps.Commands; @@ -27,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddPattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)), new ValidationError("Name is required.", "Name")); } @@ -36,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)), new ValidationError("Pattern is required.", "Pattern")); } @@ -45,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_0)), new ValidationError("Pattern is not a valid value.", "Pattern")); } @@ -56,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_1)), new ValidationError("A pattern with the same name already exists.")); } @@ -67,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new AddPattern { PatternId = patternId, Name = "other", Pattern = "[a-z]" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(command, App(patterns_1)), new ValidationError("This pattern already exists but with another name.")); } @@ -76,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; - GuardAppPatterns.CanAdd(patterns_0, command); + GuardAppPatterns.CanAdd(command, App(patterns_0)); } [Fact] @@ -84,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new DeletePattern { PatternId = patternId }; - Assert.Throws(() => GuardAppPatterns.CanDelete(patterns_0, command)); + Assert.Throws(() => GuardAppPatterns.CanDelete(command, App(patterns_0))); } [Fact] @@ -94,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new DeletePattern { PatternId = patternId }; - GuardAppPatterns.CanDelete(patterns_1, command); + GuardAppPatterns.CanDelete(command, App(patterns_1)); } [Fact] @@ -104,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdatePattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)), new ValidationError("Name is required.", "Name")); } @@ -115,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)), new ValidationError("Pattern is required.", "Pattern")); } @@ -126,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_1)), new ValidationError("Pattern is not a valid value.", "Pattern")); } @@ -141,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdatePattern { PatternId = id2, Name = "Pattern1", Pattern = "[0-4]" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_2)), new ValidationError("A pattern with the same name already exists.")); } @@ -156,7 +157,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdatePattern { PatternId = id2, Name = "Pattern2", Pattern = "[0-5]" }; - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command), + ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_2)), new ValidationError("This pattern already exists but with another name.")); } @@ -165,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; - Assert.Throws(() => GuardAppPatterns.CanUpdate(patterns_0, command)); + Assert.Throws(() => GuardAppPatterns.CanUpdate(command, App(patterns_0))); } [Fact] @@ -175,7 +176,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; - GuardAppPatterns.CanUpdate(patterns_1, command); + GuardAppPatterns.CanUpdate(command, App(patterns_1)); + } + + private static IAppEntity App(AppPatterns patterns) + { + var app = A.Fake(); + + A.CallTo(() => app.Patterns) + .Returns(patterns); + + return app; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs index 8f3057b7c..048d369e9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Apps.Commands; @@ -21,15 +22,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { private readonly string roleName = "Role1"; private readonly Roles roles_0 = Roles.Empty; - private readonly AppContributors contributors = AppContributors.Empty; - private readonly AppClients clients = AppClients.Empty; + private readonly AppClients clients = AppClients.Empty.Add("client", "secret", "clientRole"); + private readonly AppContributors contributors = AppContributors.Empty.Assign("contributor", "contributorRole"); [Fact] public void CanAdd_should_throw_exception_if_name_empty() { var command = new AddRole { Name = null! }; - ValidationAssert.Throws(() => GuardAppRoles.CanAdd(roles_0, command), + ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App(roles_0)), new ValidationError("Name is required.", "Name")); } @@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new AddRole { Name = roleName }; - ValidationAssert.Throws(() => GuardAppRoles.CanAdd(roles_1, command), + ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App(roles_1)), new ValidationError("A role with the same name already exists.")); } @@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new AddRole { Name = roleName }; - GuardAppRoles.CanAdd(roles_0, command); + GuardAppRoles.CanAdd(command, App(roles_0)); } [Fact] @@ -57,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new DeleteRole { Name = null! }; - ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_0, command, contributors, clients), + ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_0)), new ValidationError("Name is required.", "Name")); } @@ -66,30 +67,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new DeleteRole { Name = roleName }; - Assert.Throws(() => GuardAppRoles.CanDelete(roles_0, command, contributors, clients)); + Assert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_0))); } [Fact] public void CanDelete_should_throw_exception_if_contributor_found() { - var roles_1 = roles_0.Add(roleName); + var roles_1 = roles_0.Add("contributorRole"); - var command = new DeleteRole { Name = roleName }; + var command = new DeleteRole { Name = "contributorRole" }; - ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_1, command, contributors.Assign("1", roleName), clients), + ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)), new ValidationError("Cannot remove a role when a contributor is assigned.")); } [Fact] public void CanDelete_should_throw_exception_if_client_found() { - var roles_1 = roles_0.Add(roleName); + var roles_1 = roles_0.Add("clientRole"); - var clients_1 = clients.Add("1", "my-secret", roleName); - - var command = new DeleteRole { Name = roleName }; + var command = new DeleteRole { Name = "clientRole" }; - ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_1, command, contributors, clients_1), + ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)), new ValidationError("Cannot remove a role when a client is assigned.")); } @@ -100,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new DeleteRole { Name = Role.Developer }; - ValidationAssert.Throws(() => GuardAppRoles.CanDelete(roles_1, command, contributors, clients), + ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)), new ValidationError("Cannot delete a default role.")); } @@ -111,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new DeleteRole { Name = roleName }; - GuardAppRoles.CanDelete(roles_1, command, contributors, clients); + GuardAppRoles.CanDelete(command, App(roles_1)); } [Fact] @@ -121,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdateRole { Name = null!, Permissions = new[] { "P1" } }; - ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(roles_1, command), + ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)), new ValidationError("Name is required.", "Name")); } @@ -132,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdateRole { Name = roleName }; - ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(roles_1, command), + ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)), new ValidationError("Permissions is required.", "Permissions")); } @@ -143,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdateRole { Name = Role.Developer, Permissions = new[] { "P1" } }; - ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(roles_1, command), + ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)), new ValidationError("Cannot update a default role.")); } @@ -152,7 +151,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } }; - Assert.Throws(() => GuardAppRoles.CanUpdate(roles_0, command)); + Assert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_0))); } [Fact] @@ -162,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } }; - GuardAppRoles.CanUpdate(roles_1, command); + GuardAppRoles.CanUpdate(command, App(roles_1)); } [Fact] @@ -172,7 +171,21 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } }; - GuardAppRoles.CanUpdate(roles_1, command); + GuardAppRoles.CanUpdate(command, App(roles_1)); + } + + private IAppEntity App(Roles roles) + { + var app = A.Fake(); + + A.CallTo(() => app.Contributors) + .Returns(contributors); + A.CallTo(() => app.Clients) + .Returns(clients); + A.CallTo(() => app.Roles) + .Returns(roles); + + return app; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs index 1ca08bfe7..71bb145d9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs @@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards AppPlan? plan = null; - ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, plan, appPlans), + ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), appPlans), new ValidationError("Plan ID is required.", "PlanId")); } @@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards AppPlan? plan = null; - ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, plan, appPlans), + ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), appPlans), new ValidationError("A plan with this id does not exist.", "PlanId")); } @@ -103,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var plan = new AppPlan(new RefToken("user", "other"), "premium"); - ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, plan, appPlans), + ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), appPlans), new ValidationError("Plan can only changed from the user who configured the plan initially.")); } @@ -114,7 +114,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var plan = new AppPlan(command.Actor, "basic"); - GuardApp.CanChangePlan(command, plan, appPlans); + GuardApp.CanChangePlan(command, App(plan), appPlans); } [Fact] @@ -124,7 +124,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var plan = new AppPlan(command.Actor, "premium"); - GuardApp.CanChangePlan(command, plan, appPlans); + GuardApp.CanChangePlan(command, App(plan), appPlans); + } + + private static IAppEntity App(AppPlan? plan) + { + var app = A.Fake(); + + A.CallTo(() => app.Plan) + .Returns(plan); + + return app; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs index c2dd4ddd1..db6a23043 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Collections.Generic; +using FakeItEasy; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Apps.Commands; @@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards WorkflowId = DomainId.NewGuid() }; - Assert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command)); + Assert.Throws(() => GuardAppWorkflows.CanUpdate(command, App())); } [Fact] @@ -60,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateWorkflow { WorkflowId = workflowId }; - ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command), + ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), new ValidationError("Workflow is required.", "Workflow")); } @@ -78,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards WorkflowId = workflowId }; - ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command), + ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), new ValidationError("Initial step is required.", "Workflow.Initial")); } @@ -96,7 +97,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards WorkflowId = workflowId }; - ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command), + ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), new ValidationError("Initial step cannot be published step.", "Workflow.Initial")); } @@ -114,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards WorkflowId = workflowId }; - ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command), + ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), new ValidationError("Workflow must have a published step.", "Workflow.Steps")); } @@ -133,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards WorkflowId = workflowId }; - ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command), + ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), new ValidationError("Step is required.", "Workflow.Steps.Published")); } @@ -157,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards WorkflowId = workflowId }; - ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command), + ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), new ValidationError("Transition has an invalid target.", "Workflow.Steps.Published.Transitions.Archived")); } @@ -182,7 +183,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards WorkflowId = workflowId }; - ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command), + ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), new ValidationError("Transition is required.", "Workflow.Steps.Published.Transitions.Draft")); } @@ -191,7 +192,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateWorkflow { Workflow = Workflow.Default, WorkflowId = workflowId }; - GuardAppWorkflows.CanUpdate(workflows, command); + GuardAppWorkflows.CanUpdate(command, App()); } [Fact] @@ -199,7 +200,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new DeleteWorkflow { WorkflowId = DomainId.NewGuid() }; - Assert.Throws(() => GuardAppWorkflows.CanDelete(workflows, command)); + Assert.Throws(() => GuardAppWorkflows.CanDelete(command, App())); } [Fact] @@ -207,7 +208,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new DeleteWorkflow { WorkflowId = workflowId }; - GuardAppWorkflows.CanDelete(workflows, command); + GuardAppWorkflows.CanDelete(command, App()); + } + + private IAppEntity App() + { + var app = A.Fake(); + + A.CallTo(() => app.Workflows) + .Returns(workflows); + + return app; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs index 50eb67615..d671c4714 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.State; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Assets; @@ -254,7 +255,7 @@ namespace Squidex.Domain.Apps.Entities.Assets await ExecuteCreateAsync(); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id)) + A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id, SearchScope.All)) .Returns(true); await Assert.ThrowsAsync(() => PublishAsync(command)); @@ -267,7 +268,7 @@ namespace Squidex.Domain.Apps.Entities.Assets await ExecuteCreateAsync(); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id)) + A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id, SearchScope.All)) .Returns(true); await PublishAsync(command); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetFolderTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetFolderTests.cs index 719e8b832..316b1da90 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetFolderTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetFolderTests.cs @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards var command = new CreateAssetFolder { AppId = appId, FolderName = "My Folder", ParentId = DomainId.NewGuid() }; A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId)) - .Returns(new List { CreateFolder() }); + .Returns(new List { AssetFolder() }); await GuardAssetFolder.CanCreate(command, assetQuery); } @@ -75,11 +75,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId)) .Returns(new List { - CreateFolder(id), - CreateFolder(command.ParentId) + AssetFolder(id), + AssetFolder(command.ParentId) }); - await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, assetQuery, id, DomainId.NewGuid()), + await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, AssetFolder(id), assetQuery), new ValidationError("Cannot add folder to its own child.", "ParentId")); } @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId)) .Returns(new List()); - await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), DomainId.NewGuid()), + await ValidationAssert.ThrowsAsync(() => GuardAssetFolder.CanMove(command, AssetFolder(), assetQuery), new ValidationError("Asset folder does not exist.", "ParentId")); } @@ -101,9 +101,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards var command = new MoveAssetFolder { AppId = appId, ParentId = DomainId.NewGuid() }; A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId)) - .Returns(new List { CreateFolder() }); + .Returns(new List { AssetFolder() }); - await GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), DomainId.NewGuid()); + await GuardAssetFolder.CanMove(command, AssetFolder(), assetQuery); } [Fact] @@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { var command = new MoveAssetFolder { AppId = appId, ParentId = DomainId.NewGuid() }; - await GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), command.ParentId); + await GuardAssetFolder.CanMove(command, AssetFolder(parentId: command.ParentId), assetQuery); } [Fact] @@ -119,7 +119,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { var command = new MoveAssetFolder { AppId = appId }; - await GuardAssetFolder.CanMove(command, assetQuery, DomainId.NewGuid(), DomainId.NewGuid()); + await GuardAssetFolder.CanMove(command, AssetFolder(), assetQuery); } [Fact] @@ -147,11 +147,16 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards GuardAssetFolder.CanDelete(command); } - private static IAssetFolderEntity CreateFolder(DomainId id = default) + private IAssetFolderEntity AssetFolder(DomainId id = default, DomainId parentId = default) { var assetFolder = A.Fake(); - A.CallTo(() => assetFolder.Id).Returns(id); + A.CallTo(() => assetFolder.Id) + .Returns(id == default ? DomainId.NewGuid() : id); + A.CallTo(() => assetFolder.AppId) + .Returns(appId); + A.CallTo(() => assetFolder.ParentId) + .Returns(parentId == default ? DomainId.NewGuid() : parentId); return assetFolder; } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs index abf1bccac..10708c108 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs @@ -10,6 +10,8 @@ using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Validation; @@ -20,6 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards public class GuardAssetTests : IClassFixture { private readonly IAssetQueryService assetQuery = A.Fake(); + private readonly IContentRepository contentRepository = A.Fake(); private readonly NamedId appId = NamedId.Of(DomainId.NewGuid(), "my-app"); [Fact] @@ -28,7 +31,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards var command = new CreateAsset { AppId = appId, ParentId = DomainId.NewGuid() }; A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId)) - .Returns(new List { CreateFolder() }); + .Returns(new List { AssetFolder() }); await GuardAsset.CanCreate(command, assetQuery); } @@ -61,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId)) .Returns(new List()); - await ValidationAssert.ThrowsAsync(() => GuardAsset.CanMove(command, assetQuery, DomainId.NewGuid()), + await ValidationAssert.ThrowsAsync(() => GuardAsset.CanMove(command, Asset(), assetQuery), new ValidationError("Asset folder does not exist.", "ParentId")); } @@ -70,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { var command = new MoveAsset { AppId = appId, ParentId = DomainId.NewGuid() }; - await GuardAsset.CanMove(command, assetQuery, command.ParentId); + await GuardAsset.CanMove(command, Asset(parentId: command.ParentId), assetQuery); } [Fact] @@ -79,9 +82,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards var command = new MoveAsset { AppId = appId, ParentId = DomainId.NewGuid() }; A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, command.ParentId)) - .Returns(new List { CreateFolder() }); + .Returns(new List { AssetFolder() }); - await GuardAsset.CanMove(command, assetQuery, DomainId.NewGuid()); + await GuardAsset.CanMove(command, Asset(), assetQuery); } [Fact] @@ -89,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { var command = new MoveAsset { AppId = appId }; - await GuardAsset.CanMove(command, assetQuery, DomainId.NewGuid()); + await GuardAsset.CanMove(command, Asset(), assetQuery); } [Fact] @@ -117,18 +120,50 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards } [Fact] - public void CanDelete_should_not_throw_exception() + public async Task CanDelete_should_throw_exception_if_referenced() + { + var asset = Asset(); + + var command = new DeleteAsset { AppId = appId, CheckReferrers = true }; + + A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, asset.Id, SearchScope.All)) + .Returns(true); + + await Assert.ThrowsAsync(() => GuardAsset.CanDelete(command, asset, contentRepository)); + } + + [Fact] + public async Task CanDelete_should_not_throw_exception() { var command = new DeleteAsset { AppId = appId }; - GuardAsset.CanDelete(command); + await GuardAsset.CanDelete(command, Asset(), contentRepository); + } + + private IAssetEntity Asset(DomainId id = default, DomainId parentId = default) + { + var asset = A.Fake(); + + A.CallTo(() => asset.Id) + .Returns(id == default ? DomainId.NewGuid() : id); + A.CallTo(() => asset.AppId) + .Returns(appId); + A.CallTo(() => asset.ParentId) + .Returns(parentId == default ? DomainId.NewGuid() : parentId); + + return asset; } - private static IAssetFolderEntity CreateFolder(DomainId id = default) + private IAssetFolderEntity AssetFolder(DomainId id = default, DomainId parentId = default) { var assetFolder = A.Fake(); - A.CallTo(() => assetFolder.Id).Returns(id); + A.CallTo(() => assetFolder.Id) + .Returns(id == default ? DomainId.NewGuid() : id); + A.CallTo(() => assetFolder.AppId) + .Returns(appId); + A.CallTo(() => assetFolder.ParentId) + .Returns(parentId == default ? DomainId.NewGuid() : parentId); return assetFolder; } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/Guards/GuardCommentsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/Guards/GuardCommentsTests.cs index 0c378f0aa..249362b5e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/Guards/GuardCommentsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/Guards/GuardCommentsTests.cs @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To() }; - ValidationAssert.Throws(() => GuardComments.CanUpdate(commentsId, events, command), + ValidationAssert.Throws(() => GuardComments.CanUpdate(command, commentsId, events), new ValidationError("Text is required.", "Text")); } @@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To() }; - Assert.Throws(() => GuardComments.CanUpdate(commentsId, events, command)); + Assert.Throws(() => GuardComments.CanUpdate(command, commentsId, events)); } [Fact] @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards var events = new List>(); - Assert.Throws(() => GuardComments.CanUpdate(commentsId, events, command)); + Assert.Throws(() => GuardComments.CanUpdate(command, commentsId, events)); } [Fact] @@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentDeleted { CommentId = commentId }).To() }; - Assert.Throws(() => GuardComments.CanUpdate(commentsId, events, command)); + Assert.Throws(() => GuardComments.CanUpdate(command, commentsId, events)); } [Fact] @@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To() }; - GuardComments.CanUpdate(user1.Identifier, events, command); + GuardComments.CanUpdate(command, user1.Identifier, events); } [Fact] @@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To() }; - GuardComments.CanUpdate(commentsId, events, command); + GuardComments.CanUpdate(command, commentsId, events); } [Fact] @@ -134,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To() }; - Assert.Throws(() => GuardComments.CanDelete(commentsId, events, command)); + Assert.Throws(() => GuardComments.CanDelete(command, commentsId, events)); } [Fact] @@ -145,7 +145,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards var events = new List>(); - Assert.Throws(() => GuardComments.CanDelete(commentsId, events, command)); + Assert.Throws(() => GuardComments.CanDelete(command, commentsId, events)); } [Fact] @@ -160,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentDeleted { CommentId = commentId }) }; - Assert.Throws(() => GuardComments.CanDelete(commentsId, events, command)); + Assert.Throws(() => GuardComments.CanDelete(command, commentsId, events)); } [Fact] @@ -174,7 +174,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To() }; - GuardComments.CanDelete(user1.Identifier, events, command); + GuardComments.CanDelete(command, user1.Identifier, events); } [Fact] @@ -188,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards Envelope.Create(new CommentCreated { CommentId = commentId, Actor = user1 }).To() }; - GuardComments.CanDelete(commentsId, events, command); + GuardComments.CanDelete(command, commentsId, events); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs index 17604ce36..01084e49c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs @@ -565,12 +565,40 @@ namespace Squidex.Domain.Apps.Entities.Contents } [Fact] - public async Task Delete_should_create_events_and_update_deleted_flag() + public async Task ChangeStatus_should_throw_exception_if_referenced_by_other_item() { - var command = new DeleteContent(); + var command = new ChangeContentStatus { Status = Status.Draft, CheckReferrers = true }; + + await ExecuteCreateAsync(); + await ExecuteChangeStatusAsync(Status.Published); + + A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.Published)) + .Returns(true); + + await Assert.ThrowsAsync(() => PublishAsync(command)); + } + + [Fact] + public async Task ChangeStatus_should_not_throw_exception_if_referenced_by_other_item_but_forced() + { + var command = new ChangeContentStatus { Status = Status.Draft, CheckReferrers = false }; + + await ExecuteCreateAsync(); + await ExecuteChangeStatusAsync(Status.Published); + A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.Published)) + .Returns(true); + + await PublishAsync(command); + } + + [Fact] + public async Task Delete_should_create_events_and_update_deleted_flag() + { await ExecuteCreateAsync(); + var command = new DeleteContent(); + var result = await PublishAsync(command); result.ShouldBeEquivalent(new EntitySavedResult(1)); @@ -589,11 +617,11 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Delete_should_throw_exception_if_referenced_by_other_item() { - var command = new DeleteContent { CheckReferrers = true }; - await ExecuteCreateAsync(); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId)) + var command = new DeleteContent { CheckReferrers = true }; + + A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.All)) .Returns(true); await Assert.ThrowsAsync(() => PublishAsync(command)); @@ -606,7 +634,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await ExecuteCreateAsync(); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId)) + A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.All)) .Returns(true); await PublishAsync(command); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs index b2c9c0fe5..ec757a1aa 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs @@ -15,7 +15,6 @@ using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Guards; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -39,7 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var command = new CreateContent(); - await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(schema, contentWorkflow, command), + await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(command, contentWorkflow, schema), new ValidationError("Data is required.", "Data")); } @@ -50,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var command = new CreateContent { Data = new NamedContentData() }; - await Assert.ThrowsAsync(() => GuardContent.CanCreate(schema, contentWorkflow, command)); + await Assert.ThrowsAsync(() => GuardContent.CanCreate(command, contentWorkflow, schema)); } [Fact] @@ -60,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id }; - await GuardContent.CanCreate(schema, contentWorkflow, command); + await GuardContent.CanCreate(command, contentWorkflow, schema); } [Fact] @@ -72,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var command = new CreateContent { Data = new NamedContentData(), Publish = true }; - await Assert.ThrowsAsync(() => GuardContent.CanCreate(schema, contentWorkflow, command)); + await Assert.ThrowsAsync(() => GuardContent.CanCreate(command, contentWorkflow, schema)); } [Fact] @@ -84,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var command = new CreateContent { Data = new NamedContentData(), Publish = true }; - await Assert.ThrowsAsync(() => GuardContent.CanCreate(schema, contentWorkflow, command)); + await Assert.ThrowsAsync(() => GuardContent.CanCreate(command, contentWorkflow, schema)); } [Fact] @@ -94,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var command = new CreateContent { Data = new NamedContentData() }; - await GuardContent.CanCreate(schema, contentWorkflow, command); + await GuardContent.CanCreate(command, contentWorkflow, schema); } [Fact] @@ -105,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Draft); var command = new UpdateContent(); - await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(content, contentWorkflow, command), + await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(command, content, contentWorkflow), new ValidationError("Data is required.", "Data")); } @@ -117,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Draft); var command = new UpdateContent { Data = new NamedContentData() }; - await Assert.ThrowsAsync(() => GuardContent.CanUpdate(content, contentWorkflow, command)); + await Assert.ThrowsAsync(() => GuardContent.CanUpdate(command, content, contentWorkflow)); } [Fact] @@ -128,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Draft); var command = new UpdateContent { Data = new NamedContentData(), User = user }; - await GuardContent.CanUpdate(content, contentWorkflow, command); + await GuardContent.CanUpdate(command, content, contentWorkflow); } [Fact] @@ -139,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Draft); var command = new PatchContent(); - await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(content, contentWorkflow, command), + await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(command, content, contentWorkflow), new ValidationError("Data is required.", "Data")); } @@ -151,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Draft); var command = new PatchContent { Data = new NamedContentData() }; - await Assert.ThrowsAsync(() => GuardContent.CanPatch(content, contentWorkflow, command)); + await Assert.ThrowsAsync(() => GuardContent.CanPatch(command, content, contentWorkflow)); } [Fact] @@ -162,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Draft); var command = new PatchContent { Data = new NamedContentData(), User = user }; - await GuardContent.CanPatch(content, contentWorkflow, command); + await GuardContent.CanPatch(command, content, contentWorkflow); } [Fact] @@ -173,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Published); var command = new ChangeContentStatus { Status = Status.Draft }; - await Assert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command)); + await Assert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema)); } [Fact] @@ -187,7 +186,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) .Returns(true); - await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command), + await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema), new ValidationError("Due time must be in the future.", "DueTime")); } @@ -202,10 +201,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) .Returns(false); - await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command), + await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema), new ValidationError("Cannot change status from Draft to Published.", "Status")); } + [Fact] + public async Task CanChangeStatus_should_throw_exception_if_referenced() + { + var schema = CreateSchema(true); + + var content = CreateContent(Status.Published); + var command = new ChangeContentStatus { Status = Status.Draft, User = user }; + + A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.Published)) + .Returns(true); + + await Assert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema)); + } + [Fact] public async Task CanChangeStatus_should_not_throw_exception_if_singleton_is_published() { @@ -214,7 +227,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateDraftContent(Status.Draft); var command = new ChangeContentStatus { Status = Status.Published }; - await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command); + await GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema); } [Fact] @@ -228,7 +241,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) .Returns(true); - await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command); + await GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema); } [Fact] @@ -279,7 +292,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Published); var command = new DeleteContent(); - await Assert.ThrowsAsync(() => GuardContent.CanDelete(schema, content, contentRepository, command)); + await Assert.ThrowsAsync(() => GuardContent.CanDelete(command, content, contentRepository, schema)); } [Fact] @@ -290,10 +303,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Published); var command = new DeleteContent(); - A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id)) + A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.All)) .Returns(true); - await Assert.ThrowsAsync(() => GuardContent.CanDelete(schema, content, contentRepository, command)); + await Assert.ThrowsAsync(() => GuardContent.CanDelete(command, content, contentRepository, schema)); } [Fact] @@ -304,7 +317,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var content = CreateContent(Status.Published); var command = new DeleteContent(); - await GuardContent.CanDelete(schema, content, contentRepository, command); + await GuardContent.CanDelete(command, content, contentRepository, schema); } private void SetupCanUpdate(bool canUpdate) @@ -324,22 +337,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard return Mocks.Schema(appId, NamedId.Of(DomainId.NewGuid(), "my-schema"), new Schema("schema", isSingleton: isSingleton)); } - private ContentState CreateDraftContent(Status status) + private IContentEntity CreateDraftContent(Status status) { - return new ContentState + return new ContentEntity { Id = DomainId.NewGuid(), - NewVersion = new ContentVersion(status, new NamedContentData()), + NewStatus = status, AppId = appId }; } - private ContentState CreateContent(Status status) + private IContentEntity CreateContent(Status status) { - return new ContentState + return new ContentEntity { Id = DomainId.NewGuid(), - CurrentVersion = new ContentVersion(status, new NamedContentData()), + Status = status, AppId = appId }; } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs index 59f3595cd..926a4158d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs @@ -41,14 +41,15 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards [Fact] public async Task CanCreate_should_throw_exception_if_trigger_null() { - var command = CreateCommand(new CreateRule + var command = new CreateRule { Trigger = null!, Action = new TestAction { Url = validUrl - } - }); + }, + AppId = appId + }; await ValidationAssert.ThrowsAsync(() => GuardRule.CanCreate(command, appProvider), new ValidationError("Trigger is required.", "Trigger")); @@ -57,14 +58,15 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards [Fact] public async Task CanCreate_should_throw_exception_if_action_null() { - var command = CreateCommand(new CreateRule + var command = new CreateRule { Trigger = new ContentChangedTriggerV2 { Schemas = ReadOnlyCollection.Empty() }, - Action = null! - }); + Action = null!, + AppId = appId + }; await ValidationAssert.ThrowsAsync(() => GuardRule.CanCreate(command, appProvider), new ValidationError("Action is required.", "Action")); @@ -73,7 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards [Fact] public async Task CanCreate_should_not_throw_exception_if_trigger_and_action_valid() { - var command = CreateCommand(new CreateRule + var command = new CreateRule { Trigger = new ContentChangedTriggerV2 { @@ -82,8 +84,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards Action = new TestAction { Url = validUrl - } - }); + }, + AppId = appId + }; await GuardRule.CanCreate(command, appProvider); } @@ -93,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { var command = new UpdateRule(); - await GuardRule.CanUpdate(command, appId.Id, appProvider); + await GuardRule.CanUpdate(command, Rule(), appProvider); } [Fact] @@ -101,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { var command = new UpdateRule { Name = "MyName" }; - await GuardRule.CanUpdate(command, appId.Id, appProvider); + await GuardRule.CanUpdate(command, Rule(), appProvider); } [Fact] @@ -120,7 +123,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards Name = "NewName" }; - await GuardRule.CanUpdate(command, appId.Id, appProvider); + await GuardRule.CanUpdate(command, Rule(), appProvider); } [Fact] @@ -147,11 +150,13 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards GuardRule.CanDelete(command); } - private CreateRule CreateCommand(CreateRule command) + private IRuleEntity Rule() { - command.AppId = appId; + var rule = A.Fake(); + + A.CallTo(() => rule.AppId).Returns(appId); - return command; + return rule; } } } diff --git a/frontend/app/app.module.ts b/frontend/app/app.module.ts index 56029d2f0..fa0dedd2d 100644 --- a/frontend/app/app.module.ts +++ b/frontend/app/app.module.ts @@ -55,7 +55,7 @@ export function configCurrency() { return new CurrencyConfig('EUR', '€', true); } -export function configTranslations() { +export function configLocalizerService() { if (process.env.NODE_ENV === 'production') { return new LocalizerService(window['texts']); } else { @@ -86,8 +86,8 @@ export function configTranslations() { { provide: ApiUrlConfig, useFactory: configApiUrl }, { provide: CurrencyConfig, useFactory: configCurrency }, { provide: DecimalSeparatorConfig, useFactory: configDecimalSeparator }, + { provide: LocalizerService, useFactory: configLocalizerService }, { provide: TitlesConfig, useFactory: configTitles }, - { provide: LocalizerService, useFactory: configTranslations }, { provide: UIOptions, useFactory: configUIOptions } ], entryComponents: [AppComponent] diff --git a/frontend/app/features/content/pages/content/content-history-page.component.ts b/frontend/app/features/content/pages/content/content-history-page.component.ts index d9a1418de..4ea83adeb 100644 --- a/frontend/app/features/content/pages/content/content-history-page.component.ts +++ b/frontend/app/features/content/pages/content/content-history-page.component.ts @@ -73,7 +73,7 @@ export class ContentHistoryPageComponent extends ResourceOwner implements OnInit this.contentPage.checkPendingChangesBeforeChangingStatus().pipe( filter(x => !!x), switchMap(_ => this.dueTimeSelector.selectDueTime(status)), - switchMap(d => this.contentsState.changeStatus(this.content, status, d)), + switchMap(d => this.contentsState.changeManyStatus([this.content], status, d)), onErrorResumeNext()) .subscribe(); } diff --git a/frontend/app/shared/services/contents.service.spec.ts b/frontend/app/shared/services/contents.service.spec.ts index 199271193..1921a95e1 100644 --- a/frontend/app/shared/services/contents.service.spec.ts +++ b/frontend/app/shared/services/contents.service.spec.ts @@ -366,7 +366,7 @@ describe('ContentsService', () => { let content: ContentDto; - contentsService.putStatus('my-app', resource, 'published', '2016-12-12T10:10:00', version).subscribe(result => { + contentsService.putStatus('my-app', resource, 'published', true, '2016-12-12T10:10:00', version).subscribe(result => { content = result; }); diff --git a/frontend/app/shared/services/contents.service.ts b/frontend/app/shared/services/contents.service.ts index c5db772f2..eafe8891e 100644 --- a/frontend/app/shared/services/contents.service.ts +++ b/frontend/app/shared/services/contents.service.ts @@ -301,12 +301,12 @@ export class ContentsService { pretifyError('i18n:contents.deleteVersionFailed')); } - public putStatus(appName: string, resource: Resource, status: string, dueTime: string | null, version: Version): Observable { + public putStatus(appName: string, resource: Resource, status: string, checkReferrers: boolean, dueTime: string | null, version: Version): Observable { const link = resource._links[`status/${status}`]; const url = this.apiUrl.buildUrl(link.href); - return HTTP.requestVersioned(this.http, link.method, url, version, { status, dueTime }).pipe( + return HTTP.requestVersioned(this.http, link.method, url, version, { status, dueTime, checkReferrers }).pipe( map(({ payload }) => { return parseContent(payload.body); }), diff --git a/frontend/app/shared/state/contents.state.ts b/frontend/app/shared/state/contents.state.ts index d9cacf7ed..e857570ed 100644 --- a/frontend/app/shared/state/contents.state.ts +++ b/frontend/app/shared/state/contents.state.ts @@ -224,7 +224,28 @@ export abstract class ContentsStateBase extends State { } public changeManyStatus(contentsToChange: ReadonlyArray, status: string, dueTime: string | null): Observable { - return this.updateManyStatus(contentsToChange, status, dueTime).pipe( + return this.changeManyStatusCore(contentsToChange, status, true, dueTime).pipe( + switchMap(results => { + const referenced = results.filter(x => x.error?.statusCode === 400).map(x => x.content); + + if (referenced.length > 0) { + return this.dialogs.confirm( + 'i18n:contents.unpublishReferrerConfirmTitle', + 'i18n:contents.unpublishReferrerConfirmText', + 'unpublishReferencngContent' + ).pipe( + switchMap(confirmed => { + if (confirmed) { + return this.changeManyStatusCore(referenced, status, false, dueTime); + } else { + return of([]); + } + }) + ); + } else { + return of(results); + } + }), tap(results => { const errors = results.filter(x => !!x.error); @@ -260,7 +281,7 @@ export abstract class ContentsStateBase extends State { return this.dialogs.confirm( 'i18n:contents.deleteReferrerConfirmTitle', 'i18n:contents.deleteReferrerConfirmText', - 'deleteReferencedAsset' + 'deleteReferencingContent' ).pipe( switchMap(confirmed => { if (confirmed) { @@ -302,14 +323,6 @@ export abstract class ContentsStateBase extends State { shareSubscribed(this.dialogs, { silent: true })); } - public changeStatus(content: ContentDto, status: string, dueTime: string | null): Observable { - return this.contentsService.putStatus(this.appName, content, status, dueTime, content.version).pipe( - tap(updated => { - this.replaceContent(updated, content.version, 'i18n:contents.updated'); - }), - shareSubscribed(this.dialogs)); - } - public update(content: ContentDto, request: any): Observable { return this.contentsService.putContent(this.appName, content, request, content.version).pipe( tap(updated => { @@ -379,9 +392,9 @@ export abstract class ContentsStateBase extends State { contents.map(c => this.deleteCore(c, checkReferrers))); } - private updateManyStatus(contents: ReadonlyArray, status: string, dueTime: string | null): Observable> { + private changeManyStatusCore(contents: ReadonlyArray, status: string, checkReferrers: boolean, dueTime: string | null): Observable> { return forkJoin( - contents.map(c => this.updateStatus(c, status, dueTime))); + contents.map(c => this.changeStatusCore(c, status, checkReferrers, dueTime))); } private deleteCore(content: ContentDto, checkReferrers: boolean): Observable { @@ -389,8 +402,8 @@ export abstract class ContentsStateBase extends State { map(() => ({ content })), catchError(error => of({ content, error }))); } - private updateStatus(content: ContentDto, status: string, dueTime: string | null): Observable { - return this.contentsService.putStatus(this.appName, content, status, dueTime, content.version).pipe( + private changeStatusCore(content: ContentDto, status: string, checkReferrers: boolean, dueTime: string | null): Observable { + return this.contentsService.putStatus(this.appName, content, status, checkReferrers, dueTime, content.version).pipe( map(x => ({ content: x })), catchError(error => of({ content, error }))); }