Browse Source

Merge pull request #247 from Squidex/command-cleanup

Command cleanup & scheduled publishing
pull/248/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
cc550e0954
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs
  2. 15
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  3. 1
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  4. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  5. 24
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  6. 24
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  7. 10
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  8. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  9. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs
  10. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs
  11. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs
  12. 4
      src/Squidex.Domain.Apps.Entities/AppProvider.cs
  13. 3
      src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  14. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs
  15. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs
  16. 8
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs
  17. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs
  18. 9
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs
  19. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs
  20. 9
      src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs
  21. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs
  22. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs
  23. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs
  24. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs
  25. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs
  26. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs
  27. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs
  28. 191
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs
  29. 11
      src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs
  30. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs
  31. 5
      src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs
  32. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs
  33. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs
  34. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs
  35. 5
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs
  36. 8
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  37. 3
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs
  38. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs
  39. 7
      src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs
  40. 7
      src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  41. 25
      src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs
  42. 14
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  43. 17
      src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  44. 57
      src/Squidex.Domain.Apps.Entities/Contents/ContentScheduler.cs
  45. 44
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs
  46. 7
      src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  47. 14
      src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs
  48. 3
      src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  49. 35
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  50. 9
      src/Squidex.Domain.Apps.Entities/EntityMapper.cs
  51. 7
      src/Squidex.Domain.Apps.Entities/IAppCommand.cs
  52. 2
      src/Squidex.Domain.Apps.Entities/IAppProvider.cs
  53. 7
      src/Squidex.Domain.Apps.Entities/ISchemaCommand.cs
  54. 5
      src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs
  55. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs
  56. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs
  57. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs
  58. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs
  59. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs
  60. 5
      src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs
  61. 5
      src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs
  62. 2
      src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs
  63. 11
      src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs
  64. 9
      src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs
  65. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs
  66. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs
  67. 15
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs
  68. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs
  69. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs
  70. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs
  71. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs
  72. 6
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs
  73. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs
  74. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs
  75. 5
      src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs
  76. 16
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs
  77. 12
      src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs
  78. 13
      src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs
  79. 35
      src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs
  80. 15
      src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs
  81. 1
      src/Squidex/Config/Authentication/MicrosoftHandler.cs
  82. 2
      src/Squidex/Config/Domain/ReadServices.cs
  83. 4
      src/Squidex/Config/Domain/SerializationServices.cs
  84. 3
      src/Squidex/Config/Domain/WriteServices.cs
  85. 5
      src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs
  86. 5
      src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs
  87. 7
      src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs
  88. 33
      src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs
  89. 35
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  90. 54
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  91. 174
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  92. 9
      src/Squidex/app/features/content/pages/messages.ts
  93. 18
      src/Squidex/app/features/content/shared/content-item.component.html
  94. 32
      src/Squidex/app/features/content/shared/content-item.component.scss
  95. 7
      src/Squidex/app/features/content/shared/content-item.component.ts
  96. 2
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.html
  97. 4
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.scss
  98. 2
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  99. 2
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  100. 4
      src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.scss

3
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs

@ -8,14 +8,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;

15
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs

@ -20,9 +20,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
IAssetEntity, IAssetEntity,
IUpdateableEntityWithVersion, IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy, IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy, IUpdateableEntityWithLastModifiedBy
IUpdateableEntityWithAppRef
{ {
[BsonRequired]
[BsonElement]
public Guid AppIdId { get; set; }
[BsonRequired]
[BsonElement]
public NamedId<Guid> AppId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public string MimeType { get; set; } public string MimeType { get; set; }
@ -55,10 +62,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonElement] [BsonElement]
public int? PixelHeight { get; set; } public int? PixelHeight { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public RefToken CreatedBy { get; set; } public RefToken CreatedBy { get; set; }

1
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()); var entity = SimpleMapper.Map(value, new MongoAssetEntity());
entity.Version = newVersion; entity.Version = newVersion;
entity.AppIdId = value.AppId.Id;
await Collection.ReplaceOneAsync(x => x.Id == key && x.Version == oldVersion, entity, Upsert); await Collection.ReplaceOneAsync(x => x.Id == key && x.Version == oldVersion, entity, Upsert);
} }

2
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<FilterDefinition<MongoAssetEntity>> var filters = new List<FilterDefinition<MongoAssetEntity>>
{ {
Filter.Eq(x => x.AppId, appId), Filter.Eq(x => x.AppIdId, appId),
Filter.Eq(x => x.IsDeleted, false) Filter.Eq(x => x.IsDeleted, false)
}; };

24
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -35,12 +35,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonRequired] [BsonRequired]
[BsonElement("ai")] [BsonElement("ai")]
[BsonRepresentation(BsonType.String)] [BsonRepresentation(BsonType.String)]
public Guid AppId { get; set; } public Guid AppIdId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("si")] [BsonElement("si")]
[BsonRepresentation(BsonType.String)] [BsonRepresentation(BsonType.String)]
public Guid SchemaId { get; set; } public Guid SchemaIdId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("rf")] [BsonElement("rf")]
@ -62,6 +62,26 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonJson] [BsonJson]
public IdContentData DataByIds { get; set; } public IdContentData DataByIds { get; set; }
[BsonRequired]
[BsonElement("ai2")]
public NamedId<Guid> AppId { get; set; }
[BsonRequired]
[BsonElement("si2")]
public NamedId<Guid> SchemaId { get; set; }
[BsonIgnoreIfNull]
[BsonElement("sdt")]
public Status? ScheduledTo { get; set; }
[BsonIgnoreIfNull]
[BsonElement("sda")]
public Instant? ScheduledAt { get; set; }
[BsonIgnoreIfNull]
[BsonElement("sdb")]
public RefToken ScheduledBy { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("ct")] [BsonElement("ct")]
public Instant Created { get; set; } public Instant Created { get; set; }

24
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.OData.UriParser; using Microsoft.OData.UriParser;
using MongoDB.Driver; using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
@ -51,6 +52,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
await collection.Indexes.TryDropOneAsync("si_1_st_1_dl_1_dt_text"); 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( await archiveCollection.Indexes.CreateOneAsync(
Index Index
.Ascending(x => x.Id) .Ascending(x => x.Id)
@ -59,13 +64,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await collection.Indexes.CreateOneAsync( await collection.Indexes.CreateOneAsync(
Index Index
.Text(x => x.DataText) .Text(x => x.DataText)
.Ascending(x => x.SchemaId) .Ascending(x => x.SchemaIdId)
.Ascending(x => x.Status) .Ascending(x => x.Status)
.Ascending(x => x.IsDeleted)); .Ascending(x => x.IsDeleted));
await collection.Indexes.CreateOneAsync( await collection.Indexes.CreateOneAsync(
Index Index
.Ascending(x => x.SchemaId) .Ascending(x => x.SchemaIdId)
.Ascending(x => x.Id) .Ascending(x => x.Id)
.Ascending(x => x.IsDeleted) .Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)); .Ascending(x => x.Status));
@ -121,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> 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.SchemaIdId == schema.Id && ids.Contains(x.Id) && x.IsDeleted == false && status.Contains(x.Status));
var contentItems = find.ToListAsync(); var contentItems = find.ToListAsync();
var contentCount = find.CountAsync(); var contentCount = find.CountAsync();
@ -139,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids) public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids)
{ {
var contentEntities = 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.SchemaIdId == schemaId && ids.Contains(x.Id) && x.IsDeleted == false).Only(x => x.Id)
.ToListAsync(); .ToListAsync();
return ids.Except(contentEntities.Select(x => Guid.Parse(x["id"].AsString))).ToList(); return ids.Except(contentEntities.Select(x => Guid.Parse(x["id"].AsString))).ToList();
@ -159,7 +164,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id)
{ {
var contentEntity = var contentEntity =
await Collection.Find(x => x.SchemaId == schema.Id && x.Id == id && x.IsDeleted == false) await Collection.Find(x => x.SchemaIdId == schema.Id && x.Id == id && x.IsDeleted == false)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef); contentEntity?.ParseData(schema.SchemaDef);
@ -167,6 +172,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return contentEntity; return contentEntity;
} }
public Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback)
{
return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted == false)
.ForEachAsync(c =>
{
callback(c);
});
}
public override async Task ClearAsync() public override async Task ClearAsync()
{ {
await Database.DropCollectionAsync("States_Contents_Archive"); await Database.DropCollectionAsync("States_Contents_Archive");

10
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
if (contentEntity != null) if (contentEntity != null)
{ {
var schema = await GetSchemaAsync(contentEntity.AppId, contentEntity.SchemaId); var schema = await GetSchemaAsync(contentEntity.AppIdId, contentEntity.SchemaIdId);
contentEntity?.ParseData(schema.SchemaDef); 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) public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion)
{ {
if (value.SchemaId == Guid.Empty) if (value.SchemaId.Id == Guid.Empty)
{ {
return; return;
} }
var schema = await GetSchemaAsync(value.AppId, value.SchemaId); var schema = await GetSchemaAsync(value.AppId.Id, value.SchemaId.Id);
var idData = value.Data?.ToIdModel(schema.SchemaDef, true); 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 var document = SimpleMapper.Map(value, new MongoContentEntity
{ {
AppIdId = value.AppId.Id,
SchemaIdId = value.SchemaId.Id,
IsDeleted = value.IsDeleted, IsDeleted = value.IsDeleted,
DocumentId = key.ToString(), DocumentId = key.ToString(),
DataText = idData?.ToFullText(), DataText = idData?.ToFullText(),
@ -92,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid schemaId) private async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid schemaId)
{ {
var schema = await appProvider.GetSchemaAsync(appId, schemaId); var schema = await appProvider.GetSchemaAsync(appId, schemaId, true);
if (schema == null) if (schema == null)
{ {

2
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<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
Filter.Eq(x => x.SchemaId, schemaId), Filter.Eq(x => x.SchemaIdId, schemaId),
Filter.In(x => x.Status, status), Filter.In(x => x.Status, status),
Filter.Eq(x => x.IsDeleted, false) Filter.Eq(x => x.IsDeleted, false)
}; };

4
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, public sealed class MongoHistoryEventEntity : MongoEntity,
IEntity, IEntity,
IEntityWithAppRef,
IUpdateableEntity, IUpdateableEntity,
IUpdateableEntityWithVersion, IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy, IUpdateableEntityWithCreatedBy
IUpdateableEntityWithAppRef
{ {
[BsonElement] [BsonElement]
[BsonRequired] [BsonRequired]

2
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 return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.State, value) .Set(x => x.State, value)
.Set(x => x.AppId, value.AppId) .Set(x => x.AppId, value.AppId.Id)
.Set(x => x.IsDeleted, value.IsDeleted)); .Set(x => x.IsDeleted, value.IsDeleted));
} }
} }

2
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 return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.State, value) .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.Name, value.Name)
.Set(x => x.IsDeleted, value.IsDeleted)); .Set(x => x.IsDeleted, value.IsDeleted));
} }

