Browse Source

Merge branch 'master' into orleans3

# Conflicts:
#	src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs
pull/249/head
Sebastian Stehle 8 years ago
parent
commit
97e301213b
  1. 10
      CHANGELOG.md
  2. 3
      src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs
  4. 2
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs
  5. 4
      src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  6. 15
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  7. 1
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  8. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  9. 24
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  10. 24
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  11. 10
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  12. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  13. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs
  14. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  15. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs
  16. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs
  17. 4
      src/Squidex.Domain.Apps.Entities/AppProvider.cs
  18. 2
      src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  19. 3
      src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs
  20. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs
  21. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs
  22. 8
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs
  23. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs
  24. 9
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs
  25. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs
  26. 9
      src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs
  27. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs
  28. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs
  29. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs
  30. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs
  31. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs
  32. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs
  33. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs
  34. 8
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs
  35. 192
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs
  36. 14
      src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs
  37. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs
  38. 5
      src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs
  39. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs
  40. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs
  41. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs
  42. 2
      src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs
  43. 5
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs
  44. 8
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  45. 3
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs
  46. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs
  47. 7
      src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs
  48. 7
      src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  49. 28
      src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs
  50. 14
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  51. 17
      src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  52. 57
      src/Squidex.Domain.Apps.Entities/Contents/ContentScheduler.cs
  53. 44
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs
  54. 7
      src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  55. 14
      src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs
  56. 3
      src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  57. 35
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  58. 9
      src/Squidex.Domain.Apps.Entities/EntityMapper.cs
  59. 7
      src/Squidex.Domain.Apps.Entities/IAppCommand.cs
  60. 2
      src/Squidex.Domain.Apps.Entities/IAppProvider.cs
  61. 7
      src/Squidex.Domain.Apps.Entities/ISchemaCommand.cs
  62. 5
      src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs
  63. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs
  64. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs
  65. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs
  66. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs
  67. 2
      src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs
  68. 11
      src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs
  69. 18
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs
  70. 5
      src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs
  71. 2
      src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleEventRepository.cs
  72. 2
      src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs
  73. 4
      src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuer.cs
  74. 14
      src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs
  75. 9
      src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs
  76. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs
  77. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs
  78. 15
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs
  79. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs
  80. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs
  81. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs
  82. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs
  83. 6
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs
  84. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs
  85. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs
  86. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs
  87. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs
  88. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs
  89. 5
      src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs
  90. 19
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs
  91. 17
      src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs
  92. 26
      src/Squidex.Domain.Apps.Entities/SquidexDomainObjectBase.cs
  93. 21
      src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs
  94. 2
      src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs
  95. 3
      src/Squidex.Domain.Apps.Events/SquidexEvents.cs
  96. 28
      src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs
  97. 12
      src/Squidex.Domain.Apps.Events/SquidexHeaders.cs
  98. 12
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs
  99. 66
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs
  100. 27
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs

10
CHANGELOG.md

@ -1,12 +1,20 @@
# Changelog
## v1.2.0 - 2018-02-10
### Features
* **EventStore**: Event metadata are stored as json objects in MongoDB now and you cacn query by metadata.
* **Content**: Updated to state can be scheduled, e.g. to publish them.
> This releases will run a migration, which might take a while and also effects the events. We recommend to make a backup first.
## v1.1.7 - 2018-02-06
### Bugfixes
* **UI**: Checkbox style fixed.
## v1.1.6 - 2018-02-06
### Features

3
src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Reflection;
namespace Squidex.Domain.Apps.Core
{
public static class SquidexCoreModel
{
public static readonly Assembly Assembly = typeof(SquidexCoreModel).Assembly;
}
}

2
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs

@ -102,7 +102,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
EnsurePropertiesInitialized();
return fieldProperties.GetOrDefault(propertyName) ?? new PropertyDescriptor(new ObjectInstance(Engine) { Extensible = true }, true, false, true);
return fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this, new ContentFieldObject(this, new ContentFieldData(), false)));
}
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties()

2
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
if (value == null || !value.IsObject())
{
throw new JavaScriptException("Can only assign object to content data.");
throw new JavaScriptException("You can only assign objects to content data.");
}
var obj = value.AsObject();

