From 50e338f434c925699c039c5f7095ad13065fc962 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 9 Feb 2018 18:48:37 +0100 Subject: [PATCH 01/10] Cleaned up the commands and removed unnecessary properties. --- .../Actions/FastlyActionHandler.cs | 3 - .../Assets/MongoAssetEntity.cs | 15 +- .../MongoAssetRepository_SnapshotStore.cs | 1 + .../Assets/Visitors/FindExtensions.cs | 2 +- .../Contents/MongoContentEntity.cs | 20 +- .../Contents/MongoContentRepository.cs | 16 +- .../MongoContentRepository_SnapshotStore.cs | 8 +- .../Contents/Visitors/FindExtensions.cs | 2 +- .../History/MongoHistoryEventEntity.cs | 4 +- .../MongoRuleRepository_SnapshotStore.cs | 2 +- .../MongoSchemaRepository_SnapshotStore.cs | 2 +- .../AppProvider.cs | 2 +- .../Apps/AppCommandMiddleware.cs | 3 +- .../Apps/Commands/AddLanguage.cs | 2 +- .../Apps/Commands/AddPattern.cs | 2 +- .../Commands/AppCommand.cs} | 8 +- .../Apps/Commands/AssignContributor.cs | 2 +- .../Apps/Commands/AttachClient.cs | 9 +- .../Apps/Commands/ChangePlan.cs | 2 +- .../Apps/Commands/CreateApp.cs | 9 +- .../Apps/Commands/DeletePattern.cs | 2 +- .../Apps/Commands/RemoveContributor.cs | 2 +- .../Apps/Commands/RemoveLanguage.cs | 2 +- .../Apps/Commands/RevokeClient.cs | 2 +- .../Apps/Commands/UpdateClient.cs | 2 +- .../Apps/Commands/UpdateLanguage.cs | 2 +- .../Apps/Commands/UpdatePattern.cs | 2 +- .../Templates/CreateBlogCommandMiddleware.cs | 191 +++++++++--------- .../Assets/AssetDomainObject.cs | 11 + ...setAggregateCommand.cs => AssetCommand.cs} | 2 +- .../Assets/Commands/CreateAsset.cs | 5 +- .../Assets/Commands/DeleteAsset.cs | 2 +- .../Assets/Commands/RenameAsset.cs | 2 +- .../Assets/Commands/UpdateAsset.cs | 2 +- .../Assets/IAssetEntity.cs | 5 +- .../Assets/State/AssetState.cs | 8 +- .../Contents/Commands/ContentCommand.cs | 2 +- .../Contents/Commands/CreateContent.cs | 7 +- .../Commands/PublishContentAt.cs} | 6 +- .../Contents/ContentCommandMiddleware.cs | 10 + .../Contents/ContentDomainObject.cs | 25 +++ .../Contents/ContentEntity.cs | 8 +- .../Contents/ContentOperationContext.cs | 17 +- .../Contents/ContentPublisher.cs | 58 ++++++ .../GraphQL/Types/AppMutationsGraphType.cs | 44 ++-- .../Contents/Guards/GuardContent.cs | 14 ++ .../Contents/IContentEntity.cs | 12 +- .../Repositories/IContentRepository.cs | 3 + .../Contents/State/ContentState.cs | 30 ++- .../EntityMapper.cs | 9 - .../{AppCommand.cs => IAppCommand.cs} | 7 +- .../IEntityWithAppRef.cs | 16 -- .../{SchemaCommand.cs => ISchemaCommand.cs} | 7 +- .../Rules/Commands/CreateRule.cs | 5 +- .../Rules/Commands/DeleteRule.cs | 2 +- .../Rules/Commands/DisableRule.cs | 2 +- .../Rules/Commands/EnableRule.cs | 2 +- ...RuleAggregateCommand.cs => RuleCommand.cs} | 2 +- .../Rules/Commands/RuleEditCommand.cs | 2 +- .../Rules/Guards/GuardRule.cs | 5 +- .../Rules/IRuleEntity.cs | 5 +- .../Rules/RuleCommandMiddleware.cs | 2 +- .../Rules/RuleDomainObject.cs | 11 + .../Rules/State/RuleState.cs | 9 +- .../Schemas/Commands/AddField.cs | 2 +- .../Schemas/Commands/ConfigureScripts.cs | 2 +- .../Schemas/Commands/CreateSchema.cs | 15 +- .../Schemas/Commands/DeleteSchema.cs | 2 +- .../Schemas/Commands/FieldCommand.cs | 2 +- .../Schemas/Commands/PublishSchema.cs | 2 +- .../Schemas/Commands/ReorderFields.cs | 2 +- .../Commands/SchemaCommand.cs} | 6 +- .../Schemas/Commands/UnpublishSchema.cs | 2 +- .../Schemas/Commands/UpdateSchema.cs | 2 +- .../Schemas/ISchemaEntity.cs | 5 +- .../Schemas/SchemaDomainObject.cs | 16 ++ .../Schemas/State/SchemaState.cs | 12 +- .../Contents/ContentPublishScheduled.cs | 18 ++ .../Config/Authentication/MicrosoftHandler.cs | 1 - .../EnrichWithAppIdCommandMiddleware.cs | 2 +- .../EnrichWithSchemaIdCommandMiddleware.cs | 28 ++- .../Apps/AppCommandMiddlewareTests.cs | 3 + .../Apps/AppDomainObjectTests.cs | 7 + .../Assets/AssetDomainObjectTests.cs | 9 +- .../Contents/ContentDomainObjectTests.cs | 4 + .../Contents/GraphQL/GraphQLMutationTests.cs | 6 - .../Contents/TestData/FakeAssetEntity.cs | 4 +- .../Rules/Guards/GuardRuleTests.cs | 10 +- .../Rules/RuleDomainObjectTests.cs | 11 +- .../Schemas/SchemaDomainObjectTests.cs | 7 +- .../TestHelpers/HandlerTestBase.cs | 8 +- tools/Migrate_01/Migration02_AddPatterns.cs | 3 +- 92 files changed, 566 insertions(+), 303 deletions(-) rename src/Squidex.Domain.Apps.Entities/{SchemaAggregateCommand.cs => Apps/Commands/AppCommand.cs} (71%) rename src/Squidex.Domain.Apps.Entities/Assets/Commands/{AssetAggregateCommand.cs => AssetCommand.cs} (88%) rename src/Squidex.Domain.Apps.Entities/{IUpdateableEntityWithAppRef.cs => Contents/Commands/PublishContentAt.cs} (69%) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/ContentPublisher.cs rename src/Squidex.Domain.Apps.Entities/{AppCommand.cs => IAppCommand.cs} (70%) delete mode 100644 src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs rename src/Squidex.Domain.Apps.Entities/{SchemaCommand.cs => ISchemaCommand.cs} (69%) rename src/Squidex.Domain.Apps.Entities/Rules/Commands/{RuleAggregateCommand.cs => RuleCommand.cs} (88%) rename src/Squidex.Domain.Apps.Entities/{AppAggregateCommand.cs => Schemas/Commands/SchemaCommand.cs} (77%) create mode 100644 src/Squidex.Domain.Apps.Events/Contents/ContentPublishScheduled.cs diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs index bc3e716b3..5a8a252bd 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs @@ -8,14 +8,11 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Events; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Http; diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs index 468bd2110..2beb7522a 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs @@ -20,9 +20,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets IAssetEntity, IUpdateableEntityWithVersion, IUpdateableEntityWithCreatedBy, - IUpdateableEntityWithLastModifiedBy, - IUpdateableEntityWithAppRef + IUpdateableEntityWithLastModifiedBy { + [BsonRequired] + [BsonElement] + public Guid IdxAppId { get; set; } + + [BsonRequired] + [BsonElement] + public NamedId AppId { get; set; } + [BsonRequired] [BsonElement] public string MimeType { get; set; } @@ -55,10 +62,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets [BsonElement] public int? PixelHeight { get; set; } - [BsonRequired] - [BsonElement] - public Guid AppId { get; set; } - [BsonRequired] [BsonElement] public RefToken CreatedBy { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs index 3e1cf0c22..2573debe2 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs @@ -36,6 +36,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var entity = SimpleMapper.Map(value, new MongoAssetEntity()); entity.Version = newVersion; + entity.IdxAppId = value.AppId.Id; await Collection.ReplaceOneAsync(x => x.Id == key && x.Version == oldVersion, entity, Upsert); } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs index b43fc8f48..f8ba52bf6 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors { var filters = new List> { - Filter.Eq(x => x.AppId, appId), + Filter.Eq(x => x.IdxAppId, appId), Filter.Eq(x => x.IsDeleted, false) }; diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index 68d92be78..1cb2f8191 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -35,12 +35,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents [BsonRequired] [BsonElement("ai")] [BsonRepresentation(BsonType.String)] - public Guid AppId { get; set; } + public Guid IdxAppId { get; set; } [BsonRequired] [BsonElement("si")] [BsonRepresentation(BsonType.String)] - public Guid SchemaId { get; set; } + public Guid IdxSchemaId { get; set; } [BsonRequired] [BsonElement("rf")] @@ -62,6 +62,22 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents [BsonJson] public IdContentData DataByIds { get; set; } + [BsonRequired] + [BsonElement("ai2")] + public NamedId AppId { get; set; } + + [BsonRequired] + [BsonElement("si2")] + public NamedId SchemaId { get; set; } + + [BsonIgnoreIfNull] + [BsonElement("pa")] + public Instant? PublishAt { get; set; } + + [BsonIgnoreIfNull] + [BsonElement("pb")] + public RefToken PublishAtBy { get; set; } + [BsonRequired] [BsonElement("ct")] public Instant Created { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index a8ad1abcc..ac05250e7 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.OData.UriParser; using MongoDB.Driver; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents; @@ -59,13 +60,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await collection.Indexes.CreateOneAsync( Index .Text(x => x.DataText) - .Ascending(x => x.SchemaId) + .Ascending(x => x.IdxSchemaId) .Ascending(x => x.Status) .Ascending(x => x.IsDeleted)); await collection.Indexes.CreateOneAsync( Index - .Ascending(x => x.SchemaId) + .Ascending(x => x.IdxSchemaId) .Ascending(x => x.Id) .Ascending(x => x.IsDeleted) .Ascending(x => x.Status)); @@ -121,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids) { - var find = Collection.Find(x => x.SchemaId == schema.Id && ids.Contains(x.Id) && x.IsDeleted == false && status.Contains(x.Status)); + var find = Collection.Find(x => x.IdxSchemaId == schema.Id && ids.Contains(x.Id) && x.IsDeleted == false && status.Contains(x.Status)); var contentItems = find.ToListAsync(); var contentCount = find.CountAsync(); @@ -139,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList ids) { var contentEntities = - await Collection.Find(x => x.SchemaId == schemaId && ids.Contains(x.Id) && x.IsDeleted == false).Only(x => x.Id) + await Collection.Find(x => x.IdxSchemaId == schemaId && ids.Contains(x.Id) && x.IsDeleted == false).Only(x => x.Id) .ToListAsync(); return ids.Except(contentEntities.Select(x => Guid.Parse(x["id"].AsString))).ToList(); @@ -159,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) { var contentEntity = - await Collection.Find(x => x.SchemaId == schema.Id && x.Id == id && x.IsDeleted == false) + await Collection.Find(x => x.IdxSchemaId == schema.Id && x.Id == id && x.IsDeleted == false) .FirstOrDefaultAsync(); contentEntity?.ParseData(schema.SchemaDef); @@ -173,5 +174,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await base.ClearAsync(); } + + public Task QueryContentToPublishAsync(Instant now, Func callback) + { + throw new NotSupportedException(); + } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs index c220e416e..7fa994d7f 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents if (contentEntity != null) { - var schema = await GetSchemaAsync(contentEntity.AppId, contentEntity.SchemaId); + var schema = await GetSchemaAsync(contentEntity.IdxAppId, contentEntity.IdxSchemaId); contentEntity?.ParseData(schema.SchemaDef); @@ -40,12 +40,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion) { - if (value.SchemaId == Guid.Empty) + if (value.SchemaId.Id == Guid.Empty) { return; } - var schema = await GetSchemaAsync(value.AppId, value.SchemaId); + var schema = await GetSchemaAsync(value.SchemaId.Id, value.SchemaId.Id); var idData = value.Data?.ToIdModel(schema.SchemaDef, true); @@ -53,6 +53,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents var document = SimpleMapper.Map(value, new MongoContentEntity { + IdxAppId = value.AppId.Id, + IdxSchemaId = value.SchemaId.Id, IsDeleted = value.IsDeleted, DocumentId = key.ToString(), DataText = idData?.ToFullText(), diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs index c4ca769b6..8f9118e1c 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { var filters = new List> { - Filter.Eq(x => x.SchemaId, schemaId), + Filter.Eq(x => x.IdxSchemaId, schemaId), Filter.In(x => x.Status, status), Filter.Eq(x => x.IsDeleted, false) }; diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs index 610d38977..5e9d9d50d 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs @@ -16,11 +16,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.History { public sealed class MongoHistoryEventEntity : MongoEntity, IEntity, - IEntityWithAppRef, IUpdateableEntity, IUpdateableEntityWithVersion, - IUpdateableEntityWithCreatedBy, - IUpdateableEntityWithAppRef + IUpdateableEntityWithCreatedBy { [BsonElement] [BsonRequired] diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs index 408a3aaba..cd8a2ee02 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules { return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u .Set(x => x.State, value) - .Set(x => x.AppId, value.AppId) + .Set(x => x.AppId, value.AppId.Id) .Set(x => x.IsDeleted, value.IsDeleted)); } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs index aadd8cbcb..a23899a7a 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas { return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u .Set(x => x.State, value) - .Set(x => x.AppId, value.AppId) + .Set(x => x.AppId, value.AppId.Id) .Set(x => x.Name, value.Name) .Set(x => x.IsDeleted, value.IsDeleted)); } diff --git a/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/src/Squidex.Domain.Apps.Entities/AppProvider.cs index ace83d66c..ee7e5cab4 100644 --- a/src/Squidex.Domain.Apps.Entities/AppProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities { var schema = await stateFactory.GetSingleAsync(id); - if (!IsFound(schema) || schema.Snapshot.IsDeleted || schema.Snapshot.AppId != appId) + if (!IsFound(schema) || schema.Snapshot.IsDeleted || schema.Snapshot.AppId.Id != appId) { return null; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs index 02bb242bd..829c1388c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Guards; @@ -180,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Apps } else { - var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, command.AppId.Id, a.Snapshot.Name, command.PlanId); + var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.Snapshot.Id, a.Snapshot.Name, command.PlanId); if (result is PlanChangedResult) { diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs index b2619a6f6..3cfec0965 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs @@ -9,7 +9,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class AddLanguage : AppAggregateCommand + public sealed class AddLanguage : AppCommand { public Language Language { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs index 5442536c1..30873adbb 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs @@ -9,7 +9,7 @@ using System; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class AddPattern : AppAggregateCommand + public sealed class AddPattern : AppCommand { public Guid PatternId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/SchemaAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs similarity index 71% rename from src/Squidex.Domain.Apps.Entities/SchemaAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs index d3d73dd62..a391da077 100644 --- a/src/Squidex.Domain.Apps.Entities/SchemaAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs @@ -8,13 +8,15 @@ using System; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Entities +namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public abstract class SchemaAggregateCommand : SchemaCommand, IAggregateCommand + public abstract class AppCommand : SquidexCommand, IAggregateCommand { + public Guid AppId { get; set; } + Guid IAggregateCommand.AggregateId { - get { return SchemaId.Id; } + get { return AppId; } } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs index 13a60ddda..b54518a66 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class AssignContributor : AppAggregateCommand + public sealed class AssignContributor : AppCommand { public string ContributorId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs index 4239c1e47..0fd8c6c4d 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs @@ -9,10 +9,15 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class AttachClient : AppAggregateCommand + public sealed class AttachClient : AppCommand { public string Id { get; set; } - public string Secret { get; } = RandomHash.New(); + public string Secret { get; set; } + + public AttachClient() + { + Secret = RandomHash.New(); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs index b5af896bf..a323b8b10 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class ChangePlan : AppAggregateCommand + public sealed class ChangePlan : AppCommand { public bool FromCallback { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs index 5e8050247..d4dc2528b 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs @@ -10,19 +10,12 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class CreateApp : SquidexCommand, IAggregateCommand + public sealed class CreateApp : AppCommand, IAggregateCommand { - public Guid AppId { get; set; } - public string Name { get; set; } public string Template { get; set; } - Guid IAggregateCommand.AggregateId - { - get { return AppId; } - } - public CreateApp() { AppId = Guid.NewGuid(); diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs index 5db33b435..199bff83c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs @@ -9,7 +9,7 @@ using System; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class DeletePattern : AppAggregateCommand + public sealed class DeletePattern : AppCommand { public Guid PatternId { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs index 6f707811f..a4e27d426 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class RemoveContributor : AppAggregateCommand + public sealed class RemoveContributor : AppCommand { public string ContributorId { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs index c863e7b85..602c35756 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs @@ -9,7 +9,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class RemoveLanguage : AppAggregateCommand + public sealed class RemoveLanguage : AppCommand { public Language Language { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs index 623da3058..9361891ba 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class RevokeClient : AppAggregateCommand + public sealed class RevokeClient : AppCommand { public string Id { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs index 856de1f4a..6002bba30 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class UpdateClient : AppAggregateCommand + public sealed class UpdateClient : AppCommand { public string Id { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs index c0f442a25..52e40e5d0 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class UpdateLanguage : AppAggregateCommand + public sealed class UpdateLanguage : AppCommand { public Language Language { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs index 9ae7b510e..415856189 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs @@ -9,7 +9,7 @@ using System; namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class UpdatePattern : AppAggregateCommand + public sealed class UpdatePattern : AppCommand { public Guid PatternId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs index 6eeac4e55..adea21e4c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs @@ -36,17 +36,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates { var appId = new NamedId(createApp.AppId, createApp.Name); - Task publishAsync(AppCommand command) - { - command.AppId = appId; - - return context.CommandBus.PublishAsync(command); - } - return Task.WhenAll( - CreatePagesAsync(publishAsync, appId), - CreatePostsAsync(publishAsync, appId), - CreateClientAsync(publishAsync, appId)); + CreatePagesAsync(context.CommandBus, appId), + CreatePostsAsync(context.CommandBus, appId), + CreateClientAsync(context.CommandBus, appId)); } return TaskHelper.Done; @@ -57,16 +50,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase); } - private static async Task CreateClientAsync(Func publishAsync, NamedId appId) + private static async Task CreateClientAsync(ICommandBus bus, NamedId appId) { - await publishAsync(new AttachClient { Id = "sample-client" }); + await bus.PublishAsync(new AttachClient { Id = "sample-client" }); } - private async Task CreatePostsAsync(Func publishAsync, NamedId appId) + private async Task CreatePostsAsync(ICommandBus bus, NamedId appId) { - var postsId = await CreatePostsSchema(publishAsync); + var postsId = await CreatePostsSchema(bus, appId); - await publishAsync(new CreateContent + await bus.PublishAsync(new CreateContent { SchemaId = postsId, Data = @@ -81,11 +74,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates }); } - private async Task CreatePagesAsync(Func publishAsync, NamedId appId) + private async Task CreatePagesAsync(ICommandBus bus, NamedId appId) { - var pagesId = await CreatePagesSchema(publishAsync); + var pagesId = await CreatePagesSchema(bus, appId); - await publishAsync(new CreateContent + await bus.PublishAsync(new CreateContent { SchemaId = pagesId, Data = @@ -100,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates }); } - private async Task> CreatePostsSchema(Func publishAsync) + private async Task> CreatePostsSchema(ICommandBus bus, NamedId appId) { var command = new CreateSchema { @@ -111,58 +104,59 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates Label = "Posts" }, Fields = new List + { + new CreateSchemaField { - new CreateSchemaField + Name = "title", + Partitioning = Partitioning.Invariant.Key, + Properties = new StringFieldProperties { - Name = "title", - Partitioning = Partitioning.Invariant.Key, - Properties = new StringFieldProperties - { - Editor = StringFieldEditor.Input, - IsRequired = true, - IsListField = true, - MaxLength = 100, - MinLength = 0, - Label = "Title" - } - }, - new CreateSchemaField + Editor = StringFieldEditor.Input, + IsRequired = true, + IsListField = true, + MaxLength = 100, + MinLength = 0, + Label = "Title" + } + }, + new CreateSchemaField + { + Name = "slug", + Partitioning = Partitioning.Invariant.Key, + Properties = new StringFieldProperties { - Name = "slug", - Partitioning = Partitioning.Invariant.Key, - Properties = new StringFieldProperties - { - Editor = StringFieldEditor.Slug, - IsRequired = false, - IsListField = true, - MaxLength = 100, - MinLength = 0, - Label = "Slug (Autogenerated)" - }, - IsDisabled = true + Editor = StringFieldEditor.Slug, + IsRequired = false, + IsListField = true, + MaxLength = 100, + MinLength = 0, + Label = "Slug (Autogenerated)" }, - new CreateSchemaField + IsDisabled = true + }, + new CreateSchemaField + { + Name = "text", + Partitioning = Partitioning.Invariant.Key, + Properties = new StringFieldProperties { - Name = "text", - Partitioning = Partitioning.Invariant.Key, - Properties = new StringFieldProperties - { - Editor = StringFieldEditor.RichText, - IsRequired = true, - IsListField = false, - Label = "Text" - } + Editor = StringFieldEditor.RichText, + IsRequired = true, + IsListField = false, + Label = "Text" } } + }, + AppId = appId }; - await publishAsync(command); + await bus.PublishAsync(command); var schemaId = new NamedId(command.SchemaId, command.Name); - await publishAsync(new ConfigureScripts + await bus.PublishAsync(new ConfigureScripts { - SchemaId = schemaId, + SchemaId = schemaId.Id, ScriptCreate = SlugScript, ScriptUpdate = SlugScript }); @@ -170,7 +164,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates return schemaId; } - private async Task> CreatePagesSchema(Func publishAsync) + private async Task> CreatePagesSchema(ICommandBus bus, NamedId appId) { var command = new CreateSchema { @@ -180,58 +174,59 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates Label = "Pages" }, Fields = new List + { + new CreateSchemaField { - new CreateSchemaField + Name = "title", + Partitioning = Partitioning.Invariant.Key, + Properties = new StringFieldProperties { - Name = "title", - Partitioning = Partitioning.Invariant.Key, - Properties = new StringFieldProperties - { - Editor = StringFieldEditor.Input, - IsRequired = true, - IsListField = true, - MaxLength = 100, - MinLength = 0, - Label = "Title" - } - }, - new CreateSchemaField + Editor = StringFieldEditor.Input, + IsRequired = true, + IsListField = true, + MaxLength = 100, + MinLength = 0, + Label = "Title" + } + }, + new CreateSchemaField + { + Name = "slug", + Partitioning = Partitioning.Invariant.Key, + Properties = new StringFieldProperties { - Name = "slug", - Partitioning = Partitioning.Invariant.Key, - Properties = new StringFieldProperties - { - Editor = StringFieldEditor.Slug, - IsRequired = false, - IsListField = true, - MaxLength = 100, - MinLength = 0, - Label = "Slug (Autogenerated)" - }, - IsDisabled = true + Editor = StringFieldEditor.Slug, + IsRequired = false, + IsListField = true, + MaxLength = 100, + MinLength = 0, + Label = "Slug (Autogenerated)" }, - new CreateSchemaField + IsDisabled = true + }, + new CreateSchemaField + { + Name = "text", + Partitioning = Partitioning.Invariant.Key, + Properties = new StringFieldProperties { - Name = "text", - Partitioning = Partitioning.Invariant.Key, - Properties = new StringFieldProperties - { - Editor = StringFieldEditor.RichText, - IsRequired = true, - IsListField = false, - Label = "Text" - } + Editor = StringFieldEditor.RichText, + IsRequired = true, + IsListField = false, + Label = "Text" } } + }, + AppId = appId }; - await publishAsync(command); + await bus.PublishAsync(command); var schemaId = new NamedId(command.SchemaId, command.Name); - await publishAsync(new ConfigureScripts + await bus.PublishAsync(new ConfigureScripts { - SchemaId = schemaId, + SchemaId = schemaId.Id, ScriptCreate = SlugScript, ScriptUpdate = SlugScript }); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs index 103cd9981..cd8e74dd4 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs @@ -7,6 +7,7 @@ using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.State; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -74,6 +75,16 @@ namespace Squidex.Domain.Apps.Entities.Assets return this; } + private void RaiseEvent(AppEvent @event) + { + if (@event.AppId == null) + { + @event.AppId = Snapshot.AppId; + } + + RaiseEvent(Envelope.Create(@event)); + } + private void VerifyNotCreated() { if (!string.IsNullOrWhiteSpace(Snapshot.FileName)) diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs similarity index 88% rename from src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs index efdb41e00..4898243dd 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public abstract class AssetAggregateCommand : AppCommand, IAggregateCommand + public abstract class AssetCommand : SquidexCommand, IAggregateCommand { public Guid AssetId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs index f7696e14b..f421d9ed8 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs @@ -6,12 +6,15 @@ // ========================================================================== using System; +using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class CreateAsset : AssetAggregateCommand + public sealed class CreateAsset : AssetCommand, IAppCommand { + public NamedId AppId { get; set; } + public AssetFile File { get; set; } public ImageInfo ImageInfo { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs index 351333962..4848be209 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class DeleteAsset : AssetAggregateCommand + public sealed class DeleteAsset : AssetCommand { } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs index 3dc784b88..65cba7f35 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class RenameAsset : AssetAggregateCommand + public sealed class RenameAsset : AssetCommand { public string FileName { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs index 8c419ca71..1bb193419 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs @@ -9,7 +9,7 @@ using Squidex.Infrastructure.Assets; namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class UpdateAsset : AssetAggregateCommand + public sealed class UpdateAsset : AssetCommand { public AssetFile File { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs index c95179f66..c61c52cc0 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs @@ -5,18 +5,21 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetEntity : IEntity, - IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion, IAssetInfo { + NamedId AppId { get; } + string MimeType { get; } long FileVersion { get; } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs b/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs index 8a2f65796..3ed7714a7 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs @@ -10,6 +10,7 @@ using Newtonsoft.Json; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Infrastructure; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -18,11 +19,10 @@ namespace Squidex.Domain.Apps.Entities.Assets.State { public class AssetState : DomainObjectState, IAssetEntity, - IAssetInfo, - IUpdateableEntityWithAppRef + IAssetInfo { [JsonProperty] - public Guid AppId { get; set; } + public NamedId AppId { get; set; } [JsonProperty] public string FileName { get; set; } @@ -61,6 +61,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.State SimpleMapper.Map(@event, this); TotalSize += @event.FileSize; + + AppId = @event.AppId; } protected void On(AssetUpdated @event) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs index 3629bdfdc..8e15d2a7b 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.Commands { - public abstract class ContentCommand : SchemaCommand, IAggregateCommand + public abstract class ContentCommand : SquidexCommand, IAggregateCommand { public Guid ContentId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs index 71e0a11e7..2eec65f73 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs @@ -6,11 +6,16 @@ // ========================================================================== using System; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.Commands { - public sealed class CreateContent : ContentDataCommand + public sealed class CreateContent : ContentDataCommand, ISchemaCommand, IAppCommand { + public NamedId AppId { get; set; } + + public NamedId SchemaId { get; set; } + public bool Publish { get; set; } public CreateContent() diff --git a/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithAppRef.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/PublishContentAt.cs similarity index 69% rename from src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithAppRef.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/PublishContentAt.cs index c8ca9da7e..3b20c1aeb 100644 --- a/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithAppRef.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/PublishContentAt.cs @@ -7,10 +7,10 @@ using System; -namespace Squidex.Domain.Apps.Entities +namespace Squidex.Domain.Apps.Entities.Contents.Commands { - public interface IUpdateableEntityWithAppRef + public sealed class PublishContentAt : ContentDataCommand { - Guid AppId { get; set; } + public DateTimeOffset PublishAt { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs index 6b10ba6df..dec465b99 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs @@ -131,6 +131,16 @@ namespace Squidex.Domain.Apps.Entities.Contents }); } + protected Task On(PublishContentAt command, CommandContext context) + { + return handler.UpdateAsync(context, content => + { + GuardContent.CanPublishAt(command); + + content.PublishAt(command); + }); + } + public async Task HandleAsync(CommandContext context, Func next) { await this.DispatchActionAsync(context.Command, context); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index 0b46b12d7..dd7e4fc05 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.State; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -50,6 +51,15 @@ namespace Squidex.Domain.Apps.Entities.Contents return this; } + public ContentDomainObject PublishAt(PublishContentAt command) + { + VerifyCreatedAndNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new ContentPublishScheduled())); + + return this; + } + public ContentDomainObject Update(UpdateContent command) { VerifyCreatedAndNotDeleted(); @@ -80,6 +90,21 @@ namespace Squidex.Domain.Apps.Entities.Contents return this; } + private void RaiseEvent(SchemaEvent @event) + { + if (@event.AppId == null) + { + @event.AppId = Snapshot.AppId; + } + + if (@event.SchemaId == null) + { + @event.SchemaId = Snapshot.SchemaId; + } + + RaiseEvent(Envelope.Create(@event)); + } + private void VerifyNotCreated() { if (Snapshot.Data != null) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs index 9c0bc7af7..84269d0e5 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs @@ -18,7 +18,9 @@ namespace Squidex.Domain.Apps.Entities.Contents { public Guid Id { get; set; } - public Guid AppId { get; set; } + public NamedId AppId { get; set; } + + public NamedId SchemaId { get; set; } public long Version { get; set; } @@ -26,6 +28,10 @@ namespace Squidex.Domain.Apps.Entities.Contents public Instant LastModified { get; set; } + public Instant? PublishAt { get; set; } + + public RefToken PublishAtBy { get; set; } + public RefToken CreatedBy { get; set; } public RefToken LastModifiedBy { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index c6a8c72b3..8660af246 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -42,7 +42,16 @@ namespace Squidex.Domain.Apps.Entities.Contents IScriptEngine scriptEngine, Func message) { - var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(command.AppId.Id, command.SchemaId.Id); + var a = content.Snapshot.AppId; + var s = content.Snapshot.SchemaId; + + if (command is CreateContent createContent) + { + a = a ?? createContent.AppId; + s = s ?? createContent.SchemaId; + } + + var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(a.Id, s.Id); var context = new ContentOperationContext { @@ -75,17 +84,15 @@ namespace Squidex.Domain.Apps.Entities.Contents { var errors = new List(); - var appId = command.AppId.Id; - var ctx = new ValidationContext( (contentIds, schemaId) => { - return QueryContentsAsync(appId, schemaId, contentIds); + return QueryContentsAsync(content.Snapshot.AppId.Id, schemaId, contentIds); }, assetIds => { - return QueryAssetsAsync(appId, assetIds); + return QueryAssetsAsync(content.Snapshot.AppId.Id, assetIds); }); if (partial) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentPublisher.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentPublisher.cs new file mode 100644 index 000000000..026a53296 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentPublisher.cs @@ -0,0 +1,58 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using NodaTime; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Timers; + +namespace Squidex.Domain.Apps.Entities.Contents +{ + public sealed class ContentPublisher : IRunnable + { + private readonly CompletionTimer timer; + private readonly IContentRepository contentRepository; + private readonly ICommandBus commandBus; + private readonly IClock clock; + + public ContentPublisher( + IContentRepository contentRepository, + ICommandBus commandBus, + IClock clock) + { + Guard.NotNull(contentRepository, nameof(contentRepository)); + Guard.NotNull(commandBus, nameof(commandBus)); + Guard.NotNull(clock, nameof(clock)); + + this.contentRepository = contentRepository; + this.commandBus = commandBus; + this.clock = clock; + + timer = new CompletionTimer(5000, x => PublishAsync()); + } + + public void Run() + { + } + + private Task PublishAsync() + { + var now = clock.GetCurrentInstant(); + + return contentRepository.QueryContentToPublishAsync(now, content => + { + var command = new ChangeContentStatus { ContentId = content.Id, Status = Status.Published, Actor = content.PublishAtBy }; + + return commandBus.PublishAsync(command); + }); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs index 22c30512c..58ec5d018 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -38,13 +38,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var inputType = new ContentDataGraphInputType(model, schema); AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType); - AddContentUpdate(schemaId, schemaType, schemaName, inputType, resultType); - AddContentPatch(schemaId, schemaType, schemaName, inputType, resultType); - AddContentPublish(schemaId, schemaType, schemaName); - AddContentUnpublish(schemaId, schemaType, schemaName); - AddContentArchive(schemaId, schemaType, schemaName); - AddContentRestore(schemaId, schemaType, schemaName); - AddContentDelete(schemaId, schemaType, schemaName); + AddContentUpdate(schemaType, schemaName, inputType, resultType); + AddContentPatch(schemaType, schemaName, inputType, resultType); + AddContentPublish(schemaType, schemaName); + AddContentUnpublish(schemaType, schemaName); + AddContentArchive(schemaType, schemaName); + AddContentRestore(schemaType, schemaName); + AddContentDelete(schemaType, schemaName); } Description = "The app mutations."; @@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var contentData = GetContentData(c); - var command = new CreateContent { SchemaId = schemaId, ContentId = Guid.NewGuid(), Data = contentData, Publish = argPublish }; + var command = new CreateContent { SchemaId = schemaId, Data = contentData, Publish = argPublish }; var commandContext = await publish(command); var result = commandContext.Result>(); @@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentUpdate(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) + private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) { AddField(new FieldType { @@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var contentId = c.GetArgument("id"); var contentData = GetContentData(c); - var command = new UpdateContent { SchemaId = schemaId, ContentId = contentId, Data = contentData }; + var command = new UpdateContent { ContentId = contentId, Data = contentData }; var commandContext = await publish(command); var result = commandContext.Result(); @@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentPatch(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) + private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) { AddField(new FieldType { @@ -179,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var contentId = c.GetArgument("id"); var contentData = GetContentData(c); - var command = new PatchContent { SchemaId = schemaId, ContentId = contentId, Data = contentData }; + var command = new PatchContent { ContentId = contentId, Data = contentData }; var commandContext = await publish(command); var result = commandContext.Result(); @@ -190,7 +190,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentPublish(NamedId schemaId, string schemaType, string schemaName) + private void AddContentPublish(string schemaType, string schemaName) { AddField(new FieldType { @@ -201,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var contentId = c.GetArgument("id"); - var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Published }; + var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Published }; return publish(command); }), @@ -209,7 +209,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentUnpublish(NamedId schemaId, string schemaType, string schemaName) + private void AddContentUnpublish(string schemaType, string schemaName) { AddField(new FieldType { @@ -220,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var contentId = c.GetArgument("id"); - var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Draft }; + var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft }; return publish(command); }), @@ -228,7 +228,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentArchive(NamedId schemaId, string schemaType, string schemaName) + private void AddContentArchive(string schemaType, string schemaName) { AddField(new FieldType { @@ -239,7 +239,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var contentId = c.GetArgument("id"); - var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Archived }; + var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Archived }; return publish(command); }), @@ -247,7 +247,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentRestore(NamedId schemaId, string schemaType, string schemaName) + private void AddContentRestore(string schemaType, string schemaName) { AddField(new FieldType { @@ -258,7 +258,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var contentId = c.GetArgument("id"); - var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Draft }; + var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft }; return publish(command); }), @@ -266,7 +266,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentDelete(NamedId schemaId, string schemaType, string schemaName) + private void AddContentDelete(string schemaType, string schemaName) { AddField(new FieldType { @@ -277,7 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var contentId = c.GetArgument("id"); - var command = new DeleteContent { SchemaId = schemaId, ContentId = contentId }; + var command = new DeleteContent { ContentId = contentId }; return publish(command); }), diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index 1ae0abb79..092c144df 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; @@ -65,6 +66,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards }); } + public static void CanPublishAt(PublishContentAt command) + { + Guard.NotNull(command, nameof(command)); + + Validate.It(() => "Cannot schedule content tol publish.", error => + { + if (command.PublishAt < DateTime.UtcNow) + { + error(new ValidationError("Date must be in the future.", nameof(command.PublishAt))); + } + }); + } + public static void CanDelete(DeleteContent command) { Guard.NotNull(command, nameof(command)); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs index 4e9573116..d17ccc2e7 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs @@ -6,19 +6,29 @@ // ========================================================================== // ========================================================================== +using System; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentEntity : IEntity, - IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { + NamedId AppId { get; } + + NamedId SchemaId { get; } + Status Status { get; } + Instant? PublishAt { get; } + + RefToken PublishAtBy { get; } + NamedContentData Data { get; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index 509fc42ca..3f925d049 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.OData.UriParser; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Schemas; @@ -27,5 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id); Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version); + + Task QueryContentToPublishAsync(Instant now, Func callback); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs index cdc9884f1..75bb8f6d2 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs @@ -7,38 +7,47 @@ using System; using Newtonsoft.Json; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Contents.State { public class ContentState : DomainObjectState, - IContentEntity, - IUpdateableEntityWithAppRef + IContentEntity { [JsonProperty] - public NamedContentData Data { get; set; } + public NamedId AppId { get; set; } [JsonProperty] - public Guid AppId { get; set; } + public NamedId SchemaId { get; set; } [JsonProperty] - public Guid SchemaId { get; set; } + public NamedContentData Data { get; set; } [JsonProperty] public Status Status { get; set; } + [JsonProperty] + public RefToken PublishAtBy { get; set; } + + [JsonProperty] + public Instant? PublishAt { get; set; } + [JsonProperty] public bool IsDeleted { get; set; } protected void On(ContentCreated @event) { - SchemaId = @event.SchemaId.Id; + SchemaId = @event.SchemaId; Data = @event.Data; + + AppId = @event.AppId; } protected void On(ContentUpdated @event) @@ -46,9 +55,18 @@ namespace Squidex.Domain.Apps.Entities.Contents.State Data = @event.Data; } + protected void On(ContentPublishScheduled @event) + { + PublishAt = @event.PublishAt; + PublishAtBy = @event.Actor; + } + protected void On(ContentStatusChanged @event) { Status = @event.Status; + + PublishAt = null; + PublishAtBy = null; } protected void On(ContentDeleted @event) diff --git a/src/Squidex.Domain.Apps.Entities/EntityMapper.cs b/src/Squidex.Domain.Apps.Entities/EntityMapper.cs index 1dd17d847..f990ae781 100644 --- a/src/Squidex.Domain.Apps.Entities/EntityMapper.cs +++ b/src/Squidex.Domain.Apps.Entities/EntityMapper.cs @@ -17,7 +17,6 @@ namespace Squidex.Domain.Apps.Entities public static T Update(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action updater = null) where T : IEntity { SetId(entity, headers); - SetAppId(entity, @event); SetCreated(entity, headers); SetCreatedBy(entity, @event); SetLastModified(entity, headers); @@ -76,13 +75,5 @@ namespace Squidex.Domain.Apps.Entities withModifiedBy.LastModifiedBy = @event.Actor; } } - - private static void SetAppId(IEntity entity, SquidexEvent @event) - { - if (entity is IUpdateableEntityWithAppRef appEntity && @event is AppEvent appEvent) - { - appEntity.AppId = appEvent.AppId.Id; - } - } } } diff --git a/src/Squidex.Domain.Apps.Entities/AppCommand.cs b/src/Squidex.Domain.Apps.Entities/IAppCommand.cs similarity index 70% rename from src/Squidex.Domain.Apps.Entities/AppCommand.cs rename to src/Squidex.Domain.Apps.Entities/IAppCommand.cs index ba5b6a4e4..6a7bcf31b 100644 --- a/src/Squidex.Domain.Apps.Entities/AppCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/IAppCommand.cs @@ -1,17 +1,18 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities { - public abstract class AppCommand : SquidexCommand + public interface IAppCommand : ICommand { - public NamedId AppId { get; set; } + NamedId AppId { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs b/src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs deleted file mode 100644 index bf899acef..000000000 --- a/src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Domain.Apps.Entities -{ - public interface IEntityWithAppRef - { - Guid AppId { get; } - } -} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/SchemaCommand.cs b/src/Squidex.Domain.Apps.Entities/ISchemaCommand.cs similarity index 69% rename from src/Squidex.Domain.Apps.Entities/SchemaCommand.cs rename to src/Squidex.Domain.Apps.Entities/ISchemaCommand.cs index 62db6405d..bd75842d8 100644 --- a/src/Squidex.Domain.Apps.Entities/SchemaCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/ISchemaCommand.cs @@ -1,17 +1,18 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities { - public abstract class SchemaCommand : AppCommand + public interface ISchemaCommand : ICommand { - public NamedId SchemaId { get; set; } + NamedId SchemaId { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs index 667f95dd7..07b49c12b 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs @@ -6,11 +6,14 @@ // ========================================================================== using System; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Rules.Commands { - public sealed class CreateRule : RuleEditCommand + public sealed class CreateRule : RuleEditCommand, IAppCommand { + public NamedId AppId { get; set; } + public CreateRule() { RuleId = Guid.NewGuid(); diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs index 055730bd9..d895ed5b4 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Commands { - public sealed class DeleteRule : RuleAggregateCommand + public sealed class DeleteRule : RuleCommand { } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs index 0718a7b12..de40cf95a 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Commands { - public sealed class DisableRule : RuleAggregateCommand + public sealed class DisableRule : RuleCommand { } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs index b35f97d86..62cd528f0 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Commands { - public sealed class EnableRule : RuleAggregateCommand + public sealed class EnableRule : RuleCommand { } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs similarity index 88% rename from src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs index 41a81ea3e..7d8690c46 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Rules.Commands { - public abstract class RuleAggregateCommand : AppCommand, IAggregateCommand + public abstract class RuleCommand : SquidexCommand, IAggregateCommand { public Guid RuleId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs index ba8d77981..e461ff8ac 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Domain.Apps.Entities.Rules.Commands { - public abstract class RuleEditCommand : RuleAggregateCommand + public abstract class RuleEditCommand : RuleCommand { public RuleTrigger Trigger { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs index 249553206..77f1298df 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; @@ -44,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards }); } - public static Task CanUpdate(UpdateRule command, IAppProvider appProvider) + public static Task CanUpdate(UpdateRule command, Guid appId, IAppProvider appProvider) { Guard.NotNull(command, nameof(command)); @@ -57,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (command.Trigger != null) { - var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Id, command.Trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(appId, command.Trigger, appProvider); errors.Foreach(error); } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs b/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs index 2caaa814c..ef69c574f 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs @@ -5,17 +5,20 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Rules { public interface IRuleEntity : IEntity, - IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { + NamedId AppId { get; set; } + Rule RuleDef { get; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs index c0ee1cf5b..8a6e43441 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Rules { return handler.UpdateSyncedAsync(context, async r => { - await GuardRule.CanUpdate(command, appProvider); + await GuardRule.CanUpdate(command, r.Snapshot.AppId.Id, appProvider); r.Update(command); }); diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs index bc05ac8bd..03d3dd802 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs @@ -7,6 +7,7 @@ using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.State; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Rules; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -52,6 +53,16 @@ namespace Squidex.Domain.Apps.Entities.Rules RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); } + private void RaiseEvent(AppEvent @event) + { + if (@event.AppId == null) + { + @event.AppId = Snapshot.AppId; + } + + RaiseEvent(Envelope.Create(@event)); + } + private void VerifyNotCreated() { if (Snapshot.RuleDef != null) diff --git a/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs b/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs index 7b6dd9602..fa87078da 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs @@ -10,18 +10,17 @@ using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Rules; +using Squidex.Infrastructure; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Rules.State { public class RuleState : DomainObjectState, - IRuleEntity, - IEntityWithAppRef, - IUpdateableEntityWithAppRef + IRuleEntity { [JsonProperty] - public Guid AppId { get; set; } + public NamedId AppId { get; set; } [JsonProperty] public Rule RuleDef { get; set; } @@ -32,6 +31,8 @@ namespace Squidex.Domain.Apps.Entities.Rules.State protected void On(RuleCreated @event) { RuleDef = new Rule(@event.Trigger, @event.Action); + + AppId = @event.AppId; } protected void On(RuleUpdated @event) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs index e14d082e8..8856821d6 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class AddField : SchemaAggregateCommand + public sealed class AddField : SchemaCommand { public string Name { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs index f4fea680a..d850076a6 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class ConfigureScripts : SchemaAggregateCommand + public sealed class ConfigureScripts : SchemaCommand { public string ScriptQuery { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs index afbd735f5..923499e79 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs @@ -7,14 +7,16 @@ using System; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure; using SchemaFields = System.Collections.Generic.List; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class CreateSchema : AppCommand, IAggregateCommand + public sealed class CreateSchema : SchemaCommand, IAppCommand { - public Guid SchemaId { get; set; } + public NamedId AppId { get; set; } + + public string Name { get; set; } public SchemaFields Fields { get; set; } @@ -22,13 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands public bool Publish { get; set; } - public string Name { get; set; } - - Guid IAggregateCommand.AggregateId - { - get { return SchemaId; } - } - public CreateSchema() { SchemaId = Guid.NewGuid(); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs index d4c3d9bfb..d3b79c454 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class DeleteSchema : SchemaAggregateCommand + public sealed class DeleteSchema : SchemaCommand { } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs index 9ddbd0301..5ad93ddf1 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public class FieldCommand : SchemaAggregateCommand + public class FieldCommand : SchemaCommand { public long FieldId { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs index 8bb789b72..c8d68314d 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class PublishSchema : SchemaAggregateCommand + public sealed class PublishSchema : SchemaCommand { } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs index 068b41162..9afe0346c 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs @@ -9,7 +9,7 @@ using System.Collections.Generic; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class ReorderFields : SchemaAggregateCommand + public sealed class ReorderFields : SchemaCommand { public List FieldIds { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs similarity index 77% rename from src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs index a4143215c..49bba3620 100644 --- a/src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs @@ -10,11 +10,13 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities { - public class AppAggregateCommand : AppCommand, IAggregateCommand + public abstract class SchemaCommand : SquidexCommand, IAggregateCommand { + public Guid SchemaId { get; set; } + Guid IAggregateCommand.AggregateId { - get { return AppId.Id; } + get { return SchemaId; } } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs index c8c2b722d..31d5c284a 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class UnpublishSchema : SchemaAggregateCommand + public sealed class UnpublishSchema : SchemaCommand { } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs index 329cbc400..579f55bb7 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class UpdateSchema : SchemaAggregateCommand + public sealed class UpdateSchema : SchemaCommand { public SchemaProperties Properties { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs index da1fd2045..8c341e76e 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs @@ -5,17 +5,20 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Schemas { public interface ISchemaEntity : IEntity, - IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { + NamedId AppId { get; } + string Name { get; } bool IsPublished { get; } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs index cc081ee28..04c904853 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.State; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -190,6 +191,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas RaiseEvent(@event); } + private void RaiseEvent(SchemaEvent @event) + { + if (@event.SchemaId == null) + { + @event.SchemaId = new NamedId(Snapshot.Id, Snapshot.Name); + } + + if (@event.AppId == null) + { + @event.AppId = Snapshot.AppId; + } + + RaiseEvent(Envelope.Create(@event)); + } + private void VerifyNotCreated() { if (Snapshot.SchemaDef != null) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs index 5d06deb3b..bc148430b 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs @@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -18,16 +19,13 @@ using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Schemas.State { public class SchemaState : DomainObjectState, - ISchemaEntity, - IUpdateableEntityWithAppRef, - IUpdateableEntityWithCreatedBy, - IUpdateableEntityWithLastModifiedBy + ISchemaEntity { [JsonProperty] - public string Name { get; set; } + public NamedId AppId { get; set; } [JsonProperty] - public Guid AppId { get; set; } + public string Name { get; set; } [JsonProperty] public int TotalFields { get; set; } = 0; @@ -108,6 +106,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State } SchemaDef = schema; + + AppId = @event.AppId; } protected void On(FieldAdded @event, FieldRegistry registry) diff --git a/src/Squidex.Domain.Apps.Events/Contents/ContentPublishScheduled.cs b/src/Squidex.Domain.Apps.Events/Contents/ContentPublishScheduled.cs new file mode 100644 index 000000000..50ef1581f --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Contents/ContentPublishScheduled.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using NodaTime; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Contents +{ + [EventType(nameof(ContentPublishScheduled))] + public sealed class ContentPublishScheduled : ContentEvent + { + public Instant PublishAt { get; set; } + } +} diff --git a/src/Squidex/Config/Authentication/MicrosoftHandler.cs b/src/Squidex/Config/Authentication/MicrosoftHandler.cs index c308814af..168995ad9 100644 --- a/src/Squidex/Config/Authentication/MicrosoftHandler.cs +++ b/src/Squidex/Config/Authentication/MicrosoftHandler.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.OAuth; using Squidex.Shared.Identity; diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index 27d93b073..908fee35d 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -25,7 +25,7 @@ namespace Squidex.Pipeline.CommandMiddlewares public Task HandleAsync(CommandContext context, Func next) { - if (context.Command is AppCommand appCommand && appCommand.AppId == null) + if (context.Command is IAppCommand appCommand && appCommand.AppId == null) { var appFeature = httpContextAccessor.HttpContext.Features.Get(); diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index 15a20857f..de1552cf8 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -29,8 +29,30 @@ namespace Squidex.Pipeline.CommandMiddlewares public async Task HandleAsync(CommandContext context, Func next) { - if (context.Command is SchemaCommand schemaCommand && schemaCommand.SchemaId == null) + if (context.Command is ISchemaCommand schemaCommand && schemaCommand.SchemaId == null) { + NamedId appId = null; + + if (context.Command is IAppCommand appCommand) + { + appId = appCommand.AppId; + } + + if (appId == null) + { + var appFeature = actionContextAccessor.ActionContext.HttpContext.Features.Get(); + + if (appFeature != null && appFeature.App != null) + { + appId = new NamedId(appFeature.App.Id, appFeature.App.Name); + } + } + + if (appId == null) + { + return; + } + var routeValues = actionContextAccessor.ActionContext.RouteData.Values; if (routeValues.ContainsKey("name")) @@ -41,11 +63,11 @@ namespace Squidex.Pipeline.CommandMiddlewares if (Guid.TryParse(schemaName, out var id)) { - schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Id, id); + schema = await appProvider.GetSchemaAsync(appId.Id, id); } else { - schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Id, schemaName); + schema = await appProvider.GetSchemaAsync(appId.Id, schemaName); } if (schema == null) diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs index 42223eaaa..18ab306fa 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.States; using Squidex.Shared.Users; using Xunit; @@ -46,6 +47,8 @@ namespace Squidex.Domain.Apps.Entities.Apps .Returns(A.Fake()); sut = new AppCommandMiddleware(Handler, appProvider, appPlansProvider, appPlansBillingManager, userResolver); + + app.ActivateAsync(Id, A.Fake>()); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index 5e6a61a90..e49cf171e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -8,11 +8,13 @@ using System; using System.Collections.Generic; using System.Linq; +using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Apps; using Squidex.Infrastructure; +using Squidex.Infrastructure.States; using Xunit; namespace Squidex.Domain.Apps.Entities.Apps @@ -31,6 +33,11 @@ namespace Squidex.Domain.Apps.Entities.Apps get { return AppId; } } + public AppDomainObjectTests() + { + sut.ActivateAsync(Id, A.Fake>()); + } + [Fact] public void Create_should_throw_exception_if_created() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs index 4f2d8400d..b1c057749 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs @@ -7,11 +7,13 @@ using System; using System.IO; +using FakeItEasy; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; +using Squidex.Infrastructure.States; using Xunit; namespace Squidex.Domain.Apps.Entities.Assets @@ -28,6 +30,11 @@ namespace Squidex.Domain.Apps.Entities.Assets get { return assetId; } } + public AssetDomainObjectTests() + { + sut.ActivateAsync(Id, A.Fake>()); + } + [Fact] public void Create_should_throw_exception_if_created() { @@ -203,7 +210,7 @@ namespace Squidex.Domain.Apps.Entities.Assets return CreateEvent(@event); } - protected T CreateAssetCommand(T command) where T : AssetAggregateCommand + protected T CreateAssetCommand(T command) where T : AssetCommand { command.AssetId = assetId; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs index f3f731edf..b0a061422 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs @@ -6,12 +6,14 @@ // ========================================================================== using System; +using FakeItEasy; using FluentAssertions; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure; +using Squidex.Infrastructure.States; using Xunit; namespace Squidex.Domain.Apps.Entities.Contents @@ -40,6 +42,8 @@ namespace Squidex.Domain.Apps.Entities.Contents public ContentDomainObjectTests() { patched = otherData.MergeInto(data); + + sut.ActivateAsync(Id, A.Fake>()); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index d92690828..aeebd190d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -11,7 +11,6 @@ using FakeItEasy; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Xunit; @@ -328,7 +327,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && x.Status == Status.Published && x.ExpectedVersion == 10))) @@ -364,7 +362,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && x.Status == Status.Draft && x.ExpectedVersion == 10))) @@ -400,7 +397,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && x.Status == Status.Archived && x.ExpectedVersion == 10))) @@ -436,7 +432,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && x.Status == Status.Draft && x.ExpectedVersion == 10))) @@ -472,7 +467,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && x.ExpectedVersion == 10))) .MustHaveHappened(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs index c8bf013c3..82a77eaef 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs @@ -14,9 +14,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.TestData { public sealed class FakeAssetEntity : IAssetEntity { - public Guid Id { get; set; } + public NamedId AppId { get; set; } - public Guid AppId { get; set; } + public Guid Id { get; set; } public Guid AssetId { get; set; } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs index 65607a265..9d061491c 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs @@ -87,13 +87,13 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { var command = new UpdateRule(); - await Assert.ThrowsAsync(() => GuardRule.CanUpdate(command, appProvider)); + await Assert.ThrowsAsync(() => GuardRule.CanUpdate(command, appId.Id, appProvider)); } [Fact] public async Task CanUpdate_should_not_throw_exception_if_trigger_and_action_valid() { - var command = CreateCommand(new UpdateRule + var command = new UpdateRule { Trigger = new ContentChangedTrigger { @@ -103,9 +103,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { Url = validUrl } - }); + }; - await GuardRule.CanUpdate(command, appProvider); + await GuardRule.CanUpdate(command, appId.Id, appProvider); } [Fact] @@ -156,7 +156,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards GuardRule.CanDelete(command); } - private T CreateCommand(T command) where T : AppCommand + private CreateRule CreateCommand(CreateRule command) { command.AppId = appId; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs index 095e5b439..0afbaab17 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Immutable; +using FakeItEasy; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Triggers; @@ -14,6 +15,7 @@ using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Rules; using Squidex.Infrastructure; +using Squidex.Infrastructure.States; using Xunit; namespace Squidex.Domain.Apps.Entities.Rules @@ -30,6 +32,11 @@ namespace Squidex.Domain.Apps.Entities.Rules get { return ruleId; } } + public RuleDomainObjectTests() + { + sut.ActivateAsync(Id, A.Fake>()); + } + [Fact] public void Create_should_throw_exception_if_created() { @@ -48,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Rules sut.Create(CreateRuleCommand(command)); - Assert.Equal(AppId, sut.Snapshot.AppId); + Assert.Equal(AppId, sut.Snapshot.AppId.Id); Assert.Same(ruleTrigger, sut.Snapshot.RuleDef.Trigger); Assert.Same(ruleAction, sut.Snapshot.RuleDef.Action); @@ -239,7 +246,7 @@ namespace Squidex.Domain.Apps.Entities.Rules return CreateEvent(@event); } - protected T CreateRuleCommand(T command) where T : RuleAggregateCommand + protected T CreateRuleCommand(T command) where T : RuleCommand { command.RuleId = ruleId; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs index a3b8b5ba9..71922b093 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs @@ -8,11 +8,13 @@ using System; using System.Collections.Generic; using System.Linq; +using FakeItEasy; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.States; using Xunit; namespace Squidex.Domain.Apps.Entities.Schemas @@ -35,6 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas var fieldRegistry = new FieldRegistry(new TypeNameRegistry()); sut = new SchemaDomainObject(fieldRegistry); + sut.ActivateAsync(Id, A.Fake>()); } [Fact] @@ -55,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas sut.Create(CreateCommand(new CreateSchema { Name = SchemaName, SchemaId = SchemaId, Properties = properties })); - Assert.Equal(AppId, sut.Snapshot.AppId); + Assert.Equal(AppId, sut.Snapshot.AppId.Id); Assert.Equal(SchemaName, sut.Snapshot.Name); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); @@ -81,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas var @event = (SchemaCreated)sut.GetUncomittedEvents().Single().Payload; - Assert.Equal(AppId, sut.Snapshot.AppId); + Assert.Equal(AppId, sut.Snapshot.AppId.Id); Assert.Equal(SchemaName, sut.Snapshot.Name); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs index 73a745fde..0822c2f2c 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs @@ -134,16 +134,12 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers command.Actor = User; } - var appCommand = command as AppCommand; - - if (appCommand != null && appCommand.AppId == null) + if (command is IAppCommand appCommand && appCommand.AppId == null) { appCommand.AppId = AppNamedId; } - var schemaCommand = command as SchemaCommand; - - if (schemaCommand != null && schemaCommand.SchemaId == null) + if (command is ISchemaCommand schemaCommand && schemaCommand.SchemaId == null) { schemaCommand.SchemaId = SchemaNamedId; } diff --git a/tools/Migrate_01/Migration02_AddPatterns.cs b/tools/Migrate_01/Migration02_AddPatterns.cs index 279ce58cd..72fc5929a 100644 --- a/tools/Migrate_01/Migration02_AddPatterns.cs +++ b/tools/Migrate_01/Migration02_AddPatterns.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Repositories; -using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.States; @@ -50,7 +49,7 @@ namespace Migrate_01 new AddPattern { Actor = app.Snapshot.CreatedBy, - AppId = new NamedId(app.Snapshot.Id, app.Snapshot.Name), + AppId = app.Snapshot.Id, Name = pattern.Name, PatternId = Guid.NewGuid(), Pattern = pattern.Pattern, From 66f74fcfc8cbcdfb6f0d5e8dc900b16359b8c3c9 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 9 Feb 2018 20:35:53 +0100 Subject: [PATCH 02/10] Migrations fixed. --- .../MongoContentRepository_SnapshotStore.cs | 4 +- .../AppProvider.cs | 4 +- .../IAppProvider.cs | 2 +- .../Config/Domain/SerializationServices.cs | 4 +- src/Squidex/Config/Domain/WriteServices.cs | 3 ++ .../Contents/ContentQueryServiceTests.cs | 10 ++--- .../Rules/Guards/GuardRuleTests.cs | 2 +- .../Triggers/ContentChangedTriggerTests.cs | 4 +- .../Rules/RuleCommandMiddlewareTests.cs | 2 +- .../Migration05_RebuildForNewCommands.cs | 38 +++++++++++++++++++ tools/Migrate_01/SquidexMigrations.cs | 13 +++++++ 11 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 tools/Migrate_01/Migration05_RebuildForNewCommands.cs create mode 100644 tools/Migrate_01/SquidexMigrations.cs diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs index 7fa994d7f..901ce545b 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return; } - var schema = await GetSchemaAsync(value.SchemaId.Id, value.SchemaId.Id); + var schema = await GetSchemaAsync(value.AppId.Id, value.SchemaId.Id); var idData = value.Data?.ToIdModel(schema.SchemaDef, true); @@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents private async Task GetSchemaAsync(Guid appId, Guid schemaId) { - var schema = await appProvider.GetSchemaAsync(appId, schemaId); + var schema = await appProvider.GetSchemaAsync(appId, schemaId, true); if (schema == null) { diff --git a/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/src/Squidex.Domain.Apps.Entities/AppProvider.cs index ee7e5cab4..b2e028a12 100644 --- a/src/Squidex.Domain.Apps.Entities/AppProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -88,11 +88,11 @@ namespace Squidex.Domain.Apps.Entities return (await stateFactory.GetSingleAsync(schemaId)).Snapshot; } - public async Task GetSchemaAsync(Guid appId, Guid id) + public async Task GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false) { var schema = await stateFactory.GetSingleAsync(id); - if (!IsFound(schema) || schema.Snapshot.IsDeleted || schema.Snapshot.AppId.Id != appId) + if (!IsFound(schema) || (schema.Snapshot.IsDeleted && !allowDeleted) || schema.Snapshot.AppId.Id != appId) { return null; } diff --git a/src/Squidex.Domain.Apps.Entities/IAppProvider.cs b/src/Squidex.Domain.Apps.Entities/IAppProvider.cs index c41da76c9..e246b3cb7 100644 --- a/src/Squidex.Domain.Apps.Entities/IAppProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/IAppProvider.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities Task GetAppAsync(string appName); - Task GetSchemaAsync(Guid appId, Guid id); + Task GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false); Task GetSchemaAsync(Guid appId, string name); diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index c8f3cbb12..1b8f75a26 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -28,10 +28,10 @@ namespace Squidex.Config.Domain { private static readonly TypeNameRegistry TypeNameRegistry = new TypeNameRegistry() - .MapUnmapped(typeof(Migration01_FromCqrs).Assembly) .MapUnmapped(typeof(SquidexCoreModel).Assembly) .MapUnmapped(typeof(SquidexEvents).Assembly) - .MapUnmapped(typeof(SquidexInfrastructure).Assembly); + .MapUnmapped(typeof(SquidexInfrastructure).Assembly) + .MapUnmapped(typeof(SquidexMigrations).Assembly); private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry); diff --git a/src/Squidex/Config/Domain/WriteServices.cs b/src/Squidex/Config/Domain/WriteServices.cs index 7c890b235..2a59c47fa 100644 --- a/src/Squidex/Config/Domain/WriteServices.cs +++ b/src/Squidex/Config/Domain/WriteServices.cs @@ -79,6 +79,9 @@ namespace Squidex.Config.Domain services.AddTransientAs() .As(); + services.AddTransientAs() + .As(); + services.AddTransientAs() .AsSelf(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index 211f9345b..48f5338b0 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_return_schema_from_id_if_string_is_guid() { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); var result = await sut.FindSchemaAsync(app, schemaId.ToString()); @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_return_content_from_repository_and_transform() { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.FindContentAsync(app, schema, contentId)) .Returns(content); @@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_throw_if_content_to_find_does_not_exist() { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.FindContentAsync(app, schema, contentId)) @@ -196,7 +196,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private void SetupFakeWithIdQuery(Status[] status, HashSet ids) { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), ids)) @@ -205,7 +205,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private void SetupFakeWithOdataQuery(Status[] status) { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), A.Ignored)) diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs index 9d061491c..4934a4a33 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards public GuardRuleTests() { - A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A.Ignored)) + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A.Ignored, false)) .Returns(A.Fake()); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs index e406ec61b..cee1ddb81 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers [Fact] public async Task Should_add_error_if_schemas_ids_are_not_valid() { - A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored, false)) .Returns(Task.FromResult(null)); var trigger = new ContentChangedTrigger @@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers [Fact] public async Task Should_not_add_error_if_schemas_ids_are_valid() { - A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored, false)) .Returns(A.Fake()); var trigger = new ContentChangedTrigger diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs index 239ef4a13..863bd6d97 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Rules public RuleCommandMiddlewareTests() { - A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, A.Ignored)) + A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, A.Ignored, false)) .Returns(A.Fake()); sut = new RuleCommandMiddleware(Handler, appProvider); diff --git a/tools/Migrate_01/Migration05_RebuildForNewCommands.cs b/tools/Migrate_01/Migration05_RebuildForNewCommands.cs new file mode 100644 index 000000000..9548eb03d --- /dev/null +++ b/tools/Migrate_01/Migration05_RebuildForNewCommands.cs @@ -0,0 +1,38 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Infrastructure.Migrations; + +namespace Migrate_01 +{ + public sealed class Migration05_RebuildForNewCommands : IMigration + { + private readonly Rebuilder rebuilder; + + public int FromVersion { get; } = 4; + + public int ToVersion { get; } = 5; + + public Migration05_RebuildForNewCommands(Rebuilder rebuilder) + { + this.rebuilder = rebuilder; + } + + public async Task UpdateAsync(IEnumerable previousMigrations) + { + if (!previousMigrations.Any(x => x is Migration01_FromCqrs)) + { + await rebuilder.RebuildConfigAsync(); + await rebuilder.RebuildContentAsync(); + await rebuilder.RebuildAssetsAsync(); + } + } + } +} diff --git a/tools/Migrate_01/SquidexMigrations.cs b/tools/Migrate_01/SquidexMigrations.cs new file mode 100644 index 000000000..092912098 --- /dev/null +++ b/tools/Migrate_01/SquidexMigrations.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Migrate_01 +{ + public static class SquidexMigrations + { + } +} From 3e980ddd6fe5a3caeefe9578be223588ae3ddc4a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 9 Feb 2018 21:39:55 +0100 Subject: [PATCH 03/10] Scheduler improved. --- .../Contents/MongoContentEntity.cs | 16 +++++---- .../Contents/MongoContentRepository.cs | 28 +++++++++------ .../MongoContentRepository_SnapshotStore.cs | 6 ++-- .../Contents/Visitors/FindExtensions.cs | 2 +- .../Contents/Commands/ChangeContentStatus.cs | 3 ++ .../Contents/Commands/PublishContentAt.cs | 16 --------- .../Contents/ContentCommandMiddleware.cs | 17 +++------ .../Contents/ContentDomainObject.cs | 18 +++++----- .../Contents/ContentEntity.cs | 10 +++--- ...ontentPublisher.cs => ContentScheduler.cs} | 9 +++-- .../Contents/Guards/GuardContent.cs | 13 ++----- .../Contents/IContentEntity.cs | 6 ++-- .../Repositories/IContentRepository.cs | 2 +- .../Contents/State/ContentState.cs | 19 ++++++---- ...Scheduled.cs => ContentStatusScheduled.cs} | 9 +++-- .../Controllers/Content/ContentsController.cs | 35 ++++++++++++++----- 16 files changed, 111 insertions(+), 98 deletions(-) delete mode 100644 src/Squidex.Domain.Apps.Entities/Contents/Commands/PublishContentAt.cs rename src/Squidex.Domain.Apps.Entities/Contents/{ContentPublisher.cs => ContentScheduler.cs} (85%) rename src/Squidex.Domain.Apps.Events/Contents/{ContentPublishScheduled.cs => ContentStatusScheduled.cs} (67%) diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index 1cb2f8191..b98ff890a 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -35,12 +35,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents [BsonRequired] [BsonElement("ai")] [BsonRepresentation(BsonType.String)] - public Guid IdxAppId { get; set; } + public Guid AppIdId { get; set; } [BsonRequired] [BsonElement("si")] [BsonRepresentation(BsonType.String)] - public Guid IdxSchemaId { get; set; } + public Guid SchemaIdId { get; set; } [BsonRequired] [BsonElement("rf")] @@ -71,12 +71,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public NamedId SchemaId { get; set; } [BsonIgnoreIfNull] - [BsonElement("pa")] - public Instant? PublishAt { get; set; } + [BsonElement("st")] + public Status? ScheduledTo { get; set; } + + [BsonIgnoreIfNull] + [BsonElement("sa")] + public Instant? ScheduledAt { get; set; } [BsonIgnoreIfNull] - [BsonElement("pb")] - public RefToken PublishAtBy { get; set; } + [BsonElement("sb")] + public RefToken ScheduledBy { get; set; } [BsonRequired] [BsonElement("ct")] diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index ac05250e7..1d4cc508f 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -52,6 +52,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { await collection.Indexes.TryDropOneAsync("si_1_st_1_dl_1_dt_text"); + await archiveCollection.Indexes.CreateOneAsync( + Index + .Ascending(x => x.ScheduledTo)); + await archiveCollection.Indexes.CreateOneAsync( Index .Ascending(x => x.Id) @@ -60,13 +64,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await collection.Indexes.CreateOneAsync( Index .Text(x => x.DataText) - .Ascending(x => x.IdxSchemaId) + .Ascending(x => x.SchemaIdId) .Ascending(x => x.Status) .Ascending(x => x.IsDeleted)); await collection.Indexes.CreateOneAsync( Index - .Ascending(x => x.IdxSchemaId) + .Ascending(x => x.SchemaIdId) .Ascending(x => x.Id) .Ascending(x => x.IsDeleted) .Ascending(x => x.Status)); @@ -122,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids) { - var find = Collection.Find(x => x.IdxSchemaId == schema.Id && ids.Contains(x.Id) && x.IsDeleted == false && status.Contains(x.Status)); + var find = Collection.Find(x => x.SchemaIdId == schema.Id && ids.Contains(x.Id) && x.IsDeleted == false && status.Contains(x.Status)); var contentItems = find.ToListAsync(); var contentCount = find.CountAsync(); @@ -140,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList ids) { var contentEntities = - await Collection.Find(x => x.IdxSchemaId == schemaId && ids.Contains(x.Id) && x.IsDeleted == false).Only(x => x.Id) + await Collection.Find(x => x.SchemaIdId == schemaId && ids.Contains(x.Id) && x.IsDeleted == false).Only(x => x.Id) .ToListAsync(); return ids.Except(contentEntities.Select(x => Guid.Parse(x["id"].AsString))).ToList(); @@ -160,7 +164,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) { var contentEntity = - await Collection.Find(x => x.IdxSchemaId == schema.Id && x.Id == id && x.IsDeleted == false) + await Collection.Find(x => x.SchemaIdId == schema.Id && x.Id == id && x.IsDeleted == false) .FirstOrDefaultAsync(); contentEntity?.ParseData(schema.SchemaDef); @@ -168,16 +172,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return contentEntity; } + public Task QueryScheduledWithoutDataAsync(Instant now, Func callback) + { + return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted == false) + .ForEachAsync(c => + { + callback(c); + }); + } + public override async Task ClearAsync() { await Database.DropCollectionAsync("States_Contents_Archive"); await base.ClearAsync(); } - - public Task QueryContentToPublishAsync(Instant now, Func callback) - { - throw new NotSupportedException(); - } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs index 901ce545b..54e236e4a 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents if (contentEntity != null) { - var schema = await GetSchemaAsync(contentEntity.IdxAppId, contentEntity.IdxSchemaId); + var schema = await GetSchemaAsync(contentEntity.AppIdId, contentEntity.SchemaIdId); contentEntity?.ParseData(schema.SchemaDef); @@ -53,8 +53,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents var document = SimpleMapper.Map(value, new MongoContentEntity { - IdxAppId = value.AppId.Id, - IdxSchemaId = value.SchemaId.Id, + AppIdId = value.AppId.Id, + SchemaIdId = value.SchemaId.Id, IsDeleted = value.IsDeleted, DocumentId = key.ToString(), DataText = idData?.ToFullText(), diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs index 8f9118e1c..cdfaff9b8 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { var filters = new List> { - Filter.Eq(x => x.IdxSchemaId, schemaId), + Filter.Eq(x => x.SchemaIdId, schemaId), Filter.In(x => x.Status, status), Filter.Eq(x => x.IsDeleted, false) }; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs index 9e8de0bd2..5ca260cc4 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================= +using NodaTime; using Squidex.Domain.Apps.Core.Contents; namespace Squidex.Domain.Apps.Entities.Contents.Commands @@ -12,5 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands public sealed class ChangeContentStatus : ContentCommand { public Status Status { get; set; } + + public Instant? DueDate { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Commands/PublishContentAt.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/PublishContentAt.cs deleted file mode 100644 index 3b20c1aeb..000000000 --- a/src/Squidex.Domain.Apps.Entities/Contents/Commands/PublishContentAt.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Domain.Apps.Entities.Contents.Commands -{ - public sealed class PublishContentAt : ContentDataCommand - { - public DateTimeOffset PublishAt { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs index dec465b99..73f4619f1 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs @@ -109,9 +109,12 @@ namespace Squidex.Domain.Apps.Entities.Contents { GuardContent.CanChangeContentStatus(content.Snapshot.Status, command); - var operationContext = await CreateContext(command, content, () => "Failed to patch content."); + if (!command.DueDate.HasValue) + { + var operationContext = await CreateContext(command, content, () => "Failed to patch content."); - await operationContext.ExecuteScriptAsync(x => x.ScriptChange, command.Status); + await operationContext.ExecuteScriptAsync(x => x.ScriptChange, command.Status); + } content.ChangeStatus(command); }); @@ -131,16 +134,6 @@ namespace Squidex.Domain.Apps.Entities.Contents }); } - protected Task On(PublishContentAt command, CommandContext context) - { - return handler.UpdateAsync(context, content => - { - GuardContent.CanPublishAt(command); - - content.PublishAt(command); - }); - } - public async Task HandleAsync(CommandContext context, Func next) { await this.DispatchActionAsync(context.Command, context); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index dd7e4fc05..e27a947a4 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -46,16 +46,14 @@ namespace Squidex.Domain.Apps.Entities.Contents { VerifyCreatedAndNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged())); - - return this; - } - - public ContentDomainObject PublishAt(PublishContentAt command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new ContentPublishScheduled())); + if (command.DueDate.HasValue) + { + RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled())); + } + else + { + RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged())); + } return this; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs index 84269d0e5..27e1c1895 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs @@ -28,9 +28,13 @@ namespace Squidex.Domain.Apps.Entities.Contents public Instant LastModified { get; set; } - public Instant? PublishAt { get; set; } + public Status Status { get; set; } + + public Status? ScheduledTo { get; set; } + + public Instant? ScheduledAt { get; set; } - public RefToken PublishAtBy { get; set; } + public RefToken ScheduledBy { get; set; } public RefToken CreatedBy { get; set; } @@ -38,8 +42,6 @@ namespace Squidex.Domain.Apps.Entities.Contents public NamedContentData Data { get; set; } - public Status Status { get; set; } - public static ContentEntity Create(CreateContent command, EntityCreatedResult result) { var now = SystemClock.Instance.GetCurrentInstant(); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentPublisher.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentScheduler.cs similarity index 85% rename from src/Squidex.Domain.Apps.Entities/Contents/ContentPublisher.cs rename to src/Squidex.Domain.Apps.Entities/Contents/ContentScheduler.cs index 026a53296..23d3c05a9 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentPublisher.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentScheduler.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using NodaTime; -using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Infrastructure; @@ -16,14 +15,14 @@ using Squidex.Infrastructure.Timers; namespace Squidex.Domain.Apps.Entities.Contents { - public sealed class ContentPublisher : IRunnable + public sealed class ContentScheduler : IRunnable { private readonly CompletionTimer timer; private readonly IContentRepository contentRepository; private readonly ICommandBus commandBus; private readonly IClock clock; - public ContentPublisher( + public ContentScheduler( IContentRepository contentRepository, ICommandBus commandBus, IClock clock) @@ -47,9 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Contents { var now = clock.GetCurrentInstant(); - return contentRepository.QueryContentToPublishAsync(now, content => + return contentRepository.QueryScheduledWithoutDataAsync(now, content => { - var command = new ChangeContentStatus { ContentId = content.Id, Status = Status.Published, Actor = content.PublishAtBy }; + var command = new ChangeContentStatus { ContentId = content.Id, Status = content.ScheduledTo.Value, Actor = content.ScheduledBy }; return commandBus.PublishAsync(command); }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index 092c144df..00cd6526a 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; @@ -63,18 +64,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { error(new ValidationError($"Content cannot be changed from status {status} to {command.Status}.", nameof(command.Status))); } - }); - } - - public static void CanPublishAt(PublishContentAt command) - { - Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot schedule content tol publish.", error => - { - if (command.PublishAt < DateTime.UtcNow) + if (command.DueDate.HasValue && command.DueDate.Value < SystemClock.Instance.GetCurrentInstant()) { - error(new ValidationError("Date must be in the future.", nameof(command.PublishAt))); + error(new ValidationError("DueDate must be in the future.", nameof(command.DueDate))); } }); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs index d17ccc2e7..11a33154c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs @@ -25,9 +25,11 @@ namespace Squidex.Domain.Apps.Entities.Contents Status Status { get; } - Instant? PublishAt { get; } + Status? ScheduledTo { get; } - RefToken PublishAtBy { get; } + Instant? ScheduledAt { get; } + + RefToken ScheduledBy { get; } NamedContentData Data { get; } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index 3f925d049..b9aba61be 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -29,6 +29,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version); - Task QueryContentToPublishAsync(Instant now, Func callback); + Task QueryScheduledWithoutDataAsync(Instant now, Func callback); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs index 75bb8f6d2..489a03832 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs @@ -33,10 +33,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.State public Status Status { get; set; } [JsonProperty] - public RefToken PublishAtBy { get; set; } + public Status? ScheduledTo { get; set; } [JsonProperty] - public Instant? PublishAt { get; set; } + public Instant? ScheduledAt { get; set; } + + [JsonProperty] + public RefToken ScheduledBy { get; set; } [JsonProperty] public bool IsDeleted { get; set; } @@ -55,18 +58,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.State Data = @event.Data; } - protected void On(ContentPublishScheduled @event) + protected void On(ContentStatusScheduled @event) { - PublishAt = @event.PublishAt; - PublishAtBy = @event.Actor; + ScheduledAt = @event.DueTime; + ScheduledBy = @event.Actor; + ScheduledTo = @event.Status; } protected void On(ContentStatusChanged @event) { Status = @event.Status; - PublishAt = null; - PublishAtBy = null; + ScheduledAt = null; + ScheduledBy = null; + ScheduledTo = null; } protected void On(ContentDeleted @event) diff --git a/src/Squidex.Domain.Apps.Events/Contents/ContentPublishScheduled.cs b/src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs similarity index 67% rename from src/Squidex.Domain.Apps.Events/Contents/ContentPublishScheduled.cs rename to src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs index 50ef1581f..e0d0a5bac 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/ContentPublishScheduled.cs +++ b/src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs @@ -6,13 +6,16 @@ // ========================================================================== using NodaTime; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Contents { - [EventType(nameof(ContentPublishScheduled))] - public sealed class ContentPublishScheduled : ContentEvent + [EventType(nameof(ContentStatusScheduled))] + public sealed class ContentStatusScheduled : ContentEvent { - public Instant PublishAt { get; set; } + public Status Status { get; set; } + + public Instant DueTime { get; set; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs index 9fb56d378..812bdc9cd 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs @@ -10,6 +10,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using NodaTime; +using NodaTime.Text; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Contents.Models; using Squidex.Domain.Apps.Core.Contents; @@ -213,11 +215,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/publish/")] [ApiCosts(1)] - public async Task PublishContent(string name, Guid id) + public async Task PublishContent(string name, Guid id, string dueDate = null) { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Published, ContentId = id }; + var command = CreateCommand(id, Status.Published, dueDate); await CommandBus.PublishAsync(command); @@ -228,11 +230,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/unpublish/")] [ApiCosts(1)] - public async Task UnpublishContent(string name, Guid id) + public async Task UnpublishContent(string name, Guid id, string dueDate = null) { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id }; + var command = CreateCommand(id, Status.Draft, dueDate); await CommandBus.PublishAsync(command); @@ -243,11 +245,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/archive/")] [ApiCosts(1)] - public async Task ArchiveContent(string name, Guid id) + public async Task ArchiveContent(string name, Guid id, string dueDate = null) { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Archived, ContentId = id }; + var command = CreateCommand(id, Status.Archived, dueDate); await CommandBus.PublishAsync(command); @@ -258,11 +260,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/restore/")] [ApiCosts(1)] - public async Task RestoreContent(string name, Guid id) + public async Task RestoreContent(string name, Guid id, string dueDate = null) { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id }; + var command = CreateCommand(id, Status.Draft, dueDate); await CommandBus.PublishAsync(command); @@ -283,5 +285,22 @@ namespace Squidex.Areas.Api.Controllers.Contents return NoContent(); } + + private static ChangeContentStatus CreateCommand(Guid id, Status status, string dueDate) + { + Instant? dt = null; + + if (string.IsNullOrWhiteSpace(dueDate)) + { + var parseResult = InstantPattern.General.Parse(dueDate); + + if (!parseResult.Success) + { + dt = parseResult.Value; + } + } + + return new ChangeContentStatus { Status = status, ContentId = id, DueDate = dt }; + } } } From 7723799ab37225c11a3990a279c1e19518239fc9 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 9 Feb 2018 21:41:18 +0100 Subject: [PATCH 04/10] Property renamed. --- .../Assets/MongoAssetEntity.cs | 2 +- .../Assets/MongoAssetRepository_SnapshotStore.cs | 2 +- .../Assets/Visitors/FindExtensions.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs index 2beb7522a..2f2729858 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { [BsonRequired] [BsonElement] - public Guid IdxAppId { get; set; } + public Guid AppIdId { get; set; } [BsonRequired] [BsonElement] diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs index 2573debe2..a4056d0a7 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs @@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var entity = SimpleMapper.Map(value, new MongoAssetEntity()); entity.Version = newVersion; - entity.IdxAppId = value.AppId.Id; + entity.AppIdId = value.AppId.Id; await Collection.ReplaceOneAsync(x => x.Id == key && x.Version == oldVersion, entity, Upsert); } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs index f8ba52bf6..af3a75764 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors { var filters = new List> { - Filter.Eq(x => x.IdxAppId, appId), + Filter.Eq(x => x.AppIdId, appId), Filter.Eq(x => x.IsDeleted, false) }; From a13943bf182e1801696240c6152b71223db5af23 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 9 Feb 2018 21:52:04 +0100 Subject: [PATCH 05/10] * DueDate => DueTime (renamed) * Tests added * Parsing due time from query string. --- .../Contents/MongoContentEntity.cs | 6 ++--- .../Contents/Commands/ChangeContentStatus.cs | 2 +- .../Contents/ContentCommandMiddleware.cs | 2 +- .../Contents/ContentDomainObject.cs | 4 ++-- .../Contents/Guards/GuardContent.cs | 4 ++-- .../Controllers/Content/ContentsController.cs | 24 +++++++++---------- .../Contents/ContentCommandMiddlewareTests.cs | 16 +++++++++++++ .../Contents/ContentDomainObjectTests.cs | 20 ++++++++++++++++ .../Contents/Guard/GuardContentTests.cs | 11 +++++++++ 9 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index b98ff890a..f35cb2286 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -71,15 +71,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public NamedId SchemaId { get; set; } [BsonIgnoreIfNull] - [BsonElement("st")] + [BsonElement("sdt")] public Status? ScheduledTo { get; set; } [BsonIgnoreIfNull] - [BsonElement("sa")] + [BsonElement("sda")] public Instant? ScheduledAt { get; set; } [BsonIgnoreIfNull] - [BsonElement("sb")] + [BsonElement("sdb")] public RefToken ScheduledBy { get; set; } [BsonRequired] diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs index 5ca260cc4..e855a9aff 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs @@ -14,6 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands { public Status Status { get; set; } - public Instant? DueDate { get; set; } + public Instant? DueTime { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs index 73f4619f1..7674f1512 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs @@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { GuardContent.CanChangeContentStatus(content.Snapshot.Status, command); - if (!command.DueDate.HasValue) + if (!command.DueTime.HasValue) { var operationContext = await CreateContext(command, content, () => "Failed to patch content."); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index e27a947a4..3cda651fb 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -46,9 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Contents { VerifyCreatedAndNotDeleted(); - if (command.DueDate.HasValue) + if (command.DueTime.HasValue) { - RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled())); + RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value })); } else { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index 00cd6526a..dfd5d8b68 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -65,9 +65,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards error(new ValidationError($"Content cannot be changed from status {status} to {command.Status}.", nameof(command.Status))); } - if (command.DueDate.HasValue && command.DueDate.Value < SystemClock.Instance.GetCurrentInstant()) + if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant()) { - error(new ValidationError("DueDate must be in the future.", nameof(command.DueDate))); + error(new ValidationError("DueTime must be in the future.", nameof(command.DueTime))); } }); } diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs index 812bdc9cd..fc81dbc70 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs @@ -215,11 +215,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/publish/")] [ApiCosts(1)] - public async Task PublishContent(string name, Guid id, string dueDate = null) + public async Task PublishContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Published, dueDate); + var command = CreateCommand(id, Status.Published, dueTime); await CommandBus.PublishAsync(command); @@ -230,11 +230,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/unpublish/")] [ApiCosts(1)] - public async Task UnpublishContent(string name, Guid id, string dueDate = null) + public async Task UnpublishContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Draft, dueDate); + var command = CreateCommand(id, Status.Draft, dueTime); await CommandBus.PublishAsync(command); @@ -245,11 +245,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/archive/")] [ApiCosts(1)] - public async Task ArchiveContent(string name, Guid id, string dueDate = null) + public async Task ArchiveContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Archived, dueDate); + var command = CreateCommand(id, Status.Archived, dueTime); await CommandBus.PublishAsync(command); @@ -260,11 +260,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/restore/")] [ApiCosts(1)] - public async Task RestoreContent(string name, Guid id, string dueDate = null) + public async Task RestoreContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Draft, dueDate); + var command = CreateCommand(id, Status.Draft, dueTime); await CommandBus.PublishAsync(command); @@ -286,13 +286,13 @@ namespace Squidex.Areas.Api.Controllers.Contents return NoContent(); } - private static ChangeContentStatus CreateCommand(Guid id, Status status, string dueDate) + private static ChangeContentStatus CreateCommand(Guid id, Status status, string dueTime) { Instant? dt = null; - if (string.IsNullOrWhiteSpace(dueDate)) + if (string.IsNullOrWhiteSpace(dueTime)) { - var parseResult = InstantPattern.General.Parse(dueDate); + var parseResult = InstantPattern.General.Parse(dueTime); if (!parseResult.Success) { @@ -300,7 +300,7 @@ namespace Squidex.Areas.Api.Controllers.Contents } } - return new ChangeContentStatus { Status = status, ContentId = id, DueDate = dt }; + return new ChangeContentStatus { Status = status, ContentId = id, DueTime = dt }; } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs index 0d9f45594..113b574da 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs @@ -9,6 +9,7 @@ using System; using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; +using NodaTime; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; @@ -225,6 +226,21 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustHaveHappened(); } + [Fact] + public async Task ChangeStatus_should_not_invoke_scripts_when_scheduled() + { + CreateContent(); + + var context = CreateContextForCommand(new ChangeContentStatus { ContentId = contentId, User = user, Status = Status.Published, DueTime = Instant.MaxValue }); + + await TestUpdate(content, async _ => + { + await sut.HandleAsync(context); + }); + + A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustNotHaveHappened(); + } + [Fact] public async Task Delete_should_update_domain_object() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs index b0a061422..59e7e0213 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs @@ -8,6 +8,7 @@ using System; using FakeItEasy; using FluentAssertions; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -207,6 +208,25 @@ namespace Squidex.Domain.Apps.Entities.Contents ); } + [Fact] + public void ChangeStatus_should_refresh_properties_and_create_scheduled_events_when_command_has_due_time() + { + CreateContent(); + + var dueTime = Instant.MaxValue; + + sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = Status.Published, DueTime = dueTime })); + + Assert.Equal(Status.Draft, sut.Snapshot.Status); + Assert.Equal(Status.Published, sut.Snapshot.ScheduledTo); + Assert.Equal(dueTime, sut.Snapshot.ScheduledAt); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime }) + ); + } + [Fact] public void Delete_should_throw_exception_if_not_created() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs index 60dcbbeef..587a62955 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Guards; @@ -15,6 +16,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard { public class GuardContentTests { + private readonly Instant dueTimeInPast = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(1)); + [Fact] public void CanCreate_should_throw_exception_if_data_is_null() { @@ -79,6 +82,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard Assert.Throws(() => GuardContent.CanChangeContentStatus(Status.Archived, command)); } + [Fact] + public void CanChangeContentStatus_should_throw_exception_if_due_date_in_past() + { + var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast }; + + Assert.Throws(() => GuardContent.CanChangeContentStatus(Status.Draft, command)); + } + [Fact] public void CanChangeContentStatus_not_should_throw_exception_if_status_flow_valid() { From 91c5053ff8819774dda6a5b96e3b76e446416f80 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 9 Feb 2018 23:09:41 +0100 Subject: [PATCH 06/10] UI fixed and simplified. --- .../Controllers/Content/Models/ContentDto.cs | 15 +++ .../pages/content/content-page.component.ts | 35 +++--- .../pages/contents/contents-page.component.ts | 70 +++--------- .../app/features/content/pages/messages.ts | 11 +- .../shared/services/assets.service.spec.ts | 2 +- .../shared/services/contents.service.spec.ts | 94 ++++++---------- .../app/shared/services/contents.service.ts | 104 ++++++++---------- 7 files changed, 134 insertions(+), 197 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs index eee19d42f..f2c16cedd 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs @@ -40,6 +40,21 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models [Required] public object Data { get; set; } + /// + /// The scheduled status. + /// + public Status? ScheduledTo { get; } + + /// + /// The scheduled date. + /// + public Instant? ScheduledAt { get; } + + /// + /// The user that has scheduled the content. + /// + public RefToken ScheduledBy { get; } + /// /// The date and time when the content item has been created. /// 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 0b6467631..4dd2a16a4 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 @@ -12,9 +12,8 @@ import { Observable, Subscription } from 'rxjs'; import { ContentCreated, - ContentPublished, ContentRemoved, - ContentUnpublished, + ContentStatusChanged, ContentUpdated, ContentVersionSelected } from './../messages'; @@ -39,8 +38,7 @@ import { ] }) export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, OnInit { - private contentPublishedSubscription: Subscription; - private contentUnpublishedSubscription: Subscription; + private contentStatusChangedSubscription: Subscription; private contentDeletedSubscription: Subscription; private contentVersionSelectedSubscription: Subscription; @@ -63,8 +61,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, public ngOnDestroy() { this.contentVersionSelectedSubscription.unsubscribe(); - this.contentUnpublishedSubscription.unsubscribe(); - this.contentPublishedSubscription.unsubscribe(); + this.contentStatusChangedSubscription.unsubscribe(); this.contentDeletedSubscription.unsubscribe(); } @@ -75,27 +72,25 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, this.loadVersion(message.version); }); - this.contentPublishedSubscription = - this.ctx.bus.of(ContentPublished) - .subscribe(message => { - if (this.content && message.content.id === this.content.id) { - this.content = this.content.publish(message.content.lastModifiedBy, message.content.version, message.content.lastModified); - } - }); - - this.contentUnpublishedSubscription = - this.ctx.bus.of(ContentUnpublished) + this.contentDeletedSubscription = + this.ctx.bus.of(ContentRemoved) .subscribe(message => { if (this.content && message.content.id === this.content.id) { - this.content = this.content.unpublish(message.content.lastModifiedBy, message.content.version, message.content.lastModified); + this.router.navigate(['../'], { relativeTo: this.ctx.route }); } }); - this.contentDeletedSubscription = - this.ctx.bus.of(ContentRemoved) + this.contentStatusChangedSubscription = + this.ctx.bus.of(ContentStatusChanged) .subscribe(message => { if (this.content && message.content.id === this.content.id) { - this.router.navigate(['../'], { relativeTo: this.ctx.route }); + this.content = + this.content.changeStatus( + message.content.scheduledTo || message.content.status, + message.content.lastModifiedBy, + message.content.version, + message.content.lastModified, + message.content.scheduledAt); } }); diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts index a00c846b8..ec262312c 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts @@ -11,9 +11,8 @@ import { Observable, Subscription } from 'rxjs'; import { ContentCreated, - ContentPublished, ContentRemoved, - ContentUnpublished, + ContentStatusChanged, ContentUpdated } from './../messages'; @@ -27,7 +26,8 @@ import { ImmutableArray, ModalView, Pager, - SchemaDetailsDto + SchemaDetailsDto, + DateTime } from 'shared'; @Component({ @@ -118,7 +118,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit { } public publishContent(content: ContentDto) { - this.publishContentItem(content).subscribe(); + this.changeContentItem(content, 'publish', 'Published').subscribe(); } public publishSelected() { @@ -126,29 +126,15 @@ export class ContentsPageComponent implements OnDestroy, OnInit { this.contentItems.values .filter(c => this.selectedItems[c.id]) .filter(c => c.status !== 'Published') - .map(c => this.publishContentItem(c))) + .map(c => this.changeContentItem(c, 'publish', 'Published'))) .finally(() => { this.updateSelectionSummary(); }) .subscribe(); } - private publishContentItem(content: ContentDto): Observable { - return this.contentsService.publishContent(this.ctx.appName, this.schema.name, content.id, content.version) - .catch(error => { - this.ctx.notifyError(error); - - return Observable.throw(error); - }) - .do(dto => { - this.contentItems = this.contentItems.replaceBy('id', content.publish(this.ctx.userToken, dto.version)); - - this.emitContentPublished(content); - }); - } - public unpublishContent(content: ContentDto) { - this.unpublishContentItem(content).subscribe(); + this.changeContentItem(content, 'unpublish', 'Draft').subscribe(); } public unpublishSelected() { @@ -156,31 +142,31 @@ export class ContentsPageComponent implements OnDestroy, OnInit { this.contentItems.values .filter(c => this.selectedItems[c.id]) .filter(c => c.status !== 'Unpublished') - .map(c => this.unpublishContentItem(c))) + .map(c => this.changeContentItem(c, 'unpublish', 'Draft'))) .finally(() => { this.updateSelectionSummary(); }) .subscribe(); } - private unpublishContentItem(content: ContentDto): Observable { - return this.contentsService.unpublishContent(this.ctx.appName, this.schema.name, content.id, content.version) + private changeContentItem(content: ContentDto, action: string, status: string): Observable { + return this.contentsService.changeContentStatus(this.ctx.appName, this.schema.name, content.id, action, content.version) .catch(error => { this.ctx.notifyError(error); return Observable.throw(error); }) .do(dto => { - this.contentItems = this.contentItems.replaceBy('id', content.unpublish(this.ctx.userToken, dto.version)); + this.contentItems = this.contentItems.replaceBy('id', content.changeStatus(status, this.ctx.userToken, dto.version)); - this.emitContentUnpublished(content); + this.emitContentStatusChanged(content); }); } public archiveSelected() { Observable.forkJoin( this.contentItems.values.filter(c => this.selectedItems[c.id]) - .map(c => this.archiveContentItem(c))) + .map(c => this.changeContentItem(c, 'archive', 'Archived'))) .finally(() => { this.load(); }) @@ -188,26 +174,17 @@ export class ContentsPageComponent implements OnDestroy, OnInit { } public archiveContent(content: ContentDto) { - this.archiveContentItem(content) + this.changeContentItem(content, 'archive', 'Archived') .finally(() => { this.load(); }) .subscribe(); } - public archiveContentItem(content: ContentDto): Observable { - return this.contentsService.archiveContent(this.ctx.appName, this.schema.name, content.id, content.version) - .catch(error => { - this.ctx.notifyError(error); - - return Observable.throw(error); - }); - } - public restoreSelected() { Observable.forkJoin( this.contentItems.values.filter(c => this.selectedItems[c.id]) - .map(c => this.restoreContentItem(c))) + .map(c => this.changeContentItem(c, 'restore', 'Draft'))) .finally(() => { this.load(); }) @@ -215,22 +192,13 @@ export class ContentsPageComponent implements OnDestroy, OnInit { } public restoreContent(content: ContentDto) { - this.restoreContentItem(content) + this.changeContentItem(content, 'restore', 'Draft') .finally(() => { this.load(); }) .subscribe(); } - public restoreContentItem(content: ContentDto): Observable { - return this.contentsService.restoreContent(this.ctx.appName, this.schema.name, content.id, content.version) - .catch(error => { - this.ctx.notifyError(error); - - return Observable.throw(error); - }); - } - public deleteSelected(content: ContentDto) { Observable.forkJoin( this.contentItems.values.filter(c => this.selectedItems[c.id]) @@ -359,12 +327,8 @@ export class ContentsPageComponent implements OnDestroy, OnInit { this.languageSelected = language; } - private emitContentPublished(content: ContentDto) { - this.ctx.bus.emit(new ContentPublished(content)); - } - - private emitContentUnpublished(content: ContentDto) { - this.ctx.bus.emit(new ContentUnpublished(content)); + private emitContentStatusChanged(content: ContentDto) { + this.ctx.bus.emit(new ContentStatusChanged(content)); } private emitContentRemoved(content: ContentDto) { diff --git a/src/Squidex/app/features/content/pages/messages.ts b/src/Squidex/app/features/content/pages/messages.ts index 896f677e2..ca5a19fa4 100644 --- a/src/Squidex/app/features/content/pages/messages.ts +++ b/src/Squidex/app/features/content/pages/messages.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ContentDto } from 'shared'; +import { ContentDto, DateTime } from 'shared'; export class ContentCreated { constructor( @@ -35,14 +35,7 @@ export class ContentVersionSelected { } } -export class ContentPublished { - constructor( - public readonly content: ContentDto - ) { - } -} - -export class ContentUnpublished { +export class ContentStatusChanged { constructor( public readonly content: ContentDto ) { diff --git a/src/Squidex/app/shared/services/assets.service.spec.ts b/src/Squidex/app/shared/services/assets.service.spec.ts index 1fb922072..09cb9af92 100644 --- a/src/Squidex/app/shared/services/assets.service.spec.ts +++ b/src/Squidex/app/shared/services/assets.service.spec.ts @@ -89,7 +89,7 @@ describe('AssetsService', () => { }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets?$top=17&$skip=13'); - + expect(req.request.method).toEqual('GET'); expect(req.request.headers.get('If-Match')).toBeNull(); diff --git a/src/Squidex/app/shared/services/contents.service.spec.ts b/src/Squidex/app/shared/services/contents.service.spec.ts index 396fd3a2c..af7b1dc45 100644 --- a/src/Squidex/app/shared/services/contents.service.spec.ts +++ b/src/Squidex/app/shared/services/contents.service.spec.ts @@ -23,11 +23,12 @@ describe('ContentDto', () => { const creator = 'not-me'; const modified = DateTime.now(); const modifier = 'me'; + const dueTime = DateTime.now().addDays(1); const version = new Version('1'); const newVersion = new Version('2'); it('should update data property and user info when updating', () => { - const content_1 = new ContentDto('1', 'Published', creator, creator, creation, creation, { data: 1 }, version); + const content_1 = new ContentDto('1', 'Published', creator, creator, creation, creation, null, null, null, { data: 1 }, version); const content_2 = content_1.update({ data: 2 }, modifier, newVersion, modified); expect(content_2.data).toEqual({ data: 2 }); @@ -36,9 +37,9 @@ describe('ContentDto', () => { expect(content_2.version).toEqual(newVersion); }); - it('should update status property and user info when publishing', () => { - const content_1 = new ContentDto('1', 'Draft', creator, creator, creation, creation, { data: 1 }, version); - const content_2 = content_1.publish(modifier, newVersion, modified); + it('should update status property and user info when changing status', () => { + const content_1 = new ContentDto('1', 'Draft', creator, creator, creation, creation, null, null, null, { data: 1 }, version); + const content_2 = content_1.changeStatus('Published', modifier, newVersion, modified); expect(content_2.status).toEqual('Published'); expect(content_2.lastModified).toEqual(modified); @@ -46,40 +47,23 @@ describe('ContentDto', () => { expect(content_2.version).toEqual(newVersion); }); - it('should update status property and user info when unpublishing', () => { - const content_1 = new ContentDto('1', 'Published', creator, creator, creation, creation, { data: 1 }, version); - const content_2 = content_1.unpublish(modifier, newVersion, modified); - - expect(content_2.status).toEqual('Draft'); - expect(content_2.lastModified).toEqual(modified); - expect(content_2.lastModifiedBy).toEqual(modifier); - expect(content_2.version).toEqual(newVersion); - }); - - it('should update status property and user info when archiving', () => { - const content_1 = new ContentDto('1', 'Draft', creator, creator, creation, creation, { data: 1 }, version); - const content_2 = content_1.archive(modifier, newVersion, modified); - - expect(content_2.status).toEqual('Archived'); - expect(content_2.lastModified).toEqual(modified); - expect(content_2.lastModifiedBy).toEqual(modifier); - expect(content_2.version).toEqual(newVersion); - }); - - it('should update status property and user info when restoring', () => { - const content_1 = new ContentDto('1', 'Archived', creator, creator, creation, creation, { data: 1 }, version); - const content_2 = content_1.restore(modifier, newVersion, modified); + it('should update schedules property and user info when changing status with due time', () => { + const content_1 = new ContentDto('1', 'Draft', creator, creator, creation, creation, null, null, null, { data: 1 }, version); + const content_2 = content_1.changeStatus('Published', modifier, newVersion, modified, dueTime); expect(content_2.status).toEqual('Draft'); expect(content_2.lastModified).toEqual(modified); expect(content_2.lastModifiedBy).toEqual(modifier); + expect(content_2.scheduledAt).toEqual(dueTime); + expect(content_2.scheduledBy).toEqual(modifier); + expect(content_2.scheduledTo).toEqual('Published'); expect(content_2.version).toEqual(newVersion); }); it('should update data property when setting data', () => { const newData = {}; - const content_1 = new ContentDto('1', 'Published', creator, creator, creation, creation, { data: 1 }, version); + const content_1 = new ContentDto('1', 'Published', creator, creator, creation, creation, null, null, null, { data: 1 }, version); const content_2 = content_1.setData(newData); expect(content_2.data).toBe(newData); @@ -130,6 +114,9 @@ describe('ContentsService', () => { createdBy: 'Created1', lastModified: '2017-12-12T10:10', lastModifiedBy: 'LastModifiedBy1', + scheduledTo: 'Draft', + scheduledBy: 'Scheduler1', + scheduledAt: '2018-12-12T10:10', version: 11, data: {} }, @@ -151,11 +138,17 @@ describe('ContentsService', () => { new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1', DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), + 'Draft', + 'Scheduler1', + DateTime.parseISO_UTC('2018-12-12T10:10'), {}, new Version('11')), new ContentDto('id2', 'Published', 'Created2', 'LastModifiedBy2', DateTime.parseISO_UTC('2016-10-12T10:10'), DateTime.parseISO_UTC('2017-10-12T10:10'), + null, + null, + null, {}, new Version('22')) ])); @@ -221,6 +214,9 @@ describe('ContentsService', () => { createdBy: 'Created1', lastModified: '2017-12-12T10:10', lastModifiedBy: 'LastModifiedBy1', + scheduledTo: 'Draft', + scheduledBy: 'Scheduler1', + scheduledAt: '2018-12-12T10:10', data: {} }, { headers: { @@ -232,6 +228,9 @@ describe('ContentsService', () => { new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1', DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), + 'Draft', + 'Scheduler1', + DateTime.parseISO_UTC('2018-12-12T10:10'), {}, new Version('2'))); })); @@ -270,6 +269,9 @@ describe('ContentsService', () => { new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1', DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), + null, + null, + null, {}, new Version('2'))); })); @@ -310,10 +312,10 @@ describe('ContentsService', () => { req.flush({}); })); - it('should make put request to publish content', + it('should make put request to change content status', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - contentsService.publishContent('my-app', 'my-schema', 'content1', version).subscribe(); + contentsService.changeContentStatus('my-app', 'my-schema', 'content1', 'publish', version).subscribe(); const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/publish'); @@ -323,38 +325,14 @@ describe('ContentsService', () => { req.flush({}); })); - it('should make put request to unpublish content', - inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - - contentsService.unpublishContent('my-app', 'my-schema', 'content1', version).subscribe(); - - const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/unpublish'); - - expect(req.request.method).toEqual('PUT'); - expect(req.request.headers.get('If-Match')).toEqual(version.value); - - req.flush({}); - })); - - it('should make put request to archive content', + it('should make put request with due time when status change is scheduled', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - contentsService.archiveContent('my-app', 'my-schema', 'content1', version).subscribe(); - - const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/archive'); - - expect(req.request.method).toEqual('PUT'); - expect(req.request.headers.get('If-Match')).toEqual(version.value); - - req.flush({}); - })); - - it('should make put request to restore content', - inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { + const dueTime = DateTime.parseISO_UTC('2016-12-12T10:10'); - contentsService.restoreContent('my-app', 'my-schema', 'content1', version).subscribe(); + contentsService.changeContentStatus('my-app', 'my-schema', 'content1', 'publish', version, dueTime).subscribe(); - const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/restore'); + const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/publish?dueTime=2016-12-12T10:10:00.000Z'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toEqual(version.value); diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index 553912441..3b87d6874 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -36,6 +36,9 @@ export class ContentDto { public readonly lastModifiedBy: string, public readonly created: DateTime, public readonly lastModified: DateTime, + public readonly scheduledTo: string | null, + public readonly scheduledBy: string | null, + public readonly scheduledAt: DateTime | null, public readonly data: any, public readonly version: Version ) { @@ -49,34 +52,37 @@ export class ContentDto { this.lastModifiedBy, this.created, this.lastModified, + this.scheduledTo, + this.scheduledBy, + this.scheduledAt, data, this.version); } - public publish(user: string, version: Version, now?: DateTime): ContentDto { - return this.changeStatus('Published', user, version, now); - } - - public unpublish(user: string, version: Version, now?: DateTime): ContentDto { - return this.changeStatus('Draft', user, version, now); - } - - public archive(user: string, version: Version, now?: DateTime): ContentDto { - return this.changeStatus('Archived', user, version, now); - } - - public restore(user: string, version: Version, now?: DateTime): ContentDto { - return this.changeStatus('Draft', user, version, now); - } - - private changeStatus(status: string, user: string, version: Version, now?: DateTime): ContentDto { - return new ContentDto( - this.id, - status, - this.createdBy, user, - this.created, now || DateTime.now(), - this.data, - version); + public changeStatus(status: string, user: string, version: Version, now?: DateTime, dueTime: DateTime | null = null): ContentDto { + if (dueTime) { + return new ContentDto( + this.id, + this.status, + this.createdBy, user, + this.created, now || DateTime.now(), + status, + user, + dueTime, + this.data, + version); + } else { + return new ContentDto( + this.id, + status, + this.createdBy, user, + this.created, now || DateTime.now(), + null, + null, + null, + this.data, + version); + } } public update(data: any, user: string, version: Version, now?: DateTime): ContentDto { @@ -85,6 +91,9 @@ export class ContentDto { this.status, this.createdBy, user, this.created, now || DateTime.now(), + this.scheduledTo, + this.scheduledBy, + this.scheduledAt, data, version); } @@ -146,6 +155,9 @@ export class ContentsService { item.lastModifiedBy, DateTime.parseISO_UTC(item.created), DateTime.parseISO_UTC(item.lastModified), + item.scheduledTo || null, + item.scheduledBy || null, + item.scheduledAt ? DateTime.parseISO_UTC(item.scheduledAt) : null, item.data, new Version(item.version.toString())); })); @@ -167,6 +179,9 @@ export class ContentsService { body.lastModifiedBy, DateTime.parseISO_UTC(body.created), DateTime.parseISO_UTC(body.lastModified), + body.scheduledTo || null, + body.scheduledBy || null, + body.scheduledAt || null ? DateTime.parseISO_UTC(body.scheduledAt) : null, body.data, response.version); }) @@ -197,6 +212,9 @@ export class ContentsService { body.lastModifiedBy, DateTime.parseISO_UTC(body.created), DateTime.parseISO_UTC(body.lastModified), + null, + null, + null, body.data, response.version); }) @@ -231,43 +249,17 @@ export class ContentsService { .pretifyError('Failed to delete content. Please reload.'); } - public publishContent(appName: string, schemaName: string, id: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/publish`); - - return HTTP.putVersioned(this.http, url, {}, version) - .do(() => { - this.analytics.trackEvent('Content', 'Published', appName); - }) - .pretifyError('Failed to publish content. Please reload.'); - } - - public unpublishContent(appName: string, schemaName: string, id: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/unpublish`); - - return HTTP.putVersioned(this.http, url, {}, version) - .do(() => { - this.analytics.trackEvent('Content', 'Unpublished', appName); - }) - .pretifyError('Failed to unpublish content. Please reload.'); - } + public changeContentStatus(appName: string, schemaName: string, id: string, action: string, version: Version, dueTime?: DateTime): Observable> { + let url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${action}`); - public archiveContent(appName: string, schemaName: string, id: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/archive`); + if (dueTime) { + url += `?dueTime=${dueTime.toISOString()}`; + } return HTTP.putVersioned(this.http, url, {}, version) .do(() => { this.analytics.trackEvent('Content', 'Archived', appName); }) - .pretifyError('Failed to archive content. Please reload.'); - } - - public restoreContent(appName: string, schemaName: string, id: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/restore`); - - return HTTP.putVersioned(this.http, url, {}, version) - .do(() => { - this.analytics.trackEvent('Content', 'Restored', appName); - }) - .pretifyError('Failed to restore content. Please reload.'); + .pretifyError(`Failed to ${action} content. Please reload.`); } } \ No newline at end of file From 22ed694eb1f72534d6292936084f8ce77c1b71a7 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 10 Feb 2018 11:39:00 +0100 Subject: [PATCH 07/10] UI updated. --- .../Controllers/Content/ContentsController.cs | 4 +- .../Controllers/Content/Models/ContentDto.cs | 6 +- .../pages/content/content-page.component.ts | 4 +- .../contents/contents-page.component.html | 52 +- .../pages/contents/contents-page.component.ts | 142 ++- .../app/features/content/pages/messages.ts | 2 +- .../shared/content-item.component.html | 21 +- .../shared/content-item.component.scss | 31 +- .../content/shared/content-item.component.ts | 4 +- .../events/rule-events-page.component.html | 2 +- .../pages/rules/rule-wizard.component.scss | 4 - .../schema/schema-scripts-form.component.scss | 4 - .../angular/date-time-editor.component.html | 2 +- .../angular/date-time-editor.component.ts | 5 +- .../framework/angular/date-time.pipes.spec.ts | 12 + .../app/framework/angular/date-time.pipes.ts | 10 + .../angular/dialog-renderer.component.scss | 4 - .../angular/onboarding-tooltip.component.html | 1 - .../angular/onboarding-tooltip.component.scss | 4 +- src/Squidex/app/framework/module.ts | 3 + .../shared/services/contents.service.spec.ts | 12 +- .../app/shared/services/contents.service.ts | 6 +- src/Squidex/app/theme/_bootstrap.scss | 6 + src/Squidex/app/theme/_lists.scss | 4 + src/Squidex/app/theme/_panels.scss | 21 +- src/Squidex/app/theme/_vars.scss | 1 + .../app/theme/icomoon/demo-files/demo.css | 4 +- src/Squidex/app/theme/icomoon/demo.html | 372 +++--- .../app/theme/icomoon/fonts/icomoon.eot | Bin 23028 -> 23328 bytes .../app/theme/icomoon/fonts/icomoon.svg | 2 + .../app/theme/icomoon/fonts/icomoon.ttf | Bin 22864 -> 23164 bytes .../app/theme/icomoon/fonts/icomoon.woff | Bin 22940 -> 23240 bytes src/Squidex/app/theme/icomoon/selection.json | 1098 +++++++++-------- src/Squidex/app/theme/icomoon/style.css | 94 +- 34 files changed, 1080 insertions(+), 857 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs index fc81dbc70..6cc59d1b7 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs @@ -290,11 +290,11 @@ namespace Squidex.Areas.Api.Controllers.Contents { Instant? dt = null; - if (string.IsNullOrWhiteSpace(dueTime)) + if (!string.IsNullOrWhiteSpace(dueTime)) { var parseResult = InstantPattern.General.Parse(dueTime); - if (!parseResult.Success) + if (parseResult.Success) { dt = parseResult.Value; } diff --git a/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs index f2c16cedd..2be029606 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs @@ -43,17 +43,17 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models /// /// The scheduled status. /// - public Status? ScheduledTo { get; } + public Status? ScheduledTo { get; set; } /// /// The scheduled date. /// - public Instant? ScheduledAt { get; } + public Instant? ScheduledAt { get; set; } /// /// The user that has scheduled the content. /// - public RefToken ScheduledBy { get; } + public RefToken ScheduledBy { get; set; } /// /// The date and time when the content item has been created. 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 4dd2a16a4..9ba93a9e9 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 @@ -87,10 +87,10 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, this.content = this.content.changeStatus( message.content.scheduledTo || message.content.status, + message.content.scheduledAt, message.content.lastModifiedBy, message.content.version, - message.content.lastModified, - message.content.scheduledAt); + message.content.lastModified); } }); diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index 9589b5b6b..9b610a95d 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -92,25 +92,25 @@
- {{selectionCount}} items selected: + {{selectionCount}} items selected:   - - - - -
- \ No newline at end of file + + + diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts index ec262312c..930c9640d 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts @@ -52,6 +52,12 @@ export class ContentsPageComponent implements OnDestroy, OnInit { public contentsQuery = ''; public contentsPager = new Pager(0); + public dueTimeDialog = new ModalView(); + public dueTime: string | null = ''; + public dueTimeFunction: Function | null; + public dueTimeAction: string | null = ''; + public dueTimeMode = 'Immediately'; + public selectedItems: { [id: string]: boolean; } = {}; public selectionCount = 0; @@ -118,85 +124,90 @@ export class ContentsPageComponent implements OnDestroy, OnInit { } public publishContent(content: ContentDto) { - this.changeContentItem(content, 'publish', 'Published').subscribe(); + this.changeContentItems([content], 'Publish', 'Published', false); } - public publishSelected() { - Observable.forkJoin( - this.contentItems.values - .filter(c => this.selectedItems[c.id]) - .filter(c => c.status !== 'Published') - .map(c => this.changeContentItem(c, 'publish', 'Published'))) - .finally(() => { - this.updateSelectionSummary(); - }) - .subscribe(); + public publishSelected(scheduled: boolean) { + const contents = this.contentItems.filter(c => c.status !== 'Published' && this.selectedItems[c.id]).values; + + this.changeContentItems(contents, 'Publish', 'Published', false); } public unpublishContent(content: ContentDto) { - this.changeContentItem(content, 'unpublish', 'Draft').subscribe(); + this.changeContentItems([content], 'Unpublish', 'Draft', false); } - public unpublishSelected() { - Observable.forkJoin( - this.contentItems.values - .filter(c => this.selectedItems[c.id]) - .filter(c => c.status !== 'Unpublished') - .map(c => this.changeContentItem(c, 'unpublish', 'Draft'))) - .finally(() => { - this.updateSelectionSummary(); - }) - .subscribe(); + public unpublishSelected(scheduled: boolean) { + const contents = this.contentItems.filter(c => c.status === 'Published' && this.selectedItems[c.id]).values; + + this.changeContentItems(contents, 'Unpublish', 'Draft', false); } - private changeContentItem(content: ContentDto, action: string, status: string): Observable { - return this.contentsService.changeContentStatus(this.ctx.appName, this.schema.name, content.id, action, content.version) - .catch(error => { - this.ctx.notifyError(error); + public archiveContent(content: ContentDto) { + this.changeContentItems([content], 'Archive', 'Archived', true); + } - return Observable.throw(error); - }) - .do(dto => { - this.contentItems = this.contentItems.replaceBy('id', content.changeStatus(status, this.ctx.userToken, dto.version)); + public archiveSelected(scheduled: boolean) { + const contents = this.contentItems.filter(c => this.selectedItems[c.id]).values; - this.emitContentStatusChanged(content); - }); + this.changeContentItems(contents, 'Archive', 'Archived', true); } - public archiveSelected() { - Observable.forkJoin( - this.contentItems.values.filter(c => this.selectedItems[c.id]) - .map(c => this.changeContentItem(c, 'archive', 'Archived'))) - .finally(() => { - this.load(); - }) - .subscribe(); + public restoreContent(content: ContentDto) { + this.changeContentItems([content], 'Restore', 'Draft', true); } - public archiveContent(content: ContentDto) { - this.changeContentItem(content, 'archive', 'Archived') - .finally(() => { - this.load(); - }) - .subscribe(); + public restoreSelected(scheduled: boolean) { + const contents = this.contentItems.filter(c => this.selectedItems[c.id]).values; + + this.changeContentItems(contents, 'Restore', 'Draft', true); } - public restoreSelected() { - Observable.forkJoin( - this.contentItems.values.filter(c => this.selectedItems[c.id]) - .map(c => this.changeContentItem(c, 'restore', 'Draft'))) - .finally(() => { - this.load(); - }) - .subscribe(); + private changeContentItems(contents: ContentDto[], action: string, status: string, reload: boolean) { + if (contents.length === 0) { + return; + } + + this.dueTimeFunction = () => { + if (this.dueTime) { + reload = false; + } + Observable.forkJoin( + contents + .map(c => this.changeContentItem(c, action, status, this.dueTime, reload))) + .finally(() => { + if (reload) { + this.load(); + } else { + this.updateSelectionSummary(); + } + }) + .subscribe(); + }; + + this.dueTimeAction = action; + this.dueTimeDialog.show(); } - public restoreContent(content: ContentDto) { - this.changeContentItem(content, 'restore', 'Draft') - .finally(() => { - this.load(); + private changeContentItem(content: ContentDto, action: string, status: string, dueTime: string | null, reload: boolean): Observable { + return this.contentsService.changeContentStatus(this.ctx.appName, this.schema.name, content.id, action, dueTime, content.version) + .catch(error => { + this.ctx.notifyError(error); + + return Observable.throw(error); }) - .subscribe(); + .do(dto => { + if (!reload) { + const dt = + dueTime ? + DateTime.parseISO_UTC(dueTime) : + null; + + this.contentItems = this.contentItems.replaceBy('id', content.changeStatus(status, dt, this.ctx.userToken, dto.version)); + + this.emitContentStatusChanged(content); + } + }); } public deleteSelected(content: ContentDto) { @@ -357,5 +368,18 @@ export class ContentsPageComponent implements OnDestroy, OnInit { this.contentFields = [{}]; } } + + public confirmStatusChange() { + this.dueTimeFunction!(); + + this.cancelStatusChange(); + } + + public cancelStatusChange() { + this.dueTimeMode = 'Immediately'; + this.dueTimeDialog.hide(); + this.dueTimeFunction = null; + this.dueTime = null; + } } diff --git a/src/Squidex/app/features/content/pages/messages.ts b/src/Squidex/app/features/content/pages/messages.ts index ca5a19fa4..112827d57 100644 --- a/src/Squidex/app/features/content/pages/messages.ts +++ b/src/Squidex/app/features/content/pages/messages.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ContentDto, DateTime } from 'shared'; +import { ContentDto } from 'shared'; export class ContentCreated { constructor( 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 e3bb20cdf..bfff66cfc 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.html +++ b/src/Squidex/app/features/content/shared/content-item.component.html @@ -10,7 +10,26 @@ - + + + + + +
+ {{content.status}} +
+
+ + + + + + +
+ Will be set {{content.scheduledTo}} at {{content.scheduledAt | sqxFullDateTime}} +
+
+ {{content.lastModified | sqxFromNow}} diff --git a/src/Squidex/app/features/content/shared/content-item.component.scss b/src/Squidex/app/features/content/shared/content-item.component.scss index 727784a9c..8b360453e 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.scss +++ b/src/Squidex/app/features/content/shared/content-item.component.scss @@ -1,22 +1,31 @@ @import '_vars'; @import '_mixins'; -.content { +.content-status { & { + vertical-align: middle; cursor: pointer; } &-published { - & { - @include circle(.5rem); - display: inline-block; - border: 0; - background: $color-theme-green; - margin-left: .4rem; - } + color: $color-theme-green; + } + + &-draft { + color: $color-text-decent; + } + + &-archived { + color: $color-theme-error; + } - &.unpublished { - background: $color-theme-error; - } + &-tooltip { + @include border-radius; + background: $color-tooltip; + border: 0; + font-size: .9rem; + font-weight: normal; + color: $color-dark-foreground; + padding: .75rem; } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/content-item.component.ts b/src/Squidex/app/features/content/shared/content-item.component.ts index 7ba32c2f3..715f445d9 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.ts +++ b/src/Squidex/app/features/content/shared/content-item.component.ts @@ -46,7 +46,7 @@ export class ContentItemComponent implements OnInit, OnChanges { public deleting = new EventEmitter(); @Output() - public selectedChange = new EventEmitter(); + public selectedChange = new EventEmitter(); @Input() public selected = false; @@ -74,6 +74,8 @@ export class ContentItemComponent implements OnInit, OnChanges { public dropdown = new ModalView(false, true); + public scheduleTooltip = new ModalView(false, true); + public values: any[] = []; constructor(public readonly ctx: AppContext diff --git a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html index b0cf8866b..7af9fc272 100644 --- a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html +++ b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html @@ -77,7 +77,7 @@ Attempts: {{event.numCalls}}
- Next: {{event.nextAttempt.toStringFormat('MMM DD h:mm:ss a')}} + Next: {{event.nextAttempt | sqxFromNow}}
-
+
diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.ts b/src/Squidex/app/framework/angular/date-time-editor.component.ts index 3ee9fc078..ad6103174 100644 --- a/src/Squidex/app/framework/angular/date-time-editor.component.ts +++ b/src/Squidex/app/framework/angular/date-time-editor.component.ts @@ -40,6 +40,9 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy, @Input() public enforceTime: boolean; + @Input() + public hideClear: boolean; + public timeControl = new FormControl(); public dateControl = new FormControl(); @@ -169,7 +172,7 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy, let result: string | null = null; if ((this.dateValue && !this.dateValue.isValid()) || (this.timeValue && !this.timeValue.isValid())) { - result = 'Invalid DateTime'; + result = null; } else if (!this.dateValue && !this.timeValue) { result = null; } else { diff --git a/src/Squidex/app/framework/angular/date-time.pipes.spec.ts b/src/Squidex/app/framework/angular/date-time.pipes.spec.ts index f738a3d35..c01bb8fec 100644 --- a/src/Squidex/app/framework/angular/date-time.pipes.spec.ts +++ b/src/Squidex/app/framework/angular/date-time.pipes.spec.ts @@ -13,6 +13,7 @@ import { DayPipe, DurationPipe, FromNowPipe, + FullDateTimePipe, MonthPipe, ShortDatePipe, ShortTimePipe @@ -33,6 +34,17 @@ describe('DurationPipe', () => { }); }); +describe('FullDateTimePipe', () => { + it('should format to nice string', () => { + const pipe = new FullDateTimePipe(); + + const actual = pipe.transform(dateTime); + const expected = 'Thursday, October 3, 2013 12:13 PM'; + + expect(actual).toBe(expected); + }); +}); + describe('DayPipe', () => { it('should format to day numbers', () => { const pipe = new DayPipe(); diff --git a/src/Squidex/app/framework/angular/date-time.pipes.ts b/src/Squidex/app/framework/angular/date-time.pipes.ts index 8c0d9d9e6..754f278ad 100644 --- a/src/Squidex/app/framework/angular/date-time.pipes.ts +++ b/src/Squidex/app/framework/angular/date-time.pipes.ts @@ -80,6 +80,16 @@ export class ShortTimePipe implements PipeTransform { } } +@Pipe({ + name: 'sqxFullDateTime', + pure: true +}) +export class FullDateTimePipe implements PipeTransform { + public transform(value: DateTime): any { + return value.toStringFormat('LLLL'); + } +} + @Pipe({ name: 'sqxDuration', pure: true diff --git a/src/Squidex/app/framework/angular/dialog-renderer.component.scss b/src/Squidex/app/framework/angular/dialog-renderer.component.scss index a990f3756..f86958242 100644 --- a/src/Squidex/app/framework/angular/dialog-renderer.component.scss +++ b/src/Squidex/app/framework/angular/dialog-renderer.component.scss @@ -29,8 +29,4 @@ &-bottomleft { @include fixed(auto, auto, 0, 0); } -} - -.clearfix { - width: 100%; } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/onboarding-tooltip.component.html b/src/Squidex/app/framework/angular/onboarding-tooltip.component.html index 4de8bb7f8..bd217d082 100644 --- a/src/Squidex/app/framework/angular/onboarding-tooltip.component.html +++ b/src/Squidex/app/framework/angular/onboarding-tooltip.component.html @@ -1,5 +1,4 @@
-
diff --git a/src/Squidex/app/framework/angular/onboarding-tooltip.component.scss b/src/Squidex/app/framework/angular/onboarding-tooltip.component.scss index 33430c7e2..2b991d2d7 100644 --- a/src/Squidex/app/framework/angular/onboarding-tooltip.component.scss +++ b/src/Squidex/app/framework/angular/onboarding-tooltip.component.scss @@ -1,12 +1,10 @@ @import '_mixins'; @import '_vars'; -$color: #1a2129; - .onboarding { &-help { @include border-radius; - background: $color; + background: $color-tooltip; border: 0; max-width: 20rem; padding: .75rem; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 45b1b528e..9a4f4b39f 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -32,6 +32,7 @@ import { FileSizePipe, FocusOnInitDirective, FromNowPipe, + FullDateTimePipe, IgnoreScrollbarDirective, ImageSourceDirective, IndeterminateValueDirective, @@ -98,6 +99,7 @@ import { FileSizePipe, FocusOnInitDirective, FromNowPipe, + FullDateTimePipe, IgnoreScrollbarDirective, ImageSourceDirective, IndeterminateValueDirective, @@ -148,6 +150,7 @@ import { FileSizePipe, FocusOnInitDirective, FromNowPipe, + FullDateTimePipe, IgnoreScrollbarDirective, ImageSourceDirective, IndeterminateValueDirective, diff --git a/src/Squidex/app/shared/services/contents.service.spec.ts b/src/Squidex/app/shared/services/contents.service.spec.ts index af7b1dc45..7f6637a95 100644 --- a/src/Squidex/app/shared/services/contents.service.spec.ts +++ b/src/Squidex/app/shared/services/contents.service.spec.ts @@ -39,7 +39,7 @@ describe('ContentDto', () => { it('should update status property and user info when changing status', () => { const content_1 = new ContentDto('1', 'Draft', creator, creator, creation, creation, null, null, null, { data: 1 }, version); - const content_2 = content_1.changeStatus('Published', modifier, newVersion, modified); + const content_2 = content_1.changeStatus('Published', null, modifier, newVersion, modified); expect(content_2.status).toEqual('Published'); expect(content_2.lastModified).toEqual(modified); @@ -49,7 +49,7 @@ describe('ContentDto', () => { it('should update schedules property and user info when changing status with due time', () => { const content_1 = new ContentDto('1', 'Draft', creator, creator, creation, creation, null, null, null, { data: 1 }, version); - const content_2 = content_1.changeStatus('Published', modifier, newVersion, modified, dueTime); + const content_2 = content_1.changeStatus('Published', dueTime, modifier, newVersion, modified); expect(content_2.status).toEqual('Draft'); expect(content_2.lastModified).toEqual(modified); @@ -315,7 +315,7 @@ describe('ContentsService', () => { it('should make put request to change content status', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - contentsService.changeContentStatus('my-app', 'my-schema', 'content1', 'publish', version).subscribe(); + contentsService.changeContentStatus('my-app', 'my-schema', 'content1', 'publish', null, version).subscribe(); const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/publish'); @@ -328,11 +328,11 @@ describe('ContentsService', () => { it('should make put request with due time when status change is scheduled', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - const dueTime = DateTime.parseISO_UTC('2016-12-12T10:10'); + const dueTime = '2016-12-12T10:10:00'; - contentsService.changeContentStatus('my-app', 'my-schema', 'content1', 'publish', version, dueTime).subscribe(); + contentsService.changeContentStatus('my-app', 'my-schema', 'content1', 'publish', dueTime, version).subscribe(); - const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/publish?dueTime=2016-12-12T10:10:00.000Z'); + const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/publish?dueTime=2016-12-12T10:10:00'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toEqual(version.value); diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index 3b87d6874..966c90414 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -59,7 +59,7 @@ export class ContentDto { this.version); } - public changeStatus(status: string, user: string, version: Version, now?: DateTime, dueTime: DateTime | null = null): ContentDto { + public changeStatus(status: string, dueTime: DateTime | null, user: string, version: Version, now?: DateTime): ContentDto { if (dueTime) { return new ContentDto( this.id, @@ -249,11 +249,11 @@ export class ContentsService { .pretifyError('Failed to delete content. Please reload.'); } - public changeContentStatus(appName: string, schemaName: string, id: string, action: string, version: Version, dueTime?: DateTime): Observable> { + public changeContentStatus(appName: string, schemaName: string, id: string, action: string, dueTime: string | null, version: Version): Observable> { let url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${action}`); if (dueTime) { - url += `?dueTime=${dueTime.toISOString()}`; + url += `?dueTime=${dueTime}`; } return HTTP.putVersioned(this.http, url, {}, version) diff --git a/src/Squidex/app/theme/_bootstrap.scss b/src/Squidex/app/theme/_bootstrap.scss index c22e92ab3..b7993ea66 100644 --- a/src/Squidex/app/theme/_bootstrap.scss +++ b/src/Squidex/app/theme/_bootstrap.scss @@ -458,6 +458,12 @@ a { margin-top: 4.5rem; } } + + &-footer { + .clearfix { + width: 100%; + } + } } // diff --git a/src/Squidex/app/theme/_lists.scss b/src/Squidex/app/theme/_lists.scss index 717491867..754191386 100644 --- a/src/Squidex/app/theme/_lists.scss +++ b/src/Squidex/app/theme/_lists.scss @@ -27,6 +27,10 @@ } } + td { + border-top: 0; + } + thead { // Small font size for the table header, content is more important! th { diff --git a/src/Squidex/app/theme/_panels.scss b/src/Squidex/app/theme/_panels.scss index 33bb19aa0..b1b0b88d3 100644 --- a/src/Squidex/app/theme/_panels.scss +++ b/src/Squidex/app/theme/_panels.scss @@ -289,15 +289,24 @@ .grid-content { @include flex-grow(1); - margin: 0; - margin-top: .25rem; + padding-top: .5rem; + padding-bottom: .5rem; overflow-y: scroll; } .grid-header { - padding-top: .75 * $panel-padding; - border: 0; - border-bottom: 2px solid $color-border; + & { + border: 0; + border-bottom: 2px solid $color-border; + } + + th { + padding: .7rem; + } + + .table-items { + margin: 0; + } } .grid-footer { @@ -305,6 +314,6 @@ } .pagination { - margin-top: .25rem; + margin: .25rem 0; } } \ No newline at end of file diff --git a/src/Squidex/app/theme/_vars.scss b/src/Squidex/app/theme/_vars.scss index 9dd26ff4b..a4862968b 100644 --- a/src/Squidex/app/theme/_vars.scss +++ b/src/Squidex/app/theme/_vars.scss @@ -6,6 +6,7 @@ $color-border-dark: #b3bbbf; $color-title: #000; $color-text: #373a3c; $color-text-decent: #a9b2bb; +$color-tooltip: #1a2129; $color-input: #dbe4eb; $color-input-background: #fff; diff --git a/src/Squidex/app/theme/icomoon/demo-files/demo.css b/src/Squidex/app/theme/icomoon/demo-files/demo.css index 38755ccf3..1c4674f67 100644 --- a/src/Squidex/app/theme/icomoon/demo-files/demo.css +++ b/src/Squidex/app/theme/icomoon/demo-files/demo.css @@ -150,10 +150,10 @@ p { font-size: 32px; } .fs2 { - font-size: 28px; + font-size: 32px; } .fs3 { - font-size: 32px; + font-size: 28px; } .fs4 { font-size: 32px; diff --git a/src/Squidex/app/theme/icomoon/demo.html b/src/Squidex/app/theme/icomoon/demo.html index 78d96da2d..810b7602e 100644 --- a/src/Squidex/app/theme/icomoon/demo.html +++ b/src/Squidex/app/theme/icomoon/demo.html @@ -9,10 +9,26 @@
-

Font Name: icomoon (Glyphs: 83)

+

Font Name: icomoon (Glyphs: 85)

Grid Size: Unknown

+
+
+ + + + icon-circle +
+
+ + +
+
+ liga: + +
+
@@ -847,17 +863,17 @@
-

Grid Size: 14

+

Grid Size: 16

- + - icon-action-Slack + icon-clock
- - + +
liga: @@ -866,14 +882,14 @@
- + - icon-orleans + icon-bin2
- - + +
liga: @@ -882,30 +898,30 @@
- + - icon-document-lock + icon-earth
- - + +
liga: - +
- + - icon-document-unpublish + icon-elapsed
- - + +
liga: @@ -914,14 +930,14 @@
- + - icon-angle-down + icon-google
- - + +
liga: @@ -930,14 +946,14 @@
- + - icon-angle-left + icon-lock
- - + +
liga: @@ -946,14 +962,14 @@
- + - icon-angle-right + icon-microsoft
- - + +
liga: @@ -962,14 +978,14 @@
- + - icon-angle-up + icon-action-AzureQueue
- - + +
liga: @@ -978,14 +994,14 @@
- + - icon-api + icon-pause
- - + +
liga: @@ -994,14 +1010,14 @@
- + - icon-assets + icon-play
- - + +
liga: @@ -1010,14 +1026,14 @@
- + - icon-bug + icon-reset
- - + +
liga: @@ -1026,14 +1042,14 @@
- + - icon-caret-down + icon-settings2
- - + +
liga: @@ -1042,14 +1058,14 @@
- + - icon-caret-left + icon-timeout
- - + +
liga: @@ -1058,209 +1074,209 @@
- + - icon-caret-right + icon-unlocked
- - + +
liga:
-
+
+
+

Grid Size: 14

+
- + - icon-caret-up + icon-action-Slack
- - + +
liga:
-
+
- + - icon-contents + icon-orleans
- - + +
liga:
-
+
- + - icon-trigger-ContentChanged + icon-document-lock
- - + +
liga:
-
+
- + - icon-control-Date + icon-document-unpublish
- - + +
liga:
-
+
- + - icon-control-DateTime + icon-angle-down
- - + +
liga:
-
+
- + - icon-control-Markdown + icon-angle-left
- - + +
liga:
-
+
- + - icon-grid + icon-angle-right
- - + +
liga:
-
+
- + - icon-list + icon-angle-up
- - + +
liga:
-
+
- + - icon-user-o + icon-api
- - + +
liga:
-
+
- + - icon-rules + icon-assets
- - + +
liga:
-
+
- + - icon-action-Webhook + icon-bug
- - + +
liga:
-
-
-

Grid Size: 16

- + - icon-bin2 + icon-caret-down
- - + +
liga: @@ -1269,30 +1285,30 @@
- + - icon-earth + icon-caret-left
- - + +
liga: - +
- + - icon-elapsed + icon-caret-right
- - + +
liga: @@ -1301,14 +1317,14 @@
- + - icon-google + icon-caret-up
- - + +
liga: @@ -1317,14 +1333,14 @@
- + - icon-lock + icon-contents
- - + +
liga: @@ -1333,14 +1349,14 @@
- + - icon-microsoft + icon-trigger-ContentChanged
- - + +
liga: @@ -1349,14 +1365,14 @@
- + - icon-action-AzureQueue + icon-control-Date
- - + +
liga: @@ -1365,14 +1381,14 @@
- + - icon-pause + icon-control-DateTime
- - + +
liga: @@ -1381,14 +1397,14 @@
- + - icon-play + icon-control-Markdown
- - + +
liga: @@ -1397,14 +1413,14 @@
- + - icon-reset + icon-grid
- - + +
liga: @@ -1413,14 +1429,14 @@
- + - icon-settings2 + icon-list
- - + +
liga: @@ -1429,14 +1445,14 @@
- + - icon-timeout + icon-user-o
- - + +
liga: @@ -1445,14 +1461,30 @@
- + - icon-unlocked + icon-rules
- - + + +
+
+ liga: + +
+
+
+
+ + + + icon-action-Webhook +
+
+ +
liga: diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot index c3b9462c50274c8fdad142ce201e6e366f870ec5..e8abe5415815252076e027ac437dcffc7cd7d46c 100644 GIT binary patch delta 431 zcmeyenQ_53MmB|L28NobiEL)f{d+_v+E)nfVqjp{1H=i*xrqh0b#9%WctWOL*7u+t z0|Vm(28NIg8L5dWg3H+UGcbfX0M(gg00lUiSfhaa3Lsx4Be$f&mfXaO0)_%cp!$d{K)ym=Vs7e8FV5dU{uQA5wu1cP5(Z|F(x@1a zJOeW` zU`Rui0^J6;P8!G&VdrCHXH%YB;3vYw!T{7O!Nhm~XpJ}{!{)M}aAs+dJ^k_gHeVUI zSwNm)xVHGAJdB?FBGjBQaXK8k;mDv_!NbKz1-hGDa|nfs`^ZO@0_D KyxBiuDI)*{O?B=7 delta 260 zcmZ3mjq%H7Mz$}J3=9F06WPp|T^(0Vw674{$-uy{2Z$4ra}x`0Yu!3J@q|phUyMf+ z0|Vm(28Q4X8L5dWf|FjSGcbfI0M(gg00lUiSR;UZAZAd>$StXG_$T-r$gctFiOI=N zPV|nAyTrf{b^|ECAvdw2fFX}Dih&`#1ISm%OUzB3>BadQ$X@|e-&T-cT*AN%lwpW8 z0Le2jGcz8WoWj`6=s)=bqcUrkfH!~dW(lUmfXy?5LztykIl9L4+k9o1A&1tdzo diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg index e3075362d..313a70455 100644 --- a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg +++ b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg @@ -87,6 +87,8 @@ + + diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf index d71543c96b753da153ff1f74125fe0119087cd34..d4f4fe27a0e8fc54a1f482f3a888412d5fbe5b4e 100644 GIT binary patch delta 458 zcmcbxiSf@C#(D-u1_lOhh6V;^1_S?KeItG$-d#YEJwTk0oSRs1Tj$nk1_nkMAU`KP zvA6(83jp~YK$;^xr!q~}_n;k+KY@WEWJ5-3Vv680w*3qYVGclfvkagBClhNFkY54h zt7PPsRCxRod=BK#0qRM~$xn7nEScX?!oUy#lxNtIn^;l6P{0V(5U~ZwSIA4uO`Yk* z`5VZ;0@Tn}kY8K^bSMx+#en1)n3<1HJkZS;IN66$nYBm2m%o2=31eb_1W?WY11t>8 z2N+lx6d7z87*y2-jYY+oC$IOFo*d>Z!^SlEa>LrUlOM8)D`W+@x@F1%L8hB)022H1 zB}VC|>zF1_-W?=q(10NgRSI+#+%9P#M}(b^k)2I>@}gStyhU#A3qQmN0MlxE8~^|S delta 285 zcmeyfh4I2B#(D-u1_lOhh6V;^1_S?KeItG$-km^^JwTk0oSRs1TkFvRT&Pz9j8Sq4ymlZiC~$OmEu zm5khy3WtA!&w>0Jpq`kV{A9;O@7TCY3=Cm6fC@I`CRP+MQnO`Yk* z`5VYz0o2e|kY8K^bSMx+8i3>(n3<1FJkZVPKiP*-nYBy6o4@uI=4=z zCl-Umt^oNQP%Mz1Q<(-7yTiZ`vH^r;eGl4Yq$Z{?Fog90)tG^>;4-%T89+gx*byLK z1%x@7Sfes>ODce3FM#@i>KRi!{s}(M$xjBV^Na8SD%t|VCG$H0LU16j<>$0zeJb~6S}p24Wh+9Tl0-@o|) zV`6{=Q1br)76#@646F=_47Lmms_KHqqTE;@M#J+rqQTpjRrpX!_-Epy5S+Q~58bE+x|7pO`0@V(5Gu(b@AV-9qkCB~C zd9tI0hztt@vKh#1#seVdFfweG3kqkJ7TMDu&u{aUftv;BPX>l-iyz9v=*bg8%^4#% gp9qa$oGcN(oxe$AlV*gLh*ktR#v?c13O~dM0NIa*Bme*a delta 341 zcmX@Hm2u8yMv-!VH#Y`G1|W!>!@v!u114{b5}&9oQol1fH?e?$fiVLpoCCtQwQe0v zPb>zBT>cmfFf#dtJjq$Z{?Foc5inSrq2q}S;gKtZ6`5+Gj% zggKd5BQkPJDu7}qfcj%V*x{ex^PK!-pgO;BkfIGB>>V3-DL1hKXmL2mVg)eHV~om6 z%uNM~eF1801L2uooWBe5i%WpMse!9w2C|r$k4@%b>}K?zJcCi0wM)R8zjyNi#>9Zl zj=>?!(yJU@ Date: Sat, 10 Feb 2018 12:55:43 +0100 Subject: [PATCH 08/10] UI improved. --- .../contents/contents-page.component.html | 8 +-- .../shared/content-item.component.html | 15 ++--- .../shared/content-item.component.scss | 1 - .../content/shared/content-item.component.ts | 7 +- .../schemas/pages/schema/field.component.html | 2 +- .../pages/schema/schema-page.component.html | 2 +- .../angular/control-errors.component.ts | 5 +- .../angular/modal-target.directive.ts | 4 ++ .../angular/onboarding-tooltip.component.html | 2 +- .../framework/angular/tooltip.component.scss | 12 ++++ .../framework/angular/tooltip.component.ts | 64 +++++++++++++++++++ .../angular/user-report.component.ts | 6 +- src/Squidex/app/framework/declarations.ts | 1 + src/Squidex/app/framework/module.ts | 3 + .../language-selector.component.html | 2 +- .../pages/internal/apps-menu.component.html | 2 +- .../internal/profile-menu.component.html | 2 +- 17 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 src/Squidex/app/framework/angular/tooltip.component.scss create mode 100644 src/Squidex/app/framework/angular/tooltip.component.ts diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index 9b610a95d..f00ddc40a 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -28,7 +28,7 @@ Search for content using full text search over all fields and languages! -