4
src/Squidex.Domain.Apps.Entities/AppProvider.cs

@ -88,11 +88,11 @@ namespace Squidex.Domain.Apps.Entities
return (await stateFactory.GetSingleAsync<SchemaDomainObject>(schemaId)).Snapshot; return (await stateFactory.GetSingleAsync<SchemaDomainObject>(schemaId)).Snapshot;
} }
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id) public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false)
{ {
var schema = await stateFactory.GetSingleAsync<SchemaDomainObject>(id); var schema = await stateFactory.GetSingleAsync<SchemaDomainObject>(id);
if (!IsFound(schema) || schema.Snapshot.IsDeleted || schema.Snapshot.AppId != appId) if (!IsFound(schema) || (schema.Snapshot.IsDeleted && !allowDeleted) || schema.Snapshot.AppId.Id != appId)
{ {
return null; return null;
} }

3
src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Guards; using Squidex.Domain.Apps.Entities.Apps.Guards;
@ -180,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
} }
else 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) if (result is PlanChangedResult)
{ {

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs

@ -9,7 +9,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AddLanguage : AppAggregateCommand public sealed class AddLanguage : AppCommand
{ {
public Language Language { get; set; } public Language Language { get; set; }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs

@ -9,7 +9,7 @@ using System;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AddPattern : AppAggregateCommand public sealed class AddPattern : AppCommand
{ {
public Guid PatternId { get; set; } public Guid PatternId { get; set; }

8
src/Squidex.Domain.Apps.Entities/SchemaAggregateCommand.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs

@ -8,13 +8,15 @@
using System; using System;
using Squidex.Infrastructure.Commands; 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 Guid IAggregateCommand.AggregateId
{ {
get { return SchemaId.Id; } get { return AppId; }
} }
} }
} }

2
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 namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AssignContributor : AppAggregateCommand public sealed class AssignContributor : AppCommand
{ {
public string ContributorId { get; set; } public string ContributorId { get; set; }

9
src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs

@ -9,10 +9,15 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AttachClient : AppAggregateCommand public sealed class AttachClient : AppCommand
{ {
public string Id { get; set; } public string Id { get; set; }
public string Secret { get; } = RandomHash.New(); public string Secret { get; set; }
public AttachClient()
{
Secret = RandomHash.New();
}
} }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class ChangePlan : AppAggregateCommand public sealed class ChangePlan : AppCommand
{ {
public bool FromCallback { get; set; } public bool FromCallback { get; set; }

9
src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs

@ -10,19 +10,12 @@ using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.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 Name { get; set; }
public string Template { get; set; } public string Template { get; set; }
Guid IAggregateCommand.AggregateId
{
get { return AppId; }
}
public CreateApp() public CreateApp()
{ {
AppId = Guid.NewGuid(); AppId = Guid.NewGuid();

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs

@ -9,7 +9,7 @@ using System;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class DeletePattern : AppAggregateCommand public sealed class DeletePattern : AppCommand
{ {
public Guid PatternId { get; set; } public Guid PatternId { get; set; }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class RemoveContributor : AppAggregateCommand public sealed class RemoveContributor : AppCommand
{ {
public string ContributorId { get; set; } public string ContributorId { get; set; }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs

@ -9,7 +9,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class RemoveLanguage : AppAggregateCommand public sealed class RemoveLanguage : AppCommand
{ {
public Language Language { get; set; } public Language Language { get; set; }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class RevokeClient : AppAggregateCommand public sealed class RevokeClient : AppCommand
{ {
public string Id { get; set; } public string Id { get; set; }
} }

2
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 namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class UpdateClient : AppAggregateCommand public sealed class UpdateClient : AppCommand
{ {
public string Id { get; set; } public string Id { get; set; }

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs

@ -10,7 +10,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class UpdateLanguage : AppAggregateCommand public sealed class UpdateLanguage : AppCommand
{ {
public Language Language { get; set; } public Language Language { get; set; }

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs

@ -9,7 +9,7 @@ using System;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class UpdatePattern : AppAggregateCommand public sealed class UpdatePattern : AppCommand
{ {
public Guid PatternId { get; set; } public Guid PatternId { get; set; }

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

@ -36,17 +36,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
{ {
var appId = new NamedId<Guid>(createApp.AppId, createApp.Name); var appId = new NamedId<Guid>(createApp.AppId, createApp.Name);
Task publishAsync(AppCommand command)
{
command.AppId = appId;
return context.CommandBus.PublishAsync(command);
}
return Task.WhenAll( return Task.WhenAll(
CreatePagesAsync(publishAsync, appId), CreatePagesAsync(context.CommandBus, appId),
CreatePostsAsync(publishAsync, appId), CreatePostsAsync(context.CommandBus, appId),
CreateClientAsync(publishAsync, appId)); CreateClientAsync(context.CommandBus, appId));
} }
return TaskHelper.Done; return TaskHelper.Done;
@ -57,16 +50,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase); return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase);
} }
private static async Task CreateClientAsync(Func<AppCommand, Task> publishAsync, NamedId<Guid> appId) private static async Task CreateClientAsync(ICommandBus bus, NamedId<Guid> appId)
{ {
await publishAsync(new AttachClient { Id = "sample-client" }); await bus.PublishAsync(new AttachClient { Id = "sample-client" });
} }
private async Task CreatePostsAsync(Func<AppCommand, Task> publishAsync, NamedId<Guid> appId) private async Task CreatePostsAsync(ICommandBus bus, NamedId<Guid> appId)
{ {
var postsId = await CreatePostsSchema(publishAsync); var postsId = await CreatePostsSchema(bus, appId);
await publishAsync(new CreateContent await bus.PublishAsync(new CreateContent
{ {
SchemaId = postsId, SchemaId = postsId,
Data = Data =
@ -81,11 +74,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
}); });
} }
private async Task CreatePagesAsync(Func<AppCommand, Task> publishAsync, NamedId<Guid> appId) private async Task CreatePagesAsync(ICommandBus bus, NamedId<Guid> appId)
{ {
var pagesId = await CreatePagesSchema(publishAsync); var pagesId = await CreatePagesSchema(bus, appId);
await publishAsync(new CreateContent await bus.PublishAsync(new CreateContent
{ {
SchemaId = pagesId, SchemaId = pagesId,
Data = Data =
@ -100,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
}); });
} }
private async Task<NamedId<Guid>> CreatePostsSchema(Func<AppCommand, Task> publishAsync) private async Task<NamedId<Guid>> CreatePostsSchema(ICommandBus bus, NamedId<Guid> appId)
{ {
var command = new CreateSchema var command = new CreateSchema
{ {
@ -111,58 +104,59 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
Label = "Posts" Label = "Posts"
}, },
Fields = new List<CreateSchemaField> Fields = new List<CreateSchemaField>
{
new CreateSchemaField
{ {
new CreateSchemaField Name = "title",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{ {
Name = "title", Editor = StringFieldEditor.Input,
Partitioning = Partitioning.Invariant.Key, IsRequired = true,
Properties = new StringFieldProperties IsListField = true,
{ MaxLength = 100,
Editor = StringFieldEditor.Input, MinLength = 0,
IsRequired = true, Label = "Title"
IsListField = true, }
MaxLength = 100, },
MinLength = 0, new CreateSchemaField
Label = "Title" {
} Name = "slug",
}, Partitioning = Partitioning.Invariant.Key,
new CreateSchemaField Properties = new StringFieldProperties
{ {
Name = "slug", Editor = StringFieldEditor.Slug,
Partitioning = Partitioning.Invariant.Key, IsRequired = false,
Properties = new StringFieldProperties IsListField = true,
{ MaxLength = 100,
Editor = StringFieldEditor.Slug, MinLength = 0,
IsRequired = false, Label = "Slug (Autogenerated)"
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Slug (Autogenerated)"
},
IsDisabled = true
}, },
new CreateSchemaField IsDisabled = true
},
new CreateSchemaField
{
Name = "text",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{ {
Name = "text", Editor = StringFieldEditor.RichText,
Partitioning = Partitioning.Invariant.Key, IsRequired = true,
Properties = new StringFieldProperties 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<Guid>(command.SchemaId, command.Name); var schemaId = new NamedId<Guid>(command.SchemaId, command.Name);
await publishAsync(new ConfigureScripts await bus.PublishAsync(new ConfigureScripts
{ {
SchemaId = schemaId, SchemaId = schemaId.Id,
ScriptCreate = SlugScript, ScriptCreate = SlugScript,
ScriptUpdate = SlugScript ScriptUpdate = SlugScript
}); });
@ -170,7 +164,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
return schemaId; return schemaId;
} }
private async Task<NamedId<Guid>> CreatePagesSchema(Func<AppCommand, Task> publishAsync) private async Task<NamedId<Guid>> CreatePagesSchema(ICommandBus bus, NamedId<Guid> appId)
{ {
var command = new CreateSchema var command = new CreateSchema
{ {
@ -180,58 +174,59 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
Label = "Pages" Label = "Pages"
}, },
Fields = new List<CreateSchemaField> Fields = new List<CreateSchemaField>
{
new CreateSchemaField
{ {
new CreateSchemaField Name = "title",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{ {
Name = "title", Editor = StringFieldEditor.Input,
Partitioning = Partitioning.Invariant.Key, IsRequired = true,
Properties = new StringFieldProperties IsListField = true,
{ MaxLength = 100,
Editor = StringFieldEditor.Input, MinLength = 0,
IsRequired = true, Label = "Title"
IsListField = true, }
MaxLength = 100, },
MinLength = 0, new CreateSchemaField
Label = "Title" {
} Name = "slug",
}, Partitioning = Partitioning.Invariant.Key,
new CreateSchemaField Properties = new StringFieldProperties
{ {
Name = "slug", Editor = StringFieldEditor.Slug,
Partitioning = Partitioning.Invariant.Key, IsRequired = false,
Properties = new StringFieldProperties IsListField = true,
{ MaxLength = 100,
Editor = StringFieldEditor.Slug, MinLength = 0,
IsRequired = false, Label = "Slug (Autogenerated)"
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Slug (Autogenerated)"
},
IsDisabled = true
}, },
new CreateSchemaField IsDisabled = true
},
new CreateSchemaField
{
Name = "text",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{ {
Name = "text", Editor = StringFieldEditor.RichText,
Partitioning = Partitioning.Invariant.Key, IsRequired = true,
Properties = new StringFieldProperties 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<Guid>(command.SchemaId, command.Name); var schemaId = new NamedId<Guid>(command.SchemaId, command.Name);
await publishAsync(new ConfigureScripts await bus.PublishAsync(new ConfigureScripts
{ {
SchemaId = schemaId, SchemaId = schemaId.Id,
ScriptCreate = SlugScript, ScriptCreate = SlugScript,
ScriptUpdate = SlugScript ScriptUpdate = SlugScript
}); });

11
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.Commands;
using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -74,6 +75,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
return this; return this;
} }
private void RaiseEvent(AppEvent @event)
{
if (@event.AppId == null)
{
@event.AppId = Snapshot.AppId;
}
RaiseEvent(Envelope.Create(@event));
}
private void VerifyNotCreated() private void VerifyNotCreated()
{ {
if (!string.IsNullOrWhiteSpace(Snapshot.FileName)) if (!string.IsNullOrWhiteSpace(Snapshot.FileName))

2
src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs → src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs

@ -10,7 +10,7 @@ using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Assets.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; } public Guid AssetId { get; set; }

5
src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs

@ -6,12 +6,15 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
namespace Squidex.Domain.Apps.Entities.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class CreateAsset : AssetAggregateCommand public sealed class CreateAsset : AssetCommand, IAppCommand
{ {
public NamedId<Guid> AppId { get; set; }
public AssetFile File { get; set; } public AssetFile File { get; set; }
public ImageInfo ImageInfo { get; set; } public ImageInfo ImageInfo { get; set; }

2
src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class DeleteAsset : AssetAggregateCommand public sealed class DeleteAsset : AssetCommand
{ {
} }
} }

2
src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class RenameAsset : AssetAggregateCommand public sealed class RenameAsset : AssetCommand
{ {
public string FileName { get; set; } public string FileName { get; set; }
} }

2
src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs

@ -9,7 +9,7 @@ using Squidex.Infrastructure.Assets;
namespace Squidex.Domain.Apps.Entities.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class UpdateAsset : AssetAggregateCommand public sealed class UpdateAsset : AssetCommand
{ {
public AssetFile File { get; set; } public AssetFile File { get; set; }

5
src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs

@ -5,18 +5,21 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IAssetEntity : public interface IAssetEntity :
IEntity, IEntity,
IEntityWithAppRef,
IEntityWithCreatedBy, IEntityWithCreatedBy,
IEntityWithLastModifiedBy, IEntityWithLastModifiedBy,
IEntityWithVersion, IEntityWithVersion,
IAssetInfo IAssetInfo
{ {
NamedId<Guid> AppId { get; }
string MimeType { get; } string MimeType { get; }
long FileVersion { get; } long FileVersion { get; }

8
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.Core.ValidateContent;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -18,11 +19,10 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
{ {
public class AssetState : DomainObjectState<AssetState>, public class AssetState : DomainObjectState<AssetState>,
IAssetEntity, IAssetEntity,
IAssetInfo, IAssetInfo
IUpdateableEntityWithAppRef
{ {
[JsonProperty] [JsonProperty]
public Guid AppId { get; set; } public NamedId<Guid> AppId { get; set; }
[JsonProperty] [JsonProperty]
public string FileName { get; set; } public string FileName { get; set; }
@ -61,6 +61,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
SimpleMapper.Map(@event, this); SimpleMapper.Map(@event, this);
TotalSize += @event.FileSize; TotalSize += @event.FileSize;
AppId = @event.AppId;
} }
protected void On(AssetUpdated @event) protected void On(AssetUpdated @event)

3
src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================= // =========================================================================
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
@ -12,5 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public sealed class ChangeContentStatus : ContentCommand public sealed class ChangeContentStatus : ContentCommand
{ {
public Status Status { get; set; } public Status Status { get; set; }
public Instant? DueTime { get; set; }
} }
} }

2
src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs

@ -10,7 +10,7 @@ using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Contents.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; } public Guid ContentId { get; set; }

7
src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs

@ -6,11 +6,16 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
{ {
public sealed class CreateContent : ContentDataCommand public sealed class CreateContent : ContentDataCommand, ISchemaCommand, IAppCommand
{ {
public NamedId<Guid> AppId { get; set; }
public NamedId<Guid> SchemaId { get; set; }
public bool Publish { get; set; } public bool Publish { get; set; }
public CreateContent() public CreateContent()

7
src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs

@ -109,9 +109,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
GuardContent.CanChangeContentStatus(content.Snapshot.Status, command); GuardContent.CanChangeContentStatus(content.Snapshot.Status, command);
var operationContext = await CreateContext(command, content, () => "Failed to patch content."); if (!command.DueTime.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); content.ChangeStatus(command);
}); });

25
src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs

@ -8,6 +8,7 @@
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -45,7 +46,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged())); if (command.DueTime.HasValue)
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value }));
}
else
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged()));
}
return this; return this;
} }
@ -80,6 +88,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
return this; 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() private void VerifyNotCreated()
{ {
if (Snapshot.Data != null) if (Snapshot.Data != null)

14
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 Id { get; set; }
public Guid AppId { get; set; } public NamedId<Guid> AppId { get; set; }
public NamedId<Guid> SchemaId { get; set; }
public long Version { get; set; } public long Version { get; set; }
@ -26,14 +28,20 @@ namespace Squidex.Domain.Apps.Entities.Contents
public Instant LastModified { get; set; } public Instant LastModified { get; set; }
public Status Status { get; set; }
public Status? ScheduledTo { get; set; }
public Instant? ScheduledAt { get; set; }
public RefToken ScheduledBy { get; set; }
public RefToken CreatedBy { get; set; } public RefToken CreatedBy { get; set; }
public RefToken LastModifiedBy { get; set; } public RefToken LastModifiedBy { get; set; }
public NamedContentData Data { get; set; } public NamedContentData Data { get; set; }
public Status Status { get; set; }
public static ContentEntity Create(CreateContent command, EntityCreatedResult<NamedContentData> result) public static ContentEntity Create(CreateContent command, EntityCreatedResult<NamedContentData> result)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();

17
src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs

@ -42,7 +42,16 @@ namespace Squidex.Domain.Apps.Entities.Contents
IScriptEngine scriptEngine, IScriptEngine scriptEngine,
Func<string> message) Func<string> 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 var context = new ContentOperationContext
{ {
@ -75,17 +84,15 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var errors = new List<ValidationError>(); var errors = new List<ValidationError>();
var appId = command.AppId.Id;
var ctx = var ctx =
new ValidationContext( new ValidationContext(
(contentIds, schemaId) => (contentIds, schemaId) =>
{ {
return QueryContentsAsync(appId, schemaId, contentIds); return QueryContentsAsync(content.Snapshot.AppId.Id, schemaId, contentIds);
}, },
assetIds => assetIds =>
{ {
return QueryAssetsAsync(appId, assetIds); return QueryAssetsAsync(content.Snapshot.AppId.Id, assetIds);
}); });
if (partial) if (partial)

57
src/Squidex.Domain.Apps.Entities/Contents/ContentScheduler.cs

@ -0,0 +1,57 @@
// ==========================================================================
// 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.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 ContentScheduler : IRunnable
{
private readonly CompletionTimer timer;
private readonly IContentRepository contentRepository;
private readonly ICommandBus commandBus;
private readonly IClock clock;
public ContentScheduler(
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.QueryScheduledWithoutDataAsync(now, content =>
{
var command = new ChangeContentStatus { ContentId = content.Id, Status = content.ScheduledTo.Value, Actor = content.ScheduledBy };
return commandBus.PublishAsync(command);
});
}
}
}

44
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); var inputType = new ContentDataGraphInputType(model, schema);
AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType); AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType);
AddContentUpdate(schemaId, schemaType, schemaName, inputType, resultType); AddContentUpdate(schemaType, schemaName, inputType, resultType);
AddContentPatch(schemaId, schemaType, schemaName, inputType, resultType); AddContentPatch(schemaType, schemaName, inputType, resultType);
AddContentPublish(schemaId, schemaType, schemaName); AddContentPublish(schemaType, schemaName);
AddContentUnpublish(schemaId, schemaType, schemaName); AddContentUnpublish(schemaType, schemaName);
AddContentArchive(schemaId, schemaType, schemaName); AddContentArchive(schemaType, schemaName);
AddContentRestore(schemaId, schemaType, schemaName); AddContentRestore(schemaType, schemaName);
AddContentDelete(schemaId, schemaType, schemaName); AddContentDelete(schemaType, schemaName);
} }
Description = "The app mutations."; Description = "The app mutations.";
@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
var contentData = GetContentData(c); 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 commandContext = await publish(command);
var result = commandContext.Result<EntityCreatedResult<NamedContentData>>(); var result = commandContext.Result<EntityCreatedResult<NamedContentData>>();
@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentUpdate(NamedId<Guid> schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
var contentId = c.GetArgument<Guid>("id"); var contentId = c.GetArgument<Guid>("id");
var contentData = GetContentData(c); 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 commandContext = await publish(command);
var result = commandContext.Result<ContentDataChangedResult>(); var result = commandContext.Result<ContentDataChangedResult>();
@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentPatch(NamedId<Guid> schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -179,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
var contentId = c.GetArgument<Guid>("id"); var contentId = c.GetArgument<Guid>("id");
var contentData = GetContentData(c); 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 commandContext = await publish(command);
var result = commandContext.Result<ContentDataChangedResult>(); var result = commandContext.Result<ContentDataChangedResult>();
@ -190,7 +190,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentPublish(NamedId<Guid> schemaId, string schemaType, string schemaName) private void AddContentPublish(string schemaType, string schemaName)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -201,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
var contentId = c.GetArgument<Guid>("id"); var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Published }; var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Published };
return publish(command); return publish(command);
}), }),
@ -209,7 +209,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentUnpublish(NamedId<Guid> schemaId, string schemaType, string schemaName) private void AddContentUnpublish(string schemaType, string schemaName)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -220,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
var contentId = c.GetArgument<Guid>("id"); var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Draft }; var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft };
return publish(command); return publish(command);
}), }),
@ -228,7 +228,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentArchive(NamedId<Guid> schemaId, string schemaType, string schemaName) private void AddContentArchive(string schemaType, string schemaName)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -239,7 +239,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
var contentId = c.GetArgument<Guid>("id"); var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Archived }; var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Archived };
return publish(command); return publish(command);
}), }),
@ -247,7 +247,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentRestore(NamedId<Guid> schemaId, string schemaType, string schemaName) private void AddContentRestore(string schemaType, string schemaName)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -258,7 +258,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
var contentId = c.GetArgument<Guid>("id"); var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Draft }; var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft };
return publish(command); return publish(command);
}), }),
@ -266,7 +266,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}); });
} }
private void AddContentDelete(NamedId<Guid> schemaId, string schemaType, string schemaName) private void AddContentDelete(string schemaType, string schemaName)
{ {
AddField(new FieldType AddField(new FieldType
{ {
@ -277,7 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
var contentId = c.GetArgument<Guid>("id"); var contentId = c.GetArgument<Guid>("id");
var command = new DeleteContent { SchemaId = schemaId, ContentId = contentId }; var command = new DeleteContent { ContentId = contentId };
return publish(command); return publish(command);
}), }),

7
src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs

@ -5,6 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -62,6 +64,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
error(new ValidationError($"Content cannot be changed from status {status} to {command.Status}.", nameof(command.Status))); error(new ValidationError($"Content cannot be changed from status {status} to {command.Status}.", nameof(command.Status)));
} }
if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant())
{
error(new ValidationError("DueTime must be in the future.", nameof(command.DueTime)));
}
}); });
} }