4
src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -115,11 +115,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
}
catch (ParserException ex)
{
throw new ValidationException("Failed to execute script with javascript syntax error.", new ValidationError(ex.Message));
throw new ValidationException($"Failed to execute script with javascript syntax error: {ex.Message}", new ValidationError(ex.Message));
}
catch (JavaScriptException ex)
{
throw new ValidationException("Failed to execute script with javascript error.", new ValidationError(ex.Message));
throw new ValidationException($"Failed to execute script with javascript error: {ex.Message}", new ValidationError(ex.Message));
}
}

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

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

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());
entity.Version = newVersion;
entity.AppIdId = value.AppId.Id;
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>>
{
Filter.Eq(x => x.AppId, appId),
Filter.Eq(x => x.AppIdId, appId),
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]
[BsonElement("ai")]
[BsonRepresentation(BsonType.String)]
public Guid AppId { get; set; }
public Guid AppIdId { get; set; }
[BsonRequired]
[BsonElement("si")]
[BsonRepresentation(BsonType.String)]
public Guid SchemaId { get; set; }
public Guid SchemaIdId { get; set; }
[BsonRequired]
[BsonElement("rf")]
@ -62,6 +62,26 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonJson]
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]
[BsonElement("ct")]
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 Microsoft.OData.UriParser;
using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
@ -51,6 +52,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
await collection.Indexes.TryDropOneAsync("si_1_st_1_dl_1_dt_text");
await archiveCollection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.ScheduledTo));
await archiveCollection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.Id)
@ -59,13 +64,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await collection.Indexes.CreateOneAsync(
Index
.Text(x => x.DataText)
.Ascending(x => x.SchemaId)
.Ascending(x => x.SchemaIdId)
.Ascending(x => x.Status)
.Ascending(x => x.IsDeleted));
await collection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.SchemaId)
.Ascending(x => x.SchemaIdId)
.Ascending(x => x.Id)
.Ascending(x => x.IsDeleted)
.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)
{
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 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)
{
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();
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)
{
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();
contentEntity?.ParseData(schema.SchemaDef);
@ -167,6 +172,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
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()
{
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)
{
var schema = await GetSchemaAsync(contentEntity.AppId, contentEntity.SchemaId);
var schema = await GetSchemaAsync(contentEntity.AppIdId, contentEntity.SchemaIdId);
contentEntity?.ParseData(schema.SchemaDef);
@ -40,12 +40,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion)
{
if (value.SchemaId == Guid.Empty)
if (value.SchemaId.Id == Guid.Empty)
{
return;
}
var schema = await GetSchemaAsync(value.AppId, value.SchemaId);
var schema = await GetSchemaAsync(value.AppId.Id, value.SchemaId.Id);
var idData = value.Data?.ToIdModel(schema.SchemaDef, true);
@ -53,6 +53,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
var document = SimpleMapper.Map(value, new MongoContentEntity
{
AppIdId = value.AppId.Id,
SchemaIdId = value.SchemaId.Id,
IsDeleted = value.IsDeleted,
DocumentId = key.ToString(),
DataText = idData?.ToFullText(),
@ -92,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
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)
{

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>>
{
Filter.Eq(x => x.SchemaId, schemaId),
Filter.Eq(x => x.SchemaIdId, schemaId),
Filter.In(x => x.Status, status),
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,
IEntity,
IEntityWithAppRef,
IUpdateableEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithAppRef
IUpdateableEntityWithCreatedBy
{
[BsonElement]
[BsonRequired]

4
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -39,9 +39,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero });
}
public Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken))
public Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken ct = default(CancellationToken))
{
return Collection.Find(x => x.NextAttempt < now).ForEachAsync(callback, cancellationToken);
return Collection.Find(x => x.NextAttempt < now).ForEachAsync(callback, ct);
}
public async Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20)

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
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.AppId, value.AppId.Id)
.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
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.AppId, value.AppId.Id)
.Set(x => x.Name, value.Name)
.Set(x => x.IsDeleted, value.IsDeleted));
}

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

@ -88,11 +88,11 @@ namespace Squidex.Domain.Apps.Entities
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);
if (!IsFound(schema))
if (!IsFound(schema) || (schema.Snapshot.IsDeleted && !allowDeleted) || schema.Snapshot.AppId.Id != appId)
{
return null;
}

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

