diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/RestoreContent.cs b/src/Squidex.Domain.Apps.Core/Contents/Status.cs similarity index 71% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/RestoreContent.cs rename to src/Squidex.Domain.Apps.Core/Contents/Status.cs index 3563aa716..c2f84034d 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/RestoreContent.cs +++ b/src/Squidex.Domain.Apps.Core/Contents/Status.cs @@ -1,14 +1,17 @@ // ========================================================================== -// RestoreContent.cs +// Status.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Core.Contents { - public sealed class RestoreContent : ContentCommand + public enum Status { + Draft, + Archived, + Published } } diff --git a/src/Squidex.Domain.Apps.Core/Contents/StatusFlow.cs b/src/Squidex.Domain.Apps.Core/Contents/StatusFlow.cs new file mode 100644 index 000000000..30fbf23e1 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core/Contents/StatusFlow.cs @@ -0,0 +1,33 @@ +// ========================================================================== +// StatusFlow.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; + +namespace Squidex.Domain.Apps.Core.Contents +{ + public static class StatusFlow + { + private static readonly Dictionary Flow = new Dictionary + { + [Status.Draft] = new[] { Status.Published, Status.Archived }, + [Status.Archived] = new[] { Status.Draft }, + [Status.Published] = new[] { Status.Draft, Status.Archived } + }; + + public static bool Exists(Status status) + { + return Flow.ContainsKey(status); + } + + public static bool CanChange(Status status, Status toStatus) + { + return Flow.TryGetValue(status, out var state) && state.Contains(toStatus); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core/Webhooks/WebhookSchema.cs b/src/Squidex.Domain.Apps.Core/Webhooks/WebhookSchema.cs index 222e69379..169e85b7d 100644 --- a/src/Squidex.Domain.Apps.Core/Webhooks/WebhookSchema.cs +++ b/src/Squidex.Domain.Apps.Core/Webhooks/WebhookSchema.cs @@ -21,7 +21,5 @@ namespace Squidex.Domain.Apps.Core.Webhooks public bool SendDelete { get; set; } public bool SendPublish { get; set; } - - public bool SendUnpublish { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Events/Contents/ContentStatusChanged.cs b/src/Squidex.Domain.Apps.Events/Contents/ContentStatusChanged.cs new file mode 100644 index 000000000..b7c2f7e36 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Contents/ContentStatusChanged.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// ContentStatusChanged.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Infrastructure.CQRS.Events; + +namespace Squidex.Domain.Apps.Events.Contents +{ + [EventType(nameof(ContentStatusChanged))] + public sealed class ContentStatusChanged : ContentEvent + { + public Status Status { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Events/Contents/ContentArchived.cs b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentArchived.cs similarity index 94% rename from src/Squidex.Domain.Apps.Events/Contents/ContentArchived.cs rename to src/Squidex.Domain.Apps.Events/Contents/Old/ContentArchived.cs index 4c5029b0d..58fe37ca7 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/ContentArchived.cs +++ b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentArchived.cs @@ -6,11 +6,13 @@ // All rights reserved. // ========================================================================== +using System; using Squidex.Infrastructure.CQRS.Events; namespace Squidex.Domain.Apps.Events.Contents { [EventType(nameof(ContentArchived))] + [Obsolete] public sealed class ContentArchived : ContentEvent { } diff --git a/src/Squidex.Domain.Apps.Events/Contents/ContentPublished.cs b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentPublished.cs similarity index 94% rename from src/Squidex.Domain.Apps.Events/Contents/ContentPublished.cs rename to src/Squidex.Domain.Apps.Events/Contents/Old/ContentPublished.cs index f8b6f8278..9ade43e8c 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/ContentPublished.cs +++ b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentPublished.cs @@ -6,11 +6,13 @@ // All rights reserved. // ========================================================================== +using System; using Squidex.Infrastructure.CQRS.Events; namespace Squidex.Domain.Apps.Events.Contents { [EventType(nameof(ContentPublished))] + [Obsolete] public sealed class ContentPublished : ContentEvent { } diff --git a/src/Squidex.Domain.Apps.Events/Contents/ContentRestored.cs b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentRestored.cs similarity index 94% rename from src/Squidex.Domain.Apps.Events/Contents/ContentRestored.cs rename to src/Squidex.Domain.Apps.Events/Contents/Old/ContentRestored.cs index f2a7659ed..0ddfc09ec 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/ContentRestored.cs +++ b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentRestored.cs @@ -6,11 +6,13 @@ // All rights reserved. // ========================================================================== +using System; using Squidex.Infrastructure.CQRS.Events; namespace Squidex.Domain.Apps.Events.Contents { [EventType(nameof(ContentRestored))] + [Obsolete] public sealed class ContentRestored : ContentEvent { } diff --git a/src/Squidex.Domain.Apps.Events/Contents/ContentUnpublished.cs b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentUnpublished.cs similarity index 95% rename from src/Squidex.Domain.Apps.Events/Contents/ContentUnpublished.cs rename to src/Squidex.Domain.Apps.Events/Contents/Old/ContentUnpublished.cs index 2b8d0306d..32d6986b9 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/ContentUnpublished.cs +++ b/src/Squidex.Domain.Apps.Events/Contents/Old/ContentUnpublished.cs @@ -6,11 +6,13 @@ // All rights reserved. // ========================================================================== +using System; using Squidex.Infrastructure.CQRS.Events; namespace Squidex.Domain.Apps.Events.Contents { [EventType(nameof(ContentUnpublished))] + [Obsolete] public sealed class ContentUnpublished : ContentEvent { } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs index d14127ab8..1c4f7f0ed 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs @@ -35,6 +35,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents [BsonRepresentation(BsonType.String)] public Guid Id { get; set; } + [BsonRequired] + [BsonElement("st")] + [BsonRepresentation(BsonType.String)] + public Status Status { get; set; } + [BsonRequired] [BsonElement("ct")] public Instant Created { get; set; } @@ -43,14 +48,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents [BsonElement("mt")] public Instant LastModified { get; set; } - [BsonRequired] - [BsonElement("pu")] - public bool IsPublished { get; set; } - - [BsonRequired] - [BsonElement("dl")] - public bool IsArchived { get; set; } - [BsonRequired] [BsonElement("dt")] public string DataText { get; set; } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs index ca032d5c5..e2b14f627 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.OData.UriParser; using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Contents; using Squidex.Domain.Apps.Read.Contents.Repositories; @@ -71,7 +72,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents this.database = database; } - public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, bool nonPublished, bool archived, HashSet ids, ODataUriParser odataQuery) + public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids, ODataUriParser odataQuery) { var collection = GetCollection(app.Id); @@ -80,7 +81,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { cursor = collection - .Find(odataQuery, ids, schema.Id, schema.SchemaDef, nonPublished, archived) + .Find(odataQuery, ids, schema.Id, schema.SchemaDef, status) .Take(odataQuery) .Skip(odataQuery) .Sort(odataQuery, schema.SchemaDef); @@ -104,14 +105,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents return entities; } - public Task CountAsync(IAppEntity app, ISchemaEntity schema, bool nonPublished, bool archived, HashSet ids, ODataUriParser odataQuery) + public Task CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids, ODataUriParser odataQuery) { var collection = GetCollection(app.Id); IFindFluent cursor; try { - cursor = collection.Find(odataQuery, ids, schema.Id, schema.SchemaDef, nonPublished, archived); + cursor = collection.Find(odataQuery, ids, schema.Id, schema.SchemaDef, status); } catch (NotSupportedException) { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs index ef5cdb889..8ceefa833 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs @@ -9,6 +9,7 @@ using System; using System.Threading.Tasks; using MongoDB.Driver; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Contents; @@ -17,6 +18,8 @@ using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Reflection; +#pragma warning disable CS0612 // Type or member is obsolete + namespace Squidex.Domain.Apps.Read.MongoDb.Contents { public partial class MongoContentRepository @@ -61,8 +64,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SchemaId).Descending(x => x.LastModified)); await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds)); - await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsPublished)); - await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsArchived)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Status)); await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText)); }); } @@ -99,7 +101,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { return collection.UpdateAsync(@event, headers, x => { - x.IsPublished = true; + x.Status = Status.Published; }); }); } @@ -110,7 +112,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { return collection.UpdateAsync(@event, headers, x => { - x.IsPublished = false; + x.Status = Status.Draft; }); }); } @@ -121,7 +123,18 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { return collection.UpdateAsync(@event, headers, x => { - x.IsArchived = true; + x.Status = Status.Archived; + }); + }); + } + + protected Task On(ContentStatusChanged @event, EnvelopeHeaders headers) + { + return ForAppIdAsync(@event.AppId.Id, collection => + { + return collection.UpdateAsync(@event, headers, x => + { + x.Status = @event.Status; }); }); } @@ -132,7 +145,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { return collection.UpdateAsync(@event, headers, x => { - x.IsArchived = false; + x.Status = Status.Draft; }); }); } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs index ebf2d9fd0..6152d7624 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using Microsoft.OData.UriParser; using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors @@ -56,26 +57,21 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors return cursor; } - public static IFindFluent Find(this IMongoCollection cursor, ODataUriParser query, HashSet ids, Guid schemaId, Schema schema, bool nonPublished, bool archived) + public static IFindFluent Find(this IMongoCollection cursor, ODataUriParser query, HashSet ids, Guid schemaId, Schema schema, Status[] status) { - var filter = BuildQuery(query, ids, schemaId, schema, nonPublished, archived); + var filter = BuildQuery(query, ids, schemaId, schema, status); return cursor.Find(filter); } - public static FilterDefinition BuildQuery(ODataUriParser query, HashSet ids, Guid schemaId, Schema schema, bool nonPublished, bool archived) + public static FilterDefinition BuildQuery(ODataUriParser query, HashSet ids, Guid schemaId, Schema schema, Status[] status) { var filters = new List> { Filter.Eq(x => x.SchemaId, schemaId), - Filter.Eq(x => x.IsArchived, archived) + Filter.In(x => x.Status, status) }; - if (!nonPublished) - { - filters.Add(Filter.Eq(x => x.IsPublished, true)); - } - if (ids != null && ids.Count > 0) { filters.Add(Filter.In(x => x.Id, ids)); diff --git a/src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs index 4a6c25ba3..45d02810c 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs @@ -12,6 +12,8 @@ using Squidex.Domain.Apps.Read.History; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; +#pragma warning disable CS0612 // Type or member is obsolete + namespace Squidex.Domain.Apps.Read.Contents { public sealed class ContentHistoryEventsCreator : HistoryEventsCreatorBase @@ -36,6 +38,9 @@ namespace Squidex.Domain.Apps.Read.Contents AddEventMessage( "unpublished content item."); + + AddEventMessage( + "change status of content item to {[Status]}."); } protected override Task CreateEventCoreAsync(Envelope @event) diff --git a/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs index 564adcdd9..19d70ae8d 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs @@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Read.Contents var content = await contentRepository.FindContentAsync(app, schema, id); - if (content == null || (!content.IsPublished && !isFrontendClient)) + if (content == null || (content.Status != Status.Published && !isFrontendClient)) { throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity)); } @@ -83,10 +83,27 @@ namespace Squidex.Domain.Apps.Read.Contents var parsedQuery = ParseQuery(app, query, schema); - var isFrontendClient = user.IsInClient("squidex-frontend"); + var status = new List(); - var taskForItems = contentRepository.QueryAsync(app, schema, isFrontendClient, archived, ids, parsedQuery); - var taskForCount = contentRepository.CountAsync(app, schema, isFrontendClient, archived, ids, parsedQuery); + if (user.IsInClient("squidex-frontend")) + { + if (archived) + { + status.Add(Status.Archived); + } + else + { + status.Add(Status.Draft); + status.Add(Status.Published); + } + } + else + { + status.Add(Status.Published); + } + + var taskForItems = contentRepository.QueryAsync(app, schema, status.ToArray(), ids, parsedQuery); + var taskForCount = contentRepository.CountAsync(app, schema, status.ToArray(), ids, parsedQuery); await Task.WhenAll(taskForItems, taskForCount); @@ -155,14 +172,18 @@ namespace Squidex.Domain.Apps.Read.Contents { public Guid Id { get; set; } public Guid AppId { get; set; } + public long Version { get; set; } - public bool IsArchived { get; set; } - public bool IsPublished { get; set; } + public Instant Created { get; set; } public Instant LastModified { get; set; } + public RefToken CreatedBy { get; set; } public RefToken LastModifiedBy { get; set; } + public NamedContentData Data { get; set; } + + public Status Status { get; set; } } } } diff --git a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs b/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs index 9f0e7265c..dc34ea65e 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== +// ========================================================================== using Squidex.Domain.Apps.Core.Contents; @@ -12,9 +13,7 @@ namespace Squidex.Domain.Apps.Read.Contents { public interface IContentEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { - bool IsPublished { get; } - - bool IsArchived { get; } + Status Status { get; } NamedContentData Data { get; } } diff --git a/src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs b/src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs index fea6ff459..9d1aa9f02 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.OData.UriParser; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Schemas; @@ -17,11 +18,11 @@ namespace Squidex.Domain.Apps.Read.Contents.Repositories { public interface IContentRepository { - Task> QueryAsync(IAppEntity app, ISchemaEntity schema, bool nonPublished, bool archived, HashSet ids, ODataUriParser odataQuery); + Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids, ODataUriParser odataQuery); Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList contentIds); - Task CountAsync(IAppEntity app, ISchemaEntity schema, bool nonPublished, bool archived, HashSet ids, ODataUriParser odataQuery); + Task CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids, ODataUriParser odataQuery); Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id); } diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs b/src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs index 879d1e595..c2bc2349d 100644 --- a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs +++ b/src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NodaTime; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Webhooks; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; @@ -115,12 +116,11 @@ namespace Squidex.Domain.Apps.Read.Webhooks private static bool Matchs(WebhookSchema webhookSchema, SchemaEvent @event) { return - (@event.SchemaId.Id == webhookSchema.SchemaId) && - (@event is ContentCreated && webhookSchema.SendCreate || - @event is ContentUpdated && webhookSchema.SendUpdate || - @event is ContentDeleted && webhookSchema.SendDelete || - @event is ContentPublished && webhookSchema.SendPublish || - @event is ContentUnpublished && webhookSchema.SendUnpublish); + (@event.SchemaId.Id == webhookSchema.SchemaId) && + (webhookSchema.SendCreate && @event is ContentCreated || + webhookSchema.SendUpdate && @event is ContentUpdated || + webhookSchema.SendDelete && @event is ContentDeleted || + webhookSchema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published); } private string CreatePayload(Envelope @event, string eventType) diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/ArchiveContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/ChangeContentStatus.cs similarity index 66% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/ArchiveContent.cs rename to src/Squidex.Domain.Apps.Write/Contents/Commands/ChangeContentStatus.cs index 805bd0bc2..292f257ee 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/ArchiveContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/ChangeContentStatus.cs @@ -1,14 +1,17 @@ // ========================================================================== -// ArchiveContent.cs +// ChangeContentStatus.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. -// ========================================================================== +// ========================================================================= + +using Squidex.Domain.Apps.Core.Contents; namespace Squidex.Domain.Apps.Write.Contents.Commands { - public sealed class ArchiveContent : ContentCommand + public sealed class ChangeContentStatus : ContentCommand { + public Status Status { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/PublishContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/PublishContent.cs deleted file mode 100644 index fb8e38abd..000000000 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/PublishContent.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ========================================================================== -// PublishContent.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -namespace Squidex.Domain.Apps.Write.Contents.Commands -{ - public sealed class PublishContent : ContentCommand - { - } -} diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/UnpublishContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/UnpublishContent.cs deleted file mode 100644 index d6690ced9..000000000 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/UnpublishContent.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ========================================================================== -// UnpublishContent.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -namespace Squidex.Domain.Apps.Write.Contents.Commands -{ - public sealed class UnpublishContent : ContentCommand - { - } -} diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs index 5de4c6f6d..7f4652519 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs @@ -111,55 +111,16 @@ namespace Squidex.Domain.Apps.Write.Contents }); } - protected Task On(PublishContent command, CommandContext context) + protected Task On(ChangeContentStatus command, CommandContext context) { return handler.UpdateAsync(context, async content => { var schemaAndApp = await ResolveSchemaAndAppAsync(command); - var scriptContext = CreateScriptContext(content, command, "Publish"); + var scriptContext = CreateScriptContext(content, command, command.Status.ToString()); - scriptEngine.Execute(scriptContext, schemaAndApp.SchemaEntity.ScriptChange, "publish content"); + scriptEngine.Execute(scriptContext, schemaAndApp.SchemaEntity.ScriptChange, "change content status"); - content.Publish(command); - }); - } - - protected Task On(UnpublishContent command, CommandContext context) - { - return handler.UpdateAsync(context, async content => - { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); - var scriptContext = CreateScriptContext(content, command, "Unpublish"); - - scriptEngine.Execute(scriptContext, schemaAndApp.SchemaEntity.ScriptChange, "unpublish content"); - - content.Unpublish(command); - }); - } - - protected Task On(ArchiveContent command, CommandContext context) - { - return handler.UpdateAsync(context, async content => - { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); - var scriptContext = CreateScriptContext(content, command, "Archive"); - - scriptEngine.Execute(scriptContext, schemaAndApp.SchemaEntity.ScriptChange, "archive content"); - - content.Archive(command); - }); - } - - protected Task On(RestoreContent command, CommandContext context) - { - return handler.UpdateAsync(context, async content => - { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); - var scriptContext = CreateScriptContext(content, command, "Restore"); - - scriptEngine.Execute(scriptContext, schemaAndApp.SchemaEntity.ScriptChange, "restore content"); - - content.Restore(command); + content.ChangeStatus(command); }); } diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs index 5573f9294..9286f9b14 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs @@ -22,8 +22,7 @@ namespace Squidex.Domain.Apps.Write.Contents { private bool isDeleted; private bool isCreated; - private bool isPublished; - private bool isArchived; + private Status status; private NamedContentData data; public bool IsDeleted @@ -31,14 +30,9 @@ namespace Squidex.Domain.Apps.Write.Contents get { return isDeleted; } } - public bool IsArchived + public Status Status { - get { return isArchived; } - } - - public bool IsPublished - { - get { return isPublished; } + get { return status; } } public NamedContentData Data @@ -63,24 +57,9 @@ namespace Squidex.Domain.Apps.Write.Contents data = @event.Data; } - protected void On(ContentPublished @event) - { - isPublished = true; - } - - protected void On(ContentUnpublished @event) - { - isPublished = false; - } - - protected void On(ContentArchived @event) - { - isArchived = true; - } - - protected void On(ContentRestored @event) + protected void On(ContentStatusChanged @event) { - isArchived = false; + status = @event.Status; } protected void On(ContentDeleted @event) @@ -98,7 +77,7 @@ namespace Squidex.Domain.Apps.Write.Contents if (command.Publish) { - RaiseEvent(SimpleMapper.Map(command, new ContentPublished())); + RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged { Status = Status.Published })); } return this; @@ -115,46 +94,14 @@ namespace Squidex.Domain.Apps.Write.Contents return this; } - public ContentDomainObject Restore(RestoreContent command) - { - Guard.NotNull(command, nameof(command)); - - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new ContentRestored())); - - return this; - } - - public ContentDomainObject Archive(ArchiveContent command) - { - Guard.NotNull(command, nameof(command)); - - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new ContentArchived())); - - return this; - } - - public ContentDomainObject Publish(PublishContent command) - { - Guard.NotNull(command, nameof(command)); - - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new ContentPublished())); - - return this; - } - - public ContentDomainObject Unpublish(UnpublishContent command) + public ContentDomainObject ChangeStatus(ChangeContentStatus command) { Guard.NotNull(command, nameof(command)); VerifyCreatedAndNotDeleted(); + VerifyCanChangeStatus(command.Status); - RaiseEvent(SimpleMapper.Map(command, new ContentUnpublished())); + RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged())); return this; } @@ -189,6 +136,14 @@ namespace Squidex.Domain.Apps.Write.Contents return this; } + private void VerifyCanChangeStatus(Status newStatus) + { + if (!StatusFlow.Exists(newStatus) && !StatusFlow.CanChange(status, newStatus)) + { + throw new DomainException($"Content cannot be changed from status {status} to {newStatus}."); + } + } + private void VerifyNotCreated() { if (isCreated) diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs b/src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs index d0dd44e65..251afb1e6 100644 --- a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs +++ b/src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs @@ -36,10 +36,5 @@ namespace Squidex.Controllers.Api.Webhooks.Models /// True, when to send a message for published events. /// public bool SendPublish { get; set; } - - /// - /// True, when to send a message for unpublished events. - /// - public bool SendUnpublish { get; set; } } } diff --git a/src/Squidex/Controllers/ContentApi/ContentsController.cs b/src/Squidex/Controllers/ContentApi/ContentsController.cs index a02fc529a..5d415761c 100644 --- a/src/Squidex/Controllers/ContentApi/ContentsController.cs +++ b/src/Squidex/Controllers/ContentApi/ContentsController.cs @@ -206,7 +206,7 @@ namespace Squidex.Controllers.ContentApi { await contentQuery.FindSchemaAsync(App, name); - var command = new PublishContent { ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Published, ContentId = id, User = User }; await CommandBus.PublishAsync(command); @@ -221,7 +221,7 @@ namespace Squidex.Controllers.ContentApi { await contentQuery.FindSchemaAsync(App, name); - var command = new UnpublishContent { ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id, User = User }; await CommandBus.PublishAsync(command); @@ -236,7 +236,7 @@ namespace Squidex.Controllers.ContentApi { await contentQuery.FindSchemaAsync(App, name); - var command = new ArchiveContent { ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Archived, ContentId = id, User = User }; await CommandBus.PublishAsync(command); @@ -251,7 +251,7 @@ namespace Squidex.Controllers.ContentApi { await contentQuery.FindSchemaAsync(App, name); - var command = new RestoreContent { ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id, User = User }; await CommandBus.PublishAsync(command); diff --git a/src/Squidex/Controllers/ContentApi/Models/ContentDto.cs b/src/Squidex/Controllers/ContentApi/Models/ContentDto.cs index bfaca41c2..3d95b00af 100644 --- a/src/Squidex/Controllers/ContentApi/Models/ContentDto.cs +++ b/src/Squidex/Controllers/ContentApi/Models/ContentDto.cs @@ -52,14 +52,9 @@ namespace Squidex.Controllers.ContentApi.Models public Instant LastModified { get; set; } /// - /// Indicates if the content item is published. + /// Gets the status of the content. /// - public bool? IsPublished { get; set; } - - /// - /// Indicates if the content item is archived. - /// - public bool IsArchived { get; set; } + public Status Status { get; set; } /// /// The version of the content. @@ -79,7 +74,7 @@ namespace Squidex.Controllers.ContentApi.Models CreatedBy = command.Actor, LastModified = now, LastModifiedBy = command.Actor, - IsPublished = command.Publish + Status = command.Publish ? Status.Published : Status.Draft }; return response; diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index 0d1fff1b8..f8a5fd83a 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -233,7 +233,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone } } - if (this.content.isArchived) { + if (this.content.status === 'Archived') { this.contentForm.disable(); } } diff --git a/src/Squidex/app/features/content/shared/content-item.component.html b/src/Squidex/app/features/content/shared/content-item.component.html index 75d92db75..b35ba9d6f 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.html +++ b/src/Squidex/app/features/content/shared/content-item.component.html @@ -4,7 +4,7 @@ - + {{content.lastModified | sqxFromNow}} @@ -17,19 +17,19 @@