14
src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs

@ -6,19 +6,31 @@
// ========================================================================== // ==========================================================================
// ========================================================================== // ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentEntity : public interface IContentEntity :
IEntity, IEntity,
IEntityWithAppRef,
IEntityWithCreatedBy, IEntityWithCreatedBy,
IEntityWithLastModifiedBy, IEntityWithLastModifiedBy,
IEntityWithVersion IEntityWithVersion
{ {
NamedId<Guid> AppId { get; }
NamedId<Guid> SchemaId { get; }
Status Status { get; } Status Status { get; }
Status? ScheduledTo { get; }
Instant? ScheduledAt { get; }
RefToken ScheduledBy { get; }
NamedContentData Data { get; } NamedContentData Data { get; }
} }
} }

3
src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.OData.UriParser; using Microsoft.OData.UriParser;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
@ -27,5 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id); Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id);
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version); Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version);
Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback);
} }
} }

35
src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs

@ -7,38 +7,50 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Contents.State namespace Squidex.Domain.Apps.Entities.Contents.State
{ {
public class ContentState : DomainObjectState<ContentState>, public class ContentState : DomainObjectState<ContentState>,
IContentEntity, IContentEntity
IUpdateableEntityWithAppRef
{ {
[JsonProperty] [JsonProperty]
public NamedContentData Data { get; set; } public NamedId<Guid> AppId { get; set; }
[JsonProperty] [JsonProperty]
public Guid AppId { get; set; } public NamedId<Guid> SchemaId { get; set; }
[JsonProperty] [JsonProperty]
public Guid SchemaId { get; set; } public NamedContentData Data { get; set; }
[JsonProperty] [JsonProperty]
public Status Status { get; set; } public Status Status { get; set; }
[JsonProperty]
public Status? ScheduledTo { get; set; }
[JsonProperty]
public Instant? ScheduledAt { get; set; }
[JsonProperty]
public RefToken ScheduledBy { get; set; }
[JsonProperty] [JsonProperty]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
protected void On(ContentCreated @event) protected void On(ContentCreated @event)
{ {
SchemaId = @event.SchemaId.Id; SchemaId = @event.SchemaId;
Data = @event.Data; Data = @event.Data;
AppId = @event.AppId;
} }
protected void On(ContentUpdated @event) protected void On(ContentUpdated @event)
@ -46,9 +58,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
Data = @event.Data; Data = @event.Data;
} }
protected void On(ContentStatusScheduled @event)
{
ScheduledAt = @event.DueTime;
ScheduledBy = @event.Actor;
ScheduledTo = @event.Status;
}
protected void On(ContentStatusChanged @event) protected void On(ContentStatusChanged @event)
{ {
Status = @event.Status; Status = @event.Status;
ScheduledAt = null;
ScheduledBy = null;
ScheduledTo = null;
} }
protected void On(ContentDeleted @event) protected void On(ContentDeleted @event)

9
src/Squidex.Domain.Apps.Entities/EntityMapper.cs

@ -17,7 +17,6 @@ namespace Squidex.Domain.Apps.Entities
public static T Update<T>(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity public static T Update<T>(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity
{ {
SetId(entity, headers); SetId(entity, headers);
SetAppId(entity, @event);
SetCreated(entity, headers); SetCreated(entity, headers);
SetCreatedBy(entity, @event); SetCreatedBy(entity, @event);
SetLastModified(entity, headers); SetLastModified(entity, headers);
@ -76,13 +75,5 @@ namespace Squidex.Domain.Apps.Entities
withModifiedBy.LastModifiedBy = @event.Actor; 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;
}
}
} }
} }