@ -179,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
}
else
{
var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, command.AppId.Id, a.Snapshot.Name, command.PlanId);
var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.Snapshot.Id, a.Snapshot.Name, command.PlanId);
if (result is PlanChangedResult)
{

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

@ -13,13 +13,12 @@ using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class AppDomainObject : DomainObjectBase<AppState>
public sealed class AppDomainObject : SquidexDomainObjectBase<AppState>
{
private readonly InitialPatterns initialPatterns;

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

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

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

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

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

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

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
{
public sealed class AssignContributor : AppAggregateCommand
public sealed class AssignContributor : AppCommand
{
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
{
public sealed class AttachClient : AppAggregateCommand
public sealed class AttachClient : AppCommand
{
public string Id { get; set; }
public string Secret { get; } = RandomHash.New();
public string Secret { get; set; }
public AttachClient()
{
Secret = RandomHash.New();
}
}
}

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

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

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
{
public sealed class CreateApp : SquidexCommand, IAggregateCommand
public sealed class CreateApp : AppCommand, IAggregateCommand
{
public Guid AppId { get; set; }
public string Name { get; set; }
public string Template { get; set; }
Guid IAggregateCommand.AggregateId
{
get { return AppId; }
}
public CreateApp()
{
AppId = Guid.NewGuid();

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

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

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

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

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

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

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

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

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
{
public sealed class UpdateClient : AppAggregateCommand
public sealed class UpdateClient : AppCommand
{
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
{
public sealed class UpdateLanguage : AppAggregateCommand
public sealed class UpdateLanguage : AppCommand
{
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
{
public sealed class UpdatePattern : AppAggregateCommand
public sealed class UpdatePattern : AppCommand
{
public Guid PatternId { get; set; }

8
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
if (string.IsNullOrWhiteSpace(command.Id))
{
error(new ValidationError("Client id must be defined.", nameof(command.Id)));
error(new ValidationError("Client id is required.", nameof(command.Id)));
}
else if (clients.ContainsKey(command.Id))
{
@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
if (string.IsNullOrWhiteSpace(command.Id))
{
error(new ValidationError("Client id must be defined.", nameof(command.Id)));
error(new ValidationError("Client id is required.", nameof(command.Id)));
}
});
}
@ -55,12 +55,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{
if (string.IsNullOrWhiteSpace(command.Id))
{
error(new ValidationError("Client id must be defined.", nameof(command.Id)));
error(new ValidationError("Client id is required.", nameof(command.Id)));
}
if (string.IsNullOrWhiteSpace(command.Name) && command.Permission == null)
{
error(new ValidationError("Either name or permission must be defined.", nameof(command.Name), nameof(command.Permission)));
error(new ValidationError("Either name or permission is required.", nameof(command.Name), nameof(command.Permission)));
}
if (command.Permission.HasValue && !command.Permission.Value.IsEnumValue())

192
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);
Task publishAsync(AppCommand command)
{
command.AppId = appId;
return context.CommandBus.PublishAsync(command);
}
return Task.WhenAll(
CreatePagesAsync(publishAsync, appId),
CreatePostsAsync(publishAsync, appId),
CreateClientAsync(publishAsync, appId));
CreatePagesAsync(context.CommandBus, appId),
CreatePostsAsync(context.CommandBus, appId),
CreateClientAsync(context.CommandBus, appId));
}
return TaskHelper.Done;
@ -57,16 +50,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase);
}
private static async Task CreateClientAsync(Func<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,
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,
Data =
@ -100,68 +93,70 @@ 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
{
Name = "posts",
Publish = true,
Properties = new SchemaProperties
{
Label = "Posts"
},
Fields = new List<CreateSchemaField>
{
new CreateSchemaField
{
new CreateSchemaField
Name = "title",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Name = "title",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Editor = StringFieldEditor.Input,
IsRequired = true,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Title"
}
},
new CreateSchemaField
Editor = StringFieldEditor.Input,
IsRequired = true,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Title"
}
},
new CreateSchemaField
{
Name = "slug",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Name = "slug",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Editor = StringFieldEditor.Slug,
IsRequired = false,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Slug"
}
Editor = StringFieldEditor.Slug,
IsRequired = false,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Slug (Autogenerated)"
},
new CreateSchemaField
IsDisabled = true
},
new CreateSchemaField
{
Name = "text",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Name = "text",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Editor = StringFieldEditor.RichText,
IsRequired = true,
IsListField = false,
Label = "Text"
}
Editor = StringFieldEditor.RichText,
IsRequired = true,
IsListField = false,
Label = "Text"
}
}
},
AppId = appId
};
await publishAsync(command);
await bus.PublishAsync(command);
var schemaId = new NamedId<Guid>(command.SchemaId, command.Name);
await publishAsync(new PublishSchema { SchemaId = schemaId });
await publishAsync(new ConfigureScripts
await bus.PublishAsync(new ConfigureScripts
{
SchemaId = schemaId,
SchemaId = schemaId.Id,
ScriptCreate = SlugScript,
ScriptUpdate = SlugScript
});
@ -169,7 +164,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
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
{
@ -179,58 +174,59 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
Label = "Pages"
},
Fields = new List<CreateSchemaField>
{
new CreateSchemaField
{
new CreateSchemaField
Name = "title",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Name = "title",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Editor = StringFieldEditor.Input,
IsRequired = true,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Title"
}
},
new CreateSchemaField
Editor = StringFieldEditor.Input,
IsRequired = true,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Title"
}
},
new CreateSchemaField
{
Name = "slug",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Name = "slug",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Editor = StringFieldEditor.Slug,
IsRequired = false,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Slug"
}
Editor = StringFieldEditor.Slug,
IsRequired = false,
IsListField = true,
MaxLength = 100,
MinLength = 0,
Label = "Slug (Autogenerated)"
},
new CreateSchemaField
IsDisabled = true
},
new CreateSchemaField
{
Name = "text",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Name = "text",
Partitioning = Partitioning.Invariant.Key,
Properties = new StringFieldProperties
{
Editor = StringFieldEditor.RichText,
IsRequired = true,
IsListField = false,
Label = "Text"
}
Editor = StringFieldEditor.RichText,
IsRequired = true,
IsListField = false,
Label = "Text"
}
}
},
AppId = appId
};
await publishAsync(command);
await bus.PublishAsync(command);
var schemaId = new NamedId<Guid>(command.SchemaId, command.Name);
await publishAsync(new PublishSchema { SchemaId = schemaId });
await publishAsync(new ConfigureScripts
await bus.PublishAsync(new ConfigureScripts
{
SchemaId = schemaId,
SchemaId = schemaId.Id,
ScriptCreate = SlugScript,
ScriptUpdate = SlugScript
});

14
src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs

@ -7,15 +7,15 @@
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class AssetDomainObject : DomainObjectBase<AssetState>
public sealed class AssetDomainObject : SquidexDomainObjectBase<AssetState>
{
public AssetDomainObject Create(CreateAsset command)
{
@ -74,6 +74,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
return this;
}
private void RaiseEvent(AppEvent @event)
{
if (@event.AppId == null)
{
@event.AppId = Snapshot.AppId;
}
RaiseEvent(Envelope.Create(@event));
}
private void VerifyNotCreated()
{
if (!string.IsNullOrWhiteSpace(Snapshot.FileName))

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
{
public abstract class AssetAggregateCommand : AppCommand, IAggregateCommand
public abstract class AssetCommand : SquidexCommand, IAggregateCommand
{
public Guid AssetId { get; set; }

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

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

2
src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{
if (string.IsNullOrWhiteSpace(command.FileName))
{
error(new ValidationError("Name must be defined.", nameof(command.FileName)));
error(new ValidationError("Name is required.", nameof(command.FileName)));
}
if (string.Equals(command.FileName, oldName))

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

@ -5,18 +5,21 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets
{
public interface IAssetEntity :
IEntity,
IEntityWithAppRef,
IEntityWithCreatedBy,
IEntityWithLastModifiedBy,
IEntityWithVersion,
IAssetInfo
{
NamedId<Guid> AppId { get; }
string MimeType { 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.Events;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
@ -18,11 +19,10 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
{
public class AssetState : DomainObjectState<AssetState>,
IAssetEntity,
IAssetInfo,
IUpdateableEntityWithAppRef
IAssetInfo
{
[JsonProperty]
public Guid AppId { get; set; }
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
public string FileName { get; set; }
@ -61,6 +61,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
SimpleMapper.Map(@event, this);
TotalSize += @event.FileSize;
AppId = @event.AppId;
}
protected void On(AssetUpdated @event)

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// =========================================================================
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.Contents.Commands
@ -12,5 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public sealed class ChangeContentStatus : ContentCommand
{
public Status Status { get; set; }
public Instant? 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
{
public abstract class ContentCommand : SchemaCommand, IAggregateCommand
public abstract class ContentCommand : SquidexCommand, IAggregateCommand
{
public Guid ContentId { get; set; }

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

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

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

@ -8,15 +8,15 @@
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentDomainObject : DomainObjectBase<ContentState>
public sealed class ContentDomainObject : SquidexDomainObjectBase<ContentState>
{
public ContentDomainObject Create(CreateContent command)
{
@ -45,7 +45,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
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;
}
@ -80,6 +87,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
return this;
}
private void RaiseEvent(SchemaEvent @event)
{
if (@event.AppId == null)
{
@event.AppId = Snapshot.AppId;
}
if (@event.SchemaId == null)
{
@event.SchemaId = Snapshot.SchemaId;
}
RaiseEvent(Envelope.Create(@event));
}
private void VerifyNotCreated()
{
if (Snapshot.Data != null)

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 AppId { get; set; }
public NamedId<Guid> AppId { get; set; }
public NamedId<Guid> SchemaId { get; set; }
public long Version { get; set; }
@ -26,14 +28,20 @@ namespace Squidex.Domain.Apps.Entities.Contents
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 LastModifiedBy { get; set; }
public NamedContentData Data { get; set; }
public Status Status { get; set; }
public static ContentEntity Create(CreateContent command, EntityCreatedResult<NamedContentData> result)
{
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,
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
{
@ -75,17 +84,15 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var errors = new List<ValidationError>();
var appId = command.AppId.Id;
var ctx =
new ValidationContext(
(contentIds, schemaId) =>
{
return QueryContentsAsync(appId, schemaId, contentIds);
return QueryContentsAsync(content.Snapshot.AppId.Id, schemaId, contentIds);
},
assetIds =>
{
return QueryAssetsAsync(appId, assetIds);
return QueryAssetsAsync(content.Snapshot.AppId.Id, assetIds);
});
if (partial)

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);
AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType);
AddContentUpdate(schemaId, schemaType, schemaName, inputType, resultType);
AddContentPatch(schemaId, schemaType, schemaName, inputType, resultType);
AddContentPublish(schemaId, schemaType, schemaName);
AddContentUnpublish(schemaId, schemaType, schemaName);
AddContentArchive(schemaId, schemaType, schemaName);
AddContentRestore(schemaId, schemaType, schemaName);
AddContentDelete(schemaId, schemaType, schemaName);
AddContentUpdate(schemaType, schemaName, inputType, resultType);
AddContentPatch(schemaType, schemaName, inputType, resultType);
AddContentPublish(schemaType, schemaName);
AddContentUnpublish(schemaType, schemaName);
AddContentArchive(schemaType, schemaName);
AddContentRestore(schemaType, schemaName);
AddContentDelete(schemaType, schemaName);
}
Description = "The app mutations.";
@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
var contentData = GetContentData(c);
var command = new CreateContent { SchemaId = schemaId, ContentId = Guid.NewGuid(), Data = contentData, Publish = argPublish };
var command = new CreateContent { SchemaId = schemaId, Data = contentData, Publish = argPublish };
var commandContext = await publish(command);
var result = commandContext.Result<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
{
@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
var contentId = c.GetArgument<Guid>("id");
var contentData = GetContentData(c);
var command = new UpdateContent { SchemaId = schemaId, ContentId = contentId, Data = contentData };
var command = new UpdateContent { ContentId = contentId, Data = contentData };
var commandContext = await publish(command);
var result = commandContext.Result<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
{
@ -179,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
var contentId = c.GetArgument<Guid>("id");
var contentData = GetContentData(c);
var command = new PatchContent { SchemaId = schemaId, ContentId = contentId, Data = contentData };
var command = new PatchContent { ContentId = contentId, Data = contentData };
var commandContext = await publish(command);
var result = commandContext.Result<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
{
@ -201,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
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);
}),
@ -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
{
@ -220,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
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);
}),
@ -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
{
@ -239,7 +239,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
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);
}),
@ -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
{
@ -258,7 +258,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
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);
}),
@ -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
{
@ -277,7 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
var contentId = c.GetArgument<Guid>("id");
var command = new DeleteContent { SchemaId = schemaId, ContentId = contentId };
var command = new DeleteContent { ContentId = contentId };
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.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
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)));
}
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.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents
{
public interface IContentEntity :
IEntity,
IEntityWithAppRef,
IEntityWithCreatedBy,
IEntityWithLastModifiedBy,
IEntityWithVersion
{
NamedId<Guid> AppId { get; }
NamedId<Guid> SchemaId { get; }
Status Status { get; }
Status? ScheduledTo { get; }
Instant? ScheduledAt { get; }
RefToken ScheduledBy { 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.Threading.Tasks;
using Microsoft.OData.UriParser;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
@ -27,5 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id);
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 Newtonsoft.Json;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Contents.State
{
public class ContentState : DomainObjectState<ContentState>,
IContentEntity,
IUpdateableEntityWithAppRef
IContentEntity
{
[JsonProperty]
public NamedContentData Data { get; set; }
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
public Guid AppId { get; set; }
public NamedId<Guid> SchemaId { get; set; }
[JsonProperty]
public Guid SchemaId { get; set; }
public NamedContentData Data { get; set; }
[JsonProperty]
public Status Status { get; set; }
[JsonProperty]
public Status? ScheduledTo { get; set; }
[JsonProperty]
public Instant? ScheduledAt { get; set; }
[JsonProperty]
public RefToken ScheduledBy { get; set; }
[JsonProperty]
public bool IsDeleted { get; set; }
protected void On(ContentCreated @event)
{
SchemaId = @event.SchemaId.Id;
SchemaId = @event.SchemaId;
Data = @event.Data;
AppId = @event.AppId;
}
protected void On(ContentUpdated @event)
@ -46,9 +58,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
Data = @event.Data;
}
protected void On(ContentStatusScheduled @event)
{
ScheduledAt = @event.DueTime;
ScheduledBy = @event.Actor;
ScheduledTo = @event.Status;
}
protected void On(ContentStatusChanged @event)
{
Status = @event.Status;
ScheduledAt = null;
ScheduledBy = null;
ScheduledTo = null;
}
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
{
SetId(entity, headers);
SetAppId(entity, @event);
SetCreated(entity, headers);
SetCreatedBy(entity, @event);
SetLastModified(entity, headers);
@ -76,13 +75,5 @@ namespace Squidex.Domain.Apps.Entities
withModifiedBy.LastModifiedBy = @event.Actor;
}
}
private static void SetAppId(IEntity entity, SquidexEvent @event)
{
if (entity is IUpdateableEntityWithAppRef appEntity && @event is AppEvent appEvent)
{
appEntity.AppId = appEvent.AppId.Id;
}
}
}
}

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

@ -1,17 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities
{
public abstract class AppCommand : SquidexCommand
public interface IAppCommand : ICommand
{
public NamedId<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<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id);
Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false);
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
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities
{
public abstract class SchemaCommand : AppCommand
public interface ISchemaCommand : ICommand
{
public NamedId<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 Squidex.Infrastructure;
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()
{
RuleId = Guid.NewGuid();

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

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

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

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

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

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

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
{
public abstract class RuleAggregateCommand : AppCommand, IAggregateCommand
public abstract class RuleCommand : SquidexCommand, IAggregateCommand
{
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
{
public abstract class RuleEditCommand : RuleAggregateCommand
public abstract class RuleEditCommand : RuleCommand
{
public RuleTrigger Trigger { get; set; }

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands;
@ -22,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{
if (command.Trigger == null)
{
error(new ValidationError("Trigger must be defined.", nameof(command.Trigger)));
error(new ValidationError("Trigger is required.", nameof(command.Trigger)));
}
else
{
@ -33,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (command.Action == null)
{
error(new ValidationError("Trigger must be defined.", nameof(command.Action)));
error(new ValidationError("Trigger is required.", nameof(command.Action)));
}
else
{
@ -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));
@ -52,12 +53,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{
if (command.Trigger == null && command.Action == null)
{
error(new ValidationError("Either trigger or action must be defined.", nameof(command.Trigger), nameof(command.Action)));
error(new ValidationError("Either trigger or action is required.", nameof(command.Trigger), nameof(command.Action)));
}
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);
}

18
src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs

@ -31,17 +31,17 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (string.IsNullOrWhiteSpace(action.ApiKey))
{
errors.Add(new ValidationError("Api key must be defined.", nameof(action.ApiKey)));
errors.Add(new ValidationError("Api key is required.", nameof(action.ApiKey)));
}
if (string.IsNullOrWhiteSpace(action.AppId))
{
errors.Add(new ValidationError("Application ID key must be defined.", nameof(action.AppId)));
errors.Add(new ValidationError("Application ID key is required.", nameof(action.AppId)));
}
if (string.IsNullOrWhiteSpace(action.IndexName))
{
errors.Add(new ValidationError("Index name must be defined.", nameof(action.ApiKey)));
errors.Add(new ValidationError("Index name is required.", nameof(action.IndexName)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
@ -53,12 +53,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (string.IsNullOrWhiteSpace(action.ConnectionString))
{
errors.Add(new ValidationError("Connection string must be defined.", nameof(action.ConnectionString)));
errors.Add(new ValidationError("Connection string is required.", nameof(action.ConnectionString)));
}
if (string.IsNullOrWhiteSpace(action.Queue))
{
errors.Add(new ValidationError("Queue must be defined.", nameof(action.Queue)));
errors.Add(new ValidationError("Queue is required.", nameof(action.Queue)));
}
else if (!Regex.IsMatch(action.Queue, "^[a-z][a-z0-9]{2,}(\\-[a-z0-9]+)*$"))
{
@ -74,12 +74,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (string.IsNullOrWhiteSpace(action.ApiKey))
{
errors.Add(new ValidationError("Api key must be defined.", nameof(action.ApiKey)));
errors.Add(new ValidationError("Api key is required.", nameof(action.ApiKey)));
}
if (string.IsNullOrWhiteSpace(action.ServiceId))
{
errors.Add(new ValidationError("Service name must be defined.", nameof(action.ServiceId)));
errors.Add(new ValidationError("Service ID is required.", nameof(action.ServiceId)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (action.WebhookUrl == null || !action.WebhookUrl.IsAbsoluteUri)
{
errors.Add(new ValidationError("Webhook Url must be specified and absolute.", nameof(action.WebhookUrl)));
errors.Add(new ValidationError("Webhook Url is required and must be an absolute URL.", nameof(action.WebhookUrl)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
@ -103,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (action.Url == null || !action.Url.IsAbsoluteUri)
{
errors.Add(new ValidationError("Url must be specified and absolute.", nameof(action.Url)));
errors.Add(new ValidationError("Url is required and must be an absolute URL.", nameof(action.Url)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);

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

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

2
src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleEventRepository.cs

@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Repositories
Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextCall);
Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken));
Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken ct = default(CancellationToken));
Task<int> CountByAppAsync(Guid appId);

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 =>
{
await GuardRule.CanUpdate(command, appProvider);
await GuardRule.CanUpdate(command, r.Snapshot.AppId.Id, appProvider);
r.Update(command);
});

4
src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuer.cs

@ -72,13 +72,13 @@ namespace Squidex.Domain.Apps.Entities.Rules
timer.SkipCurrentDelay();
}
private async Task QueryAsync(CancellationToken cancellationToken)
private async Task QueryAsync(CancellationToken ct)
{
try
{
var now = clock.GetCurrentInstant();
await ruleEventRepository.QueryPendingAsync(now, requestBlock.SendAsync, cancellationToken);
await ruleEventRepository.QueryPendingAsync(now, requestBlock.SendAsync, ct);
}
catch (Exception ex)
{

14
src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs

@ -7,15 +7,15 @@
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Rules
{
public sealed class RuleDomainObject : DomainObjectBase<RuleState>
public sealed class RuleDomainObject : SquidexDomainObjectBase<RuleState>
{
public void Create(CreateRule command)
{
@ -52,6 +52,16 @@ namespace Squidex.Domain.Apps.Entities.Rules
RaiseEvent(SimpleMapper.Map(command, new RuleDeleted()));
}
private void RaiseEvent(AppEvent @event)
{
if (@event.AppId == null)
{
@event.AppId = Snapshot.AppId;
}
RaiseEvent(Envelope.Create(@event));
}
private void VerifyNotCreated()
{
if (Snapshot.RuleDef != null)

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

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
{
public sealed class AddField : SchemaAggregateCommand
public sealed class AddField : SchemaCommand
{
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
{
public sealed class ConfigureScripts : SchemaAggregateCommand
public sealed class ConfigureScripts : SchemaCommand
{
public string ScriptQuery { get; set; }

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

@ -7,25 +7,22 @@
using System;
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>;
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 SchemaProperties Properties { get; set; }
public string Name { get; set; }
Guid IAggregateCommand.AggregateId
{
get { return SchemaId; }
}
public bool Publish { get; set; }
public CreateSchema()
{

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

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

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

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

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

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

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
{
public sealed class ReorderFields : SchemaAggregateCommand
public sealed class ReorderFields : SchemaCommand
{
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
{
public class AppAggregateCommand : AppCommand, IAggregateCommand
public abstract class SchemaCommand : SquidexCommand, IAggregateCommand
{
public Guid SchemaId { get; set; }
Guid IAggregateCommand.AggregateId
{
get { return AppId.Id; }
get { return SchemaId; }
}
}
}

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

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

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
{
public sealed class UpdateSchema : SchemaAggregateCommand
public sealed class UpdateSchema : SchemaCommand
{
public SchemaProperties Properties { get; set; }
}

2
src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs

@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (properties.AspectWidth.HasValue != properties.AspectHeight.HasValue)
{
yield return new ValidationError("Aspect width and height must be defined.",
yield return new ValidationError("Aspect width and height is required.",
nameof(properties.AspectWidth),
nameof(properties.AspectHeight));
}

4
src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs

@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (field.Properties == null)
{
error(new ValidationError("Properties must be defined.", $"{prefix}.{nameof(field.Properties)}"));
error(new ValidationError("Properties is required.", $"{prefix}.{nameof(field.Properties)}"));
}
var propertyErrors = FieldPropertiesValidator.Validate(field.Properties);
@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{
if (command.FieldIds == null)
{
error(new ValidationError("Field ids must be specified.", nameof(command.FieldIds)));
error(new ValidationError("Field ids is required.", nameof(command.FieldIds)));
}
if (command.FieldIds.Count != schema.Fields.Count || command.FieldIds.Any(x => !schema.FieldsById.ContainsKey(x)))

4
src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (command.Properties == null)
{
error(new ValidationError("Properties must be defined.", nameof(command.Properties)));
error(new ValidationError("Properties is required.", nameof(command.Properties)));
}
var propertyErrors = FieldPropertiesValidator.Validate(command.Properties);
@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{
if (command.Properties == null)
{
error(new ValidationError("Properties must be defined.", nameof(command.Properties)));
error(new ValidationError("Properties is required.", nameof(command.Properties)));
}
var propertyErrors = FieldPropertiesValidator.Validate(command.Properties);

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

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

19
src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs

@ -10,15 +10,15 @@ using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Schemas
{
public sealed class SchemaDomainObject : DomainObjectBase<SchemaState>
public sealed class SchemaDomainObject : SquidexDomainObjectBase<SchemaState>
{
private readonly FieldRegistry registry;
@ -190,6 +190,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas
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()
{
if (Snapshot.SchemaDef != null)

17
src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs

@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
@ -18,16 +19,13 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Schemas.State
{
public class SchemaState : DomainObjectState<SchemaState>,
ISchemaEntity,
IUpdateableEntityWithAppRef,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy
ISchemaEntity
{
[JsonProperty]
public string Name { get; set; }
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
public Guid AppId { get; set; }
public string Name { get; set; }
[JsonProperty]
public int TotalFields { get; set; } = 0;
@ -70,6 +68,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
schema = schema.Update(@event.Properties);
}
if (@event.Publish)
{
schema = schema.Publish();
}
if (@event.Fields != null)
{
foreach (var eventField in @event.Fields)
@ -103,6 +106,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
}
SchemaDef = schema;
AppId = @event.AppId;
}
protected void On(FieldAdded @event, FieldRegistry registry)

26
src/Squidex.Domain.Apps.Entities/SquidexDomainObjectBase.cs

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities
{
public abstract class SquidexDomainObjectBase<T> : DomainObjectBase<T> where T : IDomainState, new()
{
public override void RaiseEvent(Envelope<IEvent> @event)
{
if (@event.Payload is AppEvent appEvent)
{
@event.SetAppId(appEvent.AppId.Id);
}
base.RaiseEvent(@event);
}
}
}

21
src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Contents
{
[EventType(nameof(ContentStatusScheduled))]
public sealed class ContentStatusScheduled : ContentEvent
{
public Status Status { get; set; }
public Instant DueTime { get; set; }
}
}

2
src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs

@ -19,5 +19,7 @@ namespace Squidex.Domain.Apps.Events.Schemas
public SchemaFields Fields { get; set; }
public SchemaProperties Properties { get; set; }
public bool Publish { get; set; }
}
}

3
src/Squidex.Domain.Apps.Events/SquidexEvents.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Reflection;
namespace Squidex.Domain.Apps.Events
{
public static class SquidexEvents
{
public static readonly Assembly Assembly = typeof(SquidexEvents).Assembly;
}
}

28
src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs

@ -0,0 +1,28 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Globalization;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events
{
public static class SquidexHeaderExtensions
{
public static Guid AppId(this EnvelopeHeaders headers)
{
return headers[SquidexHeaders.AppId].ToGuid(CultureInfo.InvariantCulture);
}
public static Envelope<T> SetAppId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(SquidexHeaders.AppId, value);
return envelope;
}
}
}

12
src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs → src/Squidex.Domain.Apps.Events/SquidexHeaders.cs

@ -1,16 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Entities
namespace Squidex.Domain.Apps.Events
{
public interface IEntityWithAppRef
public static class SquidexHeaders
{
Guid AppId { get; }
public static readonly string AppId = "AppId";
}
}
}

12
src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Text;
using EventStore.ClientAPI;
using EventStoreData = EventStore.ClientAPI.EventData;
@ -20,7 +21,7 @@ namespace Squidex.Infrastructure.EventSourcing
var body = Encoding.UTF8.GetString(@event.Data);
var meta = Encoding.UTF8.GetString(@event.Metadata);
var eventData = new EventData { Type = @event.EventType, EventId = @event.EventId, Payload = body, Metadata = meta };
var eventData = new EventData { Type = @event.EventType, Payload = body, Metadata = meta };
return new StoredEvent(
resolvedEvent.OriginalEventNumber.ToString(),
@ -30,13 +31,10 @@ namespace Squidex.Infrastructure.EventSourcing
public static EventStoreData Write(EventData eventData)
{
var body = Encoding.UTF8.GetBytes(eventData.Payload);
var meta = Encoding.UTF8.GetBytes(eventData.Metadata);
var body = Encoding.UTF8.GetBytes(eventData.Payload.ToString());
var meta = Encoding.UTF8.GetBytes(eventData.Metadata.ToString());
return new EventStoreData(
eventData.EventId,
eventData.Type,
true, body, meta);
return new EventStoreData(Guid.NewGuid(), eventData.Type, true, body, meta);
}
}
}

66
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs

@ -11,7 +11,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EventStore.ClientAPI;
using EventStore.ClientAPI.Projections;
namespace Squidex.Infrastructure.EventSourcing
{
@ -20,18 +19,18 @@ namespace Squidex.Infrastructure.EventSourcing
private const int WritePageSize = 500;
private const int ReadPageSize = 500;
private readonly IEventStoreConnection connection;
private readonly string projectionHost;
private readonly string prefix;
private ProjectionsManager projectionsManager;
private ProjectionClient projectionClient;
public GetEventStore(IEventStoreConnection connection, string prefix, string projectionHost)
{
Guard.NotNull(connection, nameof(connection));
this.connection = connection;
this.projectionHost = projectionHost;
this.prefix = prefix?.Trim(' ', '-').WithFallback("squidex");
projectionClient = new ProjectionClient(connection, prefix, projectionHost);
}
public void Initialize()
@ -45,50 +44,43 @@ namespace Squidex.Infrastructure.EventSourcing
throw new ConfigurationException("Cannot connect to event store.", ex);
}
try
{
projectionsManager = connection.GetProjectionsManagerAsync(projectionHost).Result;
projectionsManager.ListAllAsync(connection.Settings.DefaultUserCredentials).Wait();
}
catch (Exception ex)
{
throw new ConfigurationException($"Cannot connect to event store projections: {projectionHost}.", ex);
}
projectionClient.ConnectAsync().Wait();
}
public IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position = null)
{
return new GetEventStoreSubscription(connection, subscriber, projectionsManager, prefix, position, streamFilter);
return new GetEventStoreSubscription(connection, subscriber, projectionClient, prefix, position, streamFilter);
}
public async Task GetEventsAsync(Func<StoredEvent, Task> callback, string streamFilter = null, string position = null, CancellationToken cancellationToken = default(CancellationToken))
public Task CreateIndexAsync(string property)
{
var streamName = await connection.CreateProjectionAsync(projectionsManager, prefix, streamFilter);
return projectionClient.CreateProjectionAsync(property, string.Empty);
}
var sliceStart = ProjectionHelper.ParsePosition(position);
public async Task QueryAsync(Func<StoredEvent, Task> callback, string property, object value, string position = null, CancellationToken ct = default(CancellationToken))
{
var streamName = await projectionClient.CreateProjectionAsync(property, value);
StreamEventsSlice currentSlice;
do
{
currentSlice = await connection.ReadStreamEventsForwardAsync(streamName, sliceStart, ReadPageSize, true);
var sliceStart = projectionClient.ParsePosition(position);
if (currentSlice.Status == SliceReadStatus.Success)
{
sliceStart = currentSlice.NextEventNumber;
await QueryAsync(callback, streamName, sliceStart, ct);
}
foreach (var resolved in currentSlice.Events)
{
var storedEvent = Formatter.Read(resolved);
public async Task QueryAsync(Func<StoredEvent, Task> callback, string streamFilter = null, string position = null, CancellationToken ct = default(CancellationToken))
{
var streamName = await projectionClient.CreateProjectionAsync(streamFilter);
await callback(storedEvent);
}
}
}
while (!currentSlice.IsEndOfStream && !cancellationToken.IsCancellationRequested);
var sliceStart = projectionClient.ParsePosition(position);
await QueryAsync(callback, streamName, sliceStart, ct);
}
private Task QueryAsync(Func<StoredEvent, Task> callback, string streamName, long sliceStart, CancellationToken ct)
{
return QueryAsync(callback, GetStreamName(streamName), sliceStart, ct);
}
public async Task<IReadOnlyList<StoredEvent>> GetEventsAsync(string streamName, long streamPosition = 0)
public async Task<IReadOnlyList<StoredEvent>> QueryAsync(string streamName, long streamPosition = 0)
{
var result = new List<StoredEvent>();
@ -97,7 +89,7 @@ namespace Squidex.Infrastructure.EventSourcing
StreamEventsSlice currentSlice;
do
{
currentSlice = await connection.ReadStreamEventsForwardAsync(GetStreamName(streamName), sliceStart, ReadPageSize, false);
currentSlice = await connection.ReadStreamEventsForwardAsync(streamName, sliceStart, ReadPageSize, false);
if (currentSlice.Status == SliceReadStatus.Success)
{
@ -116,12 +108,12 @@ namespace Squidex.Infrastructure.EventSourcing
return result;
}
public Task AppendEventsAsync(Guid commitId, string streamName, ICollection<EventData> events)
public Task AppendAsync(Guid commitId, string streamName, ICollection<EventData> events)
{
return AppendEventsInternalAsync(streamName, EtagVersion.Any, events);
}
public Task AppendEventsAsync(Guid commitId, string streamName, long expectedVersion, ICollection<EventData> events)
public Task AppendAsync(Guid commitId, string streamName, long expectedVersion, ICollection<EventData> events)
{
Guard.GreaterEquals(expectedVersion, -1, nameof(expectedVersion));

27
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs

@ -8,33 +8,32 @@
using System.Threading.Tasks;
using EventStore.ClientAPI;
using EventStore.ClientAPI.Exceptions;
using EventStore.ClientAPI.Projections;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing
{
internal sealed class GetEventStoreSubscription : IEventSubscription
{
private readonly IEventStoreConnection eventStoreConnection;
private readonly IEventSubscriber eventSubscriber;
private readonly IEventStoreConnection connection;
private readonly IEventSubscriber subscriber;
private readonly EventStoreCatchUpSubscription subscription;
private readonly long? position;
public GetEventStoreSubscription(
IEventStoreConnection eventStoreConnection,
IEventSubscriber eventSubscriber,
ProjectionsManager projectionsManager,
IEventStoreConnection connection,
IEventSubscriber subscriber,
ProjectionClient projectionClient,
string prefix,
string position,
string streamFilter)
{
Guard.NotNull(eventSubscriber, nameof(eventSubscriber));
Guard.NotNull(subscriber, nameof(subscriber));
this.eventStoreConnection = eventStoreConnection;
this.eventSubscriber = eventSubscriber;
this.position = ProjectionHelper.ParsePositionOrNull(position);
this.connection = connection;
this.position = projectionClient.ParsePositionOrNull(position);
this.subscriber = subscriber;
var streamName = eventStoreConnection.CreateProjectionAsync(projectionsManager, prefix, streamFilter).Result;
var streamName = projectionClient.CreateProjectionAsync(streamFilter).Result;
subscription = SubscribeToStream(streamName);
}
@ -54,12 +53,12 @@ namespace Squidex.Infrastructure.EventSourcing
{
var settings = CatchUpSubscriptionSettings.Default;
return eventStoreConnection.SubscribeToStreamFrom(streamName, position, settings,
return connection.SubscribeToStreamFrom(streamName, position, settings,
(s, e) =>
{
var storedEvent = Formatter.Read(e);
eventSubscriber.OnEventAsync(this, storedEvent).Wait();
subscriber.OnEventAsync(this, storedEvent).Wait();
}, null,
(s, reason, ex) =>
{
@ -68,7 +67,7 @@ namespace Squidex.Infrastructure.EventSourcing
{
ex = ex ?? new ConnectionClosedException($"Subscription closed with reason {reason}.");
eventSubscriber.OnErrorAsync(this, ex);
subscriber.OnErrorAsync(this, ex);
}
});
}

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

Loading…
Cancel
Save