7
src/Squidex.Domain.Apps.Entities/AppCommand.cs → src/Squidex.Domain.Apps.Entities/IAppCommand.cs

@ -1,17 +1,18 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Entities
{ {
public abstract class AppCommand : SquidexCommand public interface IAppCommand : ICommand
{ {
public NamedId<Guid> AppId { get; set; } NamedId<Guid> AppId { get; set; }
} }
} }

2
src/Squidex.Domain.Apps.Entities/IAppProvider.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities
Task<IAppEntity> GetAppAsync(string appName); Task<IAppEntity> GetAppAsync(string appName);
Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id); Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false);
Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name); Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name);

7
src/Squidex.Domain.Apps.Entities/SchemaCommand.cs → src/Squidex.Domain.Apps.Entities/ISchemaCommand.cs

@ -1,17 +1,18 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Entities
{ {
public abstract class SchemaCommand : AppCommand public interface ISchemaCommand : ICommand
{ {
public NamedId<Guid> SchemaId { get; set; } NamedId<Guid> SchemaId { get; set; }
} }
} }

5
src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs

@ -6,11 +6,14 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Rules.Commands namespace Squidex.Domain.Apps.Entities.Rules.Commands
{ {
public sealed class CreateRule : RuleEditCommand public sealed class CreateRule : RuleEditCommand, IAppCommand
{ {
public NamedId<Guid> AppId { get; set; }
public CreateRule() public CreateRule()
{ {
RuleId = Guid.NewGuid(); RuleId = Guid.NewGuid();

2
src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Rules.Commands namespace Squidex.Domain.Apps.Entities.Rules.Commands
{ {
public sealed class DeleteRule : RuleAggregateCommand public sealed class DeleteRule : RuleCommand
{ {
} }
} }

2
src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Rules.Commands namespace Squidex.Domain.Apps.Entities.Rules.Commands
{ {
public sealed class DisableRule : RuleAggregateCommand public sealed class DisableRule : RuleCommand
{ {
} }
} }

2
src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Rules.Commands namespace Squidex.Domain.Apps.Entities.Rules.Commands
{ {
public sealed class EnableRule : RuleAggregateCommand public sealed class EnableRule : RuleCommand
{ {
} }
} }

2
src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleAggregateCommand.cs → src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs

@ -10,7 +10,7 @@ using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Rules.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; } public Guid RuleId { get; set; }

2
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 namespace Squidex.Domain.Apps.Entities.Rules.Commands
{ {
public abstract class RuleEditCommand : RuleAggregateCommand public abstract class RuleEditCommand : RuleCommand
{ {
public RuleTrigger Trigger { get; set; } public RuleTrigger Trigger { get; set; }

5
src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands; 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)); Guard.NotNull(command, nameof(command));
@ -57,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (command.Trigger != null) 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); errors.Foreach(error);
} }

5
src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs

@ -5,17 +5,20 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Rules namespace Squidex.Domain.Apps.Entities.Rules
{ {
public interface IRuleEntity : public interface IRuleEntity :
IEntity, IEntity,
IEntityWithAppRef,
IEntityWithCreatedBy, IEntityWithCreatedBy,
IEntityWithLastModifiedBy, IEntityWithLastModifiedBy,
IEntityWithVersion IEntityWithVersion
{ {
NamedId<Guid> AppId { get; set; }
Rule RuleDef { get; } Rule RuleDef { get; }
} }
} }

2
src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs

@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
{ {
return handler.UpdateSyncedAsync<RuleDomainObject>(context, async r => return handler.UpdateSyncedAsync<RuleDomainObject>(context, async r =>
{ {
await GuardRule.CanUpdate(command, appProvider); await GuardRule.CanUpdate(command, r.Snapshot.AppId.Id, appProvider);
r.Update(command); r.Update(command);
}); });

11
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.Commands;
using Squidex.Domain.Apps.Entities.Rules.State; using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Rules; using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -52,6 +53,16 @@ namespace Squidex.Domain.Apps.Entities.Rules
RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); 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() private void VerifyNotCreated()
{ {
if (Snapshot.RuleDef != null) if (Snapshot.RuleDef != null)

9
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.Core.Rules;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Rules; using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Rules.State namespace Squidex.Domain.Apps.Entities.Rules.State
{ {
public class RuleState : DomainObjectState<RuleState>, public class RuleState : DomainObjectState<RuleState>,
IRuleEntity, IRuleEntity
IEntityWithAppRef,
IUpdateableEntityWithAppRef
{ {
[JsonProperty] [JsonProperty]
public Guid AppId { get; set; } public NamedId<Guid> AppId { get; set; }
[JsonProperty] [JsonProperty]
public Rule RuleDef { get; set; } public Rule RuleDef { get; set; }
@ -32,6 +31,8 @@ namespace Squidex.Domain.Apps.Entities.Rules.State
protected void On(RuleCreated @event) protected void On(RuleCreated @event)
{ {
RuleDef = new Rule(@event.Trigger, @event.Action); RuleDef = new Rule(@event.Trigger, @event.Action);
AppId = @event.AppId;
} }
protected void On(RuleUpdated @event) protected void On(RuleUpdated @event)

2
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 namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class AddField : SchemaAggregateCommand public sealed class AddField : SchemaCommand
{ {
public string Name { get; set; } public string Name { get; set; }

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class ConfigureScripts : SchemaAggregateCommand public sealed class ConfigureScripts : SchemaCommand
{ {
public string ScriptQuery { get; set; } public string ScriptQuery { get; set; }

15
src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs

@ -7,14 +7,16 @@
using System; using System;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.CreateSchemaField>; using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.CreateSchemaField>;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands 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<Guid> AppId { get; set; }
public string Name { get; set; }
public SchemaFields Fields { get; set; } public SchemaFields Fields { get; set; }
@ -22,13 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
public bool Publish { get; set; } public bool Publish { get; set; }
public string Name { get; set; }
Guid IAggregateCommand.AggregateId
{
get { return SchemaId; }
}
public CreateSchema() public CreateSchema()
{ {
SchemaId = Guid.NewGuid(); SchemaId = Guid.NewGuid();

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class DeleteSchema : SchemaAggregateCommand public sealed class DeleteSchema : SchemaCommand
{ {
} }
} }

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public class FieldCommand : SchemaAggregateCommand public class FieldCommand : SchemaCommand
{ {
public long FieldId { get; set; } public long FieldId { get; set; }
} }

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class PublishSchema : SchemaAggregateCommand public sealed class PublishSchema : SchemaCommand
{ {
} }
} }

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs

@ -9,7 +9,7 @@ using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class ReorderFields : SchemaAggregateCommand public sealed class ReorderFields : SchemaCommand
{ {
public List<long> FieldIds { get; set; } public List<long> FieldIds { get; set; }
} }

6
src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs → src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs

@ -10,11 +10,13 @@ using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Entities
{ {
public class AppAggregateCommand : AppCommand, IAggregateCommand public abstract class SchemaCommand : SquidexCommand, IAggregateCommand
{ {
public Guid SchemaId { get; set; }
Guid IAggregateCommand.AggregateId Guid IAggregateCommand.AggregateId
{ {
get { return AppId.Id; } get { return SchemaId; }
} }
} }
} }

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class UnpublishSchema : SchemaAggregateCommand public sealed class UnpublishSchema : SchemaCommand
{ {
} }
} }

2
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 namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class UpdateSchema : SchemaAggregateCommand public sealed class UpdateSchema : SchemaCommand
{ {
public SchemaProperties Properties { get; set; } public SchemaProperties Properties { get; set; }
} }

5
src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs

@ -5,17 +5,20 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Schemas namespace Squidex.Domain.Apps.Entities.Schemas
{ {
public interface ISchemaEntity : public interface ISchemaEntity :
IEntity, IEntity,
IEntityWithAppRef,
IEntityWithCreatedBy, IEntityWithCreatedBy,
IEntityWithLastModifiedBy, IEntityWithLastModifiedBy,
IEntityWithVersion IEntityWithVersion
{ {
NamedId<Guid> AppId { get; }
string Name { get; } string Name { get; }
bool IsPublished { get; } bool IsPublished { get; }

16
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.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Domain.Apps.Entities.Schemas.State; using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas; using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -190,6 +191,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas
RaiseEvent(@event); RaiseEvent(@event);
} }
private void RaiseEvent(SchemaEvent @event)
{
if (@event.SchemaId == null)
{
@event.SchemaId = new NamedId<Guid>(Snapshot.Id, Snapshot.Name);
}
if (@event.AppId == null)
{
@event.AppId = Snapshot.AppId;
}
RaiseEvent(Envelope.Create(@event));
}
private void VerifyNotCreated() private void VerifyNotCreated()
{ {
if (Snapshot.SchemaDef != null) if (Snapshot.SchemaDef != null)

12
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.Core.Schemas;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas; using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -18,16 +19,13 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Schemas.State namespace Squidex.Domain.Apps.Entities.Schemas.State
{ {
public class SchemaState : DomainObjectState<SchemaState>, public class SchemaState : DomainObjectState<SchemaState>,
ISchemaEntity, ISchemaEntity
IUpdateableEntityWithAppRef,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy
{ {
[JsonProperty] [JsonProperty]
public string Name { get; set; } public NamedId<Guid> AppId { get; set; }
[JsonProperty] [JsonProperty]
public Guid AppId { get; set; } public string Name { get; set; }
[JsonProperty] [JsonProperty]
public int TotalFields { get; set; } = 0; public int TotalFields { get; set; } = 0;
@ -108,6 +106,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
} }
SchemaDef = schema; SchemaDef = schema;
AppId = @event.AppId;
} }
protected void On(FieldAdded @event, FieldRegistry registry) protected void On(FieldAdded @event, FieldRegistry registry)

13
src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithAppRef.cs → src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs

@ -5,12 +5,17 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Events.Contents
{ {
public interface IUpdateableEntityWithAppRef [EventType(nameof(ContentStatusScheduled))]
public sealed class ContentStatusScheduled : ContentEvent
{ {
Guid AppId { get; set; } public Status Status { get; set; }
public Instant DueTime { get; set; }
} }
} }

35
src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs

@ -10,6 +10,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NodaTime;
using NodaTime.Text;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Contents.Models; using Squidex.Areas.Api.Controllers.Contents.Models;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -213,11 +215,11 @@ namespace Squidex.Areas.Api.Controllers.Contents
[HttpPut] [HttpPut]
[Route("content/{app}/{name}/{id}/publish/")] [Route("content/{app}/{name}/{id}/publish/")]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PublishContent(string name, Guid id) public async Task<IActionResult> PublishContent(string name, Guid id, string dueTime = null)
{ {
await contentQuery.FindSchemaAsync(App, name); await contentQuery.FindSchemaAsync(App, name);
var command = new ChangeContentStatus { Status = Status.Published, ContentId = id }; var command = CreateCommand(id, Status.Published, dueTime);
await CommandBus.PublishAsync(command); await CommandBus.PublishAsync(command);
@ -228,11 +230,11 @@ namespace Squidex.Areas.Api.Controllers.Contents
[HttpPut] [HttpPut]
[Route("content/{app}/{name}/{id}/unpublish/")] [Route("content/{app}/{name}/{id}/unpublish/")]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> UnpublishContent(string name, Guid id) public async Task<IActionResult> UnpublishContent(string name, Guid id, string dueTime = null)
{ {
await contentQuery.FindSchemaAsync(App, name); await contentQuery.FindSchemaAsync(App, name);
var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id }; var command = CreateCommand(id, Status.Draft, dueTime);
await CommandBus.PublishAsync(command); await CommandBus.PublishAsync(command);
@ -243,11 +245,11 @@ namespace Squidex.Areas.Api.Controllers.Contents
[HttpPut] [HttpPut]
[Route("content/{app}/{name}/{id}/archive/")] [Route("content/{app}/{name}/{id}/archive/")]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> ArchiveContent(string name, Guid id) public async Task<IActionResult> ArchiveContent(string name, Guid id, string dueTime = null)
{ {
await contentQuery.FindSchemaAsync(App, name); await contentQuery.FindSchemaAsync(App, name);
var command = new ChangeContentStatus { Status = Status.Archived, ContentId = id }; var command = CreateCommand(id, Status.Archived, dueTime);
await CommandBus.PublishAsync(command); await CommandBus.PublishAsync(command);
@ -258,11 +260,11 @@ namespace Squidex.Areas.Api.Controllers.Contents
[HttpPut] [HttpPut]
[Route("content/{app}/{name}/{id}/restore/")] [Route("content/{app}/{name}/{id}/restore/")]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> RestoreContent(string name, Guid id) public async Task<IActionResult> RestoreContent(string name, Guid id, string dueTime = null)
{ {
await contentQuery.FindSchemaAsync(App, name); await contentQuery.FindSchemaAsync(App, name);
var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id }; var command = CreateCommand(id, Status.Draft, dueTime);
await CommandBus.PublishAsync(command); await CommandBus.PublishAsync(command);
@ -283,5 +285,22 @@ namespace Squidex.Areas.Api.Controllers.Contents
return NoContent(); return NoContent();
} }
private static ChangeContentStatus CreateCommand(Guid id, Status status, string dueTime)
{
Instant? dt = null;
if (!string.IsNullOrWhiteSpace(dueTime))
{
var parseResult = InstantPattern.General.Parse(dueTime);
if (parseResult.Success)
{
dt = parseResult.Value;
}
}
return new ChangeContentStatus { Status = status, ContentId = id, DueTime = dt };
}
} }
} }

15
src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs

@ -40,6 +40,21 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
[Required] [Required]
public object Data { get; set; } public object Data { get; set; }
/// <summary>
/// The scheduled status.
/// </summary>
public Status? ScheduledTo { get; set; }
/// <summary>
/// The scheduled date.
/// </summary>
public Instant? ScheduledAt { get; set; }
/// <summary>
/// The user that has scheduled the content.
/// </summary>
public RefToken ScheduledBy { get; set; }
/// <summary> /// <summary>
/// The date and time when the content item has been created. /// The date and time when the content item has been created.
/// </summary> /// </summary>

1
src/Squidex/Config/Authentication/MicrosoftHandler.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Authentication.OAuth;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;

2
src/Squidex/Config/Domain/ReadServices.cs

@ -46,6 +46,8 @@ namespace Squidex.Config.Domain
.As<IRunnable>(); .As<IRunnable>();
services.AddSingletonAs<RuleDequeuer>() services.AddSingletonAs<RuleDequeuer>()
.As<IRunnable>(); .As<IRunnable>();
services.AddSingletonAs<ContentScheduler>()
.As<IRunnable>();
} }
var exposeSourceUrl = config.GetOptionalValue("assetStore:exposeSourceUrl", true); var exposeSourceUrl = config.GetOptionalValue("assetStore:exposeSourceUrl", true);

4
src/Squidex/Config/Domain/SerializationServices.cs

@ -28,10 +28,10 @@ namespace Squidex.Config.Domain
{ {
private static readonly TypeNameRegistry TypeNameRegistry = private static readonly TypeNameRegistry TypeNameRegistry =
new TypeNameRegistry() new TypeNameRegistry()
.MapUnmapped(typeof(Migration01_FromCqrs).Assembly)
.MapUnmapped(typeof(SquidexCoreModel).Assembly) .MapUnmapped(typeof(SquidexCoreModel).Assembly)
.MapUnmapped(typeof(SquidexEvents).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); private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry);

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

@ -79,6 +79,9 @@ namespace Squidex.Config.Domain
services.AddTransientAs<Migration04_FlattenAssetEntity>() services.AddTransientAs<Migration04_FlattenAssetEntity>()
.As<IMigration>(); .As<IMigration>();
services.AddTransientAs<Migration05_RebuildForNewCommands>()
.As<IMigration>();
services.AddTransientAs<Rebuilder>() services.AddTransientAs<Rebuilder>()
.AsSelf(); .AsSelf();

5
src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs

@ -25,6 +25,11 @@ namespace Squidex.Pipeline.CommandMiddlewares
public async Task HandleAsync(CommandContext context, Func<Task> next) public async Task HandleAsync(CommandContext context, Func<Task> next)
{ {
if (httpContextAccessor.HttpContext == null)
{
return;
}
var headers = httpContextAccessor.HttpContext.Request.Headers; var headers = httpContextAccessor.HttpContext.Request.Headers;
var headerMatch = headers["If-Match"].ToString(); var headerMatch = headers["If-Match"].ToString();

5
src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs

@ -27,6 +27,11 @@ namespace Squidex.Pipeline.CommandMiddlewares
public Task HandleAsync(CommandContext context, Func<Task> next) public Task HandleAsync(CommandContext context, Func<Task> next)
{ {
if (httpContextAccessor.HttpContext == null)
{
return next();
}
if (context.Command is SquidexCommand squidexCommand) if (context.Command is SquidexCommand squidexCommand)
{ {
if (squidexCommand.Actor == null) if (squidexCommand.Actor == null)

7
src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs

@ -25,7 +25,12 @@ namespace Squidex.Pipeline.CommandMiddlewares
public Task HandleAsync(CommandContext context, Func<Task> next) public Task HandleAsync(CommandContext context, Func<Task> next)
{ {
if (context.Command is AppCommand appCommand && appCommand.AppId == null) if (httpContextAccessor.HttpContext == null)
{
return next();
}
if (context.Command is IAppCommand appCommand && appCommand.AppId == null)
{ {
var appFeature = httpContextAccessor.HttpContext.Features.Get<IAppFeature>(); var appFeature = httpContextAccessor.HttpContext.Features.Get<IAppFeature>();

33
src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs

@ -29,8 +29,35 @@ namespace Squidex.Pipeline.CommandMiddlewares
public async Task HandleAsync(CommandContext context, Func<Task> next) public async Task HandleAsync(CommandContext context, Func<Task> next)
{ {
if (context.Command is SchemaCommand schemaCommand && schemaCommand.SchemaId == null) if (actionContextAccessor.ActionContext == null)
{ {
await next();
}
if (context.Command is ISchemaCommand schemaCommand && schemaCommand.SchemaId == null)
{
NamedId<Guid> appId = null;
if (context.Command is IAppCommand appCommand)
{
appId = appCommand.AppId;
}
if (appId == null)
{
var appFeature = actionContextAccessor.ActionContext.HttpContext.Features.Get<IAppFeature>();
if (appFeature != null && appFeature.App != null)
{
appId = new NamedId<Guid>(appFeature.App.Id, appFeature.App.Name);
}
}
if (appId == null)
{
return;
}
var routeValues = actionContextAccessor.ActionContext.RouteData.Values; var routeValues = actionContextAccessor.ActionContext.RouteData.Values;
if (routeValues.ContainsKey("name")) if (routeValues.ContainsKey("name"))
@ -41,11 +68,11 @@ namespace Squidex.Pipeline.CommandMiddlewares
if (Guid.TryParse(schemaName, out var id)) if (Guid.TryParse(schemaName, out var id))
{ {
schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Id, id); schema = await appProvider.GetSchemaAsync(appId.Id, id);
} }
else else
{ {
schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Id, schemaName); schema = await appProvider.GetSchemaAsync(appId.Id, schemaName);
} }
if (schema == null) if (schema == null)

35
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -12,9 +12,8 @@ import { Observable, Subscription } from 'rxjs';
import { import {
ContentCreated, ContentCreated,
ContentPublished,
ContentRemoved, ContentRemoved,
ContentUnpublished, ContentStatusChanged,
ContentUpdated, ContentUpdated,
ContentVersionSelected ContentVersionSelected
} from './../messages'; } from './../messages';
@ -39,8 +38,7 @@ import {
] ]
}) })
export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, OnInit { export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, OnInit {
private contentPublishedSubscription: Subscription; private contentStatusChangedSubscription: Subscription;
private contentUnpublishedSubscription: Subscription;
private contentDeletedSubscription: Subscription; private contentDeletedSubscription: Subscription;
private contentVersionSelectedSubscription: Subscription; private contentVersionSelectedSubscription: Subscription;
@ -63,8 +61,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
public ngOnDestroy() { public ngOnDestroy() {
this.contentVersionSelectedSubscription.unsubscribe(); this.contentVersionSelectedSubscription.unsubscribe();
this.contentUnpublishedSubscription.unsubscribe(); this.contentStatusChangedSubscription.unsubscribe();
this.contentPublishedSubscription.unsubscribe();
this.contentDeletedSubscription.unsubscribe(); this.contentDeletedSubscription.unsubscribe();
} }
@ -75,27 +72,25 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
this.loadVersion(message.version); this.loadVersion(message.version);
}); });
this.contentPublishedSubscription = this.contentDeletedSubscription =
this.ctx.bus.of(ContentPublished) this.ctx.bus.of(ContentRemoved)
.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)
.subscribe(message => { .subscribe(message => {
if (this.content && message.content.id === this.content.id) { 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.contentStatusChangedSubscription =
this.ctx.bus.of(ContentRemoved) this.ctx.bus.of(ContentStatusChanged)
.subscribe(message => { .subscribe(message => {
if (this.content && message.content.id === this.content.id) { 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.scheduledAt,
message.content.lastModifiedBy,
message.content.version,
message.content.lastModified);
} }
}); });

54
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! Search for content using full text search over all fields and languages!
</sqx-onboarding-tooltip> </sqx-onboarding-tooltip>
<div class="dropdown-menu" *sqxModalView="searchModal" closeAlways="true" [sqxModalTarget]="inputFind"> <div class="dropdown-menu" *sqxModalView="searchModal" [sqxModalTarget]="inputFind">
<sqx-search-form <sqx-search-form
[canArchive]="!isReadOnly" [canArchive]="!isReadOnly"
(queryChanged)="contentsFilter.setValue($event, { emitEvent: false })" (queryChanged)="contentsFilter.setValue($event, { emitEvent: false })"
@ -92,25 +92,25 @@
</div> </div>
<div class="selection" *ngIf="selectionCount > 0"> <div class="selection" *ngIf="selectionCount > 0">
{{selectionCount}} items selected: {{selectionCount}} items selected:&nbsp;&nbsp;
<button class="btn btn-link btn-secondary" (click)="publishSelected()" *ngIf="canPublish"> <button class="btn btn-secondary" (click)="publishSelected()" *ngIf="canPublish">
Publish Publish
</button> </button>
<button class="btn btn-link btn-secondary" (click)="unpublishSelected()" *ngIf="canUnpublish"> <button class="btn btn-secondary" (click)="unpublishSelected()" *ngIf="canUnpublish">
Unublish Unpublish
</button> </button>
<button class="btn btn-link btn-secondary" (click)="archiveSelected()" *ngIf="!isArchive"> <button class="btn btn-secondary" (click)="archiveSelected()" *ngIf="!isArchive">
Archive Archive
</button> </button>
<button class="btn btn-link btn-secondary" (click)="restoreSelected()" *ngIf="isArchive"> <button class="btn btn-secondary" (click)="restoreSelected()" *ngIf="isArchive">
Restore Restore
</button> </button>
<button class="btn btn-link btn-danger" <button class="btn btn-danger"
(sqxConfirmClick)="deleteSelected()" (sqxConfirmClick)="deleteSelected()"
confirmTitle="Delete content" confirmTitle="Delete content"
confirmText="Do you really want to delete the selected content items?"> confirmText="Do you really want to delete the selected content items?">
@ -168,4 +168,40 @@
</div> </div>
</sqx-panel> </sqx-panel>
<router-outlet></router-outlet> <router-outlet></router-outlet>
<div class="modal" *sqxModalView="dueTimeDialog;onRoot:true">
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{dueTimeAction}} content item(s)</h4>
</div>
<div class="modal-body">
<div class="form-check">
<input class="form-check-input" type="radio" [(ngModel)]="dueTimeMode" value="Immediately" id="immediately">
<label class="form-check-label" for="immediately">
{{dueTimeAction}} content item(s) immediately.
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" [(ngModel)]="dueTimeMode" value="Scheduled" id="scheduled">
<label class="form-check-label" for="scheduled">
{{dueTimeAction}} content item(s) at a later point date and time.
</label>
</div>
<sqx-date-time-editor [disabled]="dueTimeMode === 'Immediately'" mode="DateTime" hideClear="true" [(ngModel)]="dueTime"></sqx-date-time-editor>
</div>
<div class="modal-footer">
<div class="clearfix">
<button type="button" class="float-left btn btn-secondary" (click)="cancelStatusChange()">Cancel</button>
<button type="button" class="float-right btn btn-primary" [disabled]="dueTimeMode === 'Scheduled' && !dueTime" (click)="confirmStatusChange()">Confirm</button>
</div>
</div>
</div>
</div>
</div>

174
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -11,9 +11,8 @@ import { Observable, Subscription } from 'rxjs';
import { import {
ContentCreated, ContentCreated,
ContentPublished,
ContentRemoved, ContentRemoved,
ContentUnpublished, ContentStatusChanged,
ContentUpdated ContentUpdated
} from './../messages'; } from './../messages';
@ -23,6 +22,7 @@ import {
AppLanguageDto, AppLanguageDto,
ContentDto, ContentDto,
ContentsService, ContentsService,
DateTime,
FieldDto, FieldDto,
ImmutableArray, ImmutableArray,
ModalView, ModalView,
@ -52,6 +52,12 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public contentsQuery = ''; public contentsQuery = '';
public contentsPager = new Pager(0); 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 selectedItems: { [id: string]: boolean; } = {};
public selectionCount = 0; public selectionCount = 0;
@ -118,116 +124,89 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
} }
public publishContent(content: ContentDto) { public publishContent(content: ContentDto) {
this.publishContentItem(content).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.publishContentItem(c)))
.finally(() => {
this.updateSelectionSummary();
})
.subscribe();
} }
private publishContentItem(content: ContentDto): Observable<any> { public publishSelected(scheduled: boolean) {
return this.contentsService.publishContent(this.ctx.appName, this.schema.name, content.id, content.version) const contents = this.contentItems.filter(c => c.status !== 'Published' && this.selectedItems[c.id]).values;
.catch(error => {
this.ctx.notifyError(error);
return Observable.throw(error); this.changeContentItems(contents, 'Publish', 'Published', false);
})
.do(dto => {
this.contentItems = this.contentItems.replaceBy('id', content.publish(this.ctx.userToken, dto.version));
this.emitContentPublished(content);
});
} }
public unpublishContent(content: ContentDto) { public unpublishContent(content: ContentDto) {
this.unpublishContentItem(content).subscribe(); this.changeContentItems([content], 'Unpublish', 'Draft', false);
} }
public unpublishSelected() { public unpublishSelected(scheduled: boolean) {
Observable.forkJoin( const contents = this.contentItems.filter(c => c.status === 'Published' && this.selectedItems[c.id]).values;
this.contentItems.values
.filter(c => this.selectedItems[c.id])
.filter(c => c.status !== 'Unpublished')
.map(c => this.unpublishContentItem(c)))
.finally(() => {
this.updateSelectionSummary();
})
.subscribe();
}
private unpublishContentItem(content: ContentDto): Observable<any> {
return this.contentsService.unpublishContent(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.unpublish(this.ctx.userToken, dto.version));
this.emitContentUnpublished(content); this.changeContentItems(contents, 'Unpublish', 'Draft', false);
});
}
public archiveSelected() {
Observable.forkJoin(
this.contentItems.values.filter(c => this.selectedItems[c.id])
.map(c => this.archiveContentItem(c)))
.finally(() => {
this.load();
})
.subscribe();
} }
public archiveContent(content: ContentDto) { public archiveContent(content: ContentDto) {
this.archiveContentItem(content) this.changeContentItems([content], 'Archive', 'Archived', true);
.finally(() => {
this.load();
})
.subscribe();
} }
public archiveContentItem(content: ContentDto): Observable<any> { public archiveSelected(scheduled: boolean) {
return this.contentsService.archiveContent(this.ctx.appName, this.schema.name, content.id, content.version) const contents = this.contentItems.filter(c => this.selectedItems[c.id]).values;
.catch(error => {
this.ctx.notifyError(error);
return Observable.throw(error); this.changeContentItems(contents, 'Archive', 'Archived', true);
});
} }
public restoreSelected() { public restoreContent(content: ContentDto) {
Observable.forkJoin( this.changeContentItems([content], 'Restore', 'Draft', true);
this.contentItems.values.filter(c => this.selectedItems[c.id])
.map(c => this.restoreContentItem(c)))
.finally(() => {
this.load();
})
.subscribe();
} }
public restoreContent(content: ContentDto) { public restoreSelected(scheduled: boolean) {
this.restoreContentItem(content) const contents = this.contentItems.filter(c => this.selectedItems[c.id]).values;
.finally(() => {
this.load(); this.changeContentItems(contents, 'Restore', 'Draft', true);
})
.subscribe();
} }
public restoreContentItem(content: ContentDto): Observable<any> { private changeContentItems(contents: ContentDto[], action: string, status: string, reload: boolean) {
return this.contentsService.restoreContent(this.ctx.appName, this.schema.name, content.id, content.version) 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();
}
private changeContentItem(content: ContentDto, action: string, status: string, dueTime: string | null, reload: boolean): Observable<any> {
return this.contentsService.changeContentStatus(this.ctx.appName, this.schema.name, content.id, action, dueTime, content.version)
.catch(error => { .catch(error => {
this.ctx.notifyError(error); this.ctx.notifyError(error);
return Observable.throw(error); return Observable.throw(error);
})
.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);
}
}); });
} }
@ -359,12 +338,8 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.languageSelected = language; this.languageSelected = language;
} }
private emitContentPublished(content: ContentDto) { private emitContentStatusChanged(content: ContentDto) {
this.ctx.bus.emit(new ContentPublished(content)); this.ctx.bus.emit(new ContentStatusChanged(content));
}
private emitContentUnpublished(content: ContentDto) {
this.ctx.bus.emit(new ContentUnpublished(content));
} }
private emitContentRemoved(content: ContentDto) { private emitContentRemoved(content: ContentDto) {
@ -393,5 +368,18 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.contentFields = [<any>{}]; this.contentFields = [<any>{}];
} }
} }
public confirmStatusChange() {
this.dueTimeFunction!();
this.cancelStatusChange();
}
public cancelStatusChange() {
this.dueTimeMode = 'Immediately';
this.dueTimeDialog.hide();
this.dueTimeFunction = null;
this.dueTime = null;
}
} }

9
src/Squidex/app/features/content/pages/messages.ts

@ -35,14 +35,7 @@ export class ContentVersionSelected {
} }
} }
export class ContentPublished { export class ContentStatusChanged {
constructor(
public readonly content: ContentDto
) {
}
}
export class ContentUnpublished {
constructor( constructor(
public readonly content: ContentDto public readonly content: ContentDto
) { ) {

18
src/Squidex/app/features/content/shared/content-item.component.html

@ -10,7 +10,21 @@
</span> </span>
</td> </td>
<td class="cell-time"> <td class="cell-time">
<span class="item-published" [class.unpublished]="content.status !== 'Published'"></span> <span *ngIf="!content.scheduledTo">
<span class="content-status content-status-{{content.status | lowercase}}" #statusIcon>
<i class="icon-circle"></i>
</span>
<sqx-tooltip [target]="statusIcon">{{content.status}}</sqx-tooltip>
</span>
<span *ngIf="content.scheduledTo">
<span class="content-status content-status-{{content.scheduledTo | lowercase}}" #statusIcon>
<i class="icon-clock"></i>
</span>
<sqx-tooltip [target]="statusIcon">Will be set to '{{content.scheduledTo}}' at {{content.scheduledAt | sqxFullDateTime}}</sqx-tooltip>
</span>
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> <small class="item-modified">{{content.lastModified | sqxFromNow}}</small>
</td> </td>
@ -22,7 +36,7 @@
<button type="button" class="btn btn-link btn-secondary" (click)="dropdown.toggle(); $event.stopPropagation()" [class.active]="dropdown.isOpen | async" #optionsButton> <button type="button" class="btn btn-link btn-secondary" (click)="dropdown.toggle(); $event.stopPropagation()" [class.active]="dropdown.isOpen | async" #optionsButton>
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" @fade> <div class="dropdown-menu" *sqxModalView="dropdown" [sqxModalTarget]="optionsButton" @fade>
<a class="dropdown-item" (click)="publishing.emit(); $event.stopPropagation()" *ngIf="content.status === 'Draft'"> <a class="dropdown-item" (click)="publishing.emit(); $event.stopPropagation()" *ngIf="content.status === 'Draft'">
Publish Publish
</a> </a>

32
src/Squidex/app/features/content/shared/content-item.component.scss

@ -1,22 +1,30 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
.content { .content-status {
& { & {
cursor: pointer; vertical-align: middle;
} }
&-published { &-published {
& { color: $color-theme-green;
@include circle(.5rem); }
display: inline-block;
border: 0; &-draft {
background: $color-theme-green; color: $color-text-decent;
margin-left: .4rem; }
}
&-archived {
color: $color-theme-error;
}
&.unpublished { &-tooltip {
background: $color-theme-error; @include border-radius;
} background: $color-tooltip;
border: 0;
font-size: .9rem;
font-weight: normal;
color: $color-dark-foreground;
padding: .75rem;
} }
} }

7
src/Squidex/app/features/content/shared/content-item.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { import {
AppContext, AppContext,
@ -27,7 +27,8 @@ import {
], ],
animations: [ animations: [
fadeAnimation fadeAnimation
] ],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ContentItemComponent implements OnInit, OnChanges { export class ContentItemComponent implements OnInit, OnChanges {
@Output() @Output()
@ -46,7 +47,7 @@ export class ContentItemComponent implements OnInit, OnChanges {
public deleting = new EventEmitter(); public deleting = new EventEmitter();
@Output() @Output()
public selectedChange = new EventEmitter<boolean>(); public selectedChange = new EventEmitter();
@Input() @Input()
public selected = false; public selected = false;

2
src/Squidex/app/features/rules/pages/events/rule-events-page.component.html

@ -77,7 +77,7 @@
Attempts: {{event.numCalls}} Attempts: {{event.numCalls}}
</div> </div>
<div class="col-3"> <div class="col-3">
Next: <span *ngIf="event.nextAttempt">{{event.nextAttempt.toStringFormat('MMM DD h:mm:ss a')}}</span> Next: <span *ngIf="event.nextAttempt">{{event.nextAttempt | sqxFromNow}}</span>
</div> </div>
<div class="col-3 text-right"> <div class="col-3 text-right">
<button class="btn btn-success btn-sm" (click)="enqueueEvent(event)"> <button class="btn btn-success btn-sm" (click)="enqueueEvent(event)">

4
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.scss

@ -29,10 +29,6 @@
} }
} }
.clearfix {
width: 100%;
}
.rule-element { .rule-element {
margin-right: .5rem; margin-right: .5rem;
} }

2
src/Squidex/app/features/schemas/pages/schema/field.component.html

@ -26,7 +26,7 @@
<button type="button" class="btn btn-link btn-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #optionsButton> <button type="button" class="btn btn-link btn-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #optionsButton>
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" @fade> <div class="dropdown-menu" *sqxModalView="dropdown" [sqxModalTarget]="optionsButton" @fade>
<a class="dropdown-item" (click)="enabling.emit()" *ngIf="field.isDisabled" [class.disabled]="field.isLocked"> <a class="dropdown-item" (click)="enabling.emit()" *ngIf="field.isDisabled" [class.disabled]="field.isLocked">
Enable Enable
</a> </a>

2
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -21,7 +21,7 @@
<button type="button" class="btn btn-link btn-secondary" (click)="editOptionsDropdown.toggle()" [class.active]="editOptionsDropdown.isOpen | async" #buttonOptions> <button type="button" class="btn btn-link btn-secondary" (click)="editOptionsDropdown.toggle()" [class.active]="editOptionsDropdown.isOpen | async" #buttonOptions>
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="editOptionsDropdown" closeAlways="true" [sqxModalTarget]="buttonOptions" @fade> <div class="dropdown-menu" *sqxModalView="editOptionsDropdown" [sqxModalTarget]="buttonOptions" @fade>
<a class="dropdown-item" (click)="configureScriptsDialog.show()"> <a class="dropdown-item" (click)="configureScriptsDialog.show()">
Scripts Scripts
</a> </a>

4
src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.scss

@ -7,8 +7,4 @@
.nav-tabs { .nav-tabs {
border: 0; border: 0;
}
.clearfix {
width: 100%;
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save