Browse Source

Merge pull request #249 from Squidex/orleans3

Orleans3
pull/260/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
4abf722d4e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. BIN
      libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg
  2. 1
      libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg.sha512
  3. 25
      libs/orleansdashboard/2.0.0-rc1/orleansdashboard.nuspec
  4. 2
      src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  5. 9
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  6. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
  7. 14
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  8. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  9. 47
      src/Squidex.Domain.Apps.Entities/AppProvider.cs
  10. 10
      src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
  11. 6
      src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs
  12. 18
      src/Squidex.Domain.Apps.Entities/Apps/IAppGrain.cs
  13. 8
      src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  14. 10
      src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs
  15. 8
      src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs
  16. 4
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  17. 30
      src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs
  18. 9
      src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs
  19. 15
      src/Squidex.Domain.Apps.Entities/Contents/IContentSchedulerGrain.cs
  20. 11
      src/Squidex.Domain.Apps.Entities/Rules/IRuleDequeuerGrain.cs
  21. 18
      src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs
  22. 42
      src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs
  23. 10
      src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs
  24. 18
      src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs
  25. 10
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs
  26. 3
      src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  27. 10
      src/Squidex.Domain.Apps.Entities/SquidexEntities.cs
  28. 4
      src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  29. 4
      src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  30. 2
      src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  31. 2
      src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
  32. 4
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
  33. 2
      src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
  34. 3
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs
  35. 2
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs
  36. 2
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
  37. 2
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs
  38. 5
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonWriter.cs
  39. 2
      src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  40. 3
      src/Squidex.Infrastructure/Assets/AssetFile.cs
  41. 34
      src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
  42. 36
      src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs
  43. 14
      src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
  44. 12
      src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs
  45. 63
      src/Squidex.Infrastructure/Commands/SyncedGrainCommandMiddleware.cs
  46. 40
      src/Squidex.Infrastructure/EventSourcing/DefaultEventNotifier.cs
  47. 133
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
  48. 90
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs
  49. 104
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs
  50. 29
      src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs
  51. 27
      src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs
  52. 40
      src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs
  53. 42
      src/Squidex.Infrastructure/EventSourcing/Grains/WrapperSubscription.cs
  54. 4
      src/Squidex.Infrastructure/EventSourcing/IEventNotifier.cs
  55. 2
      src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs
  56. 18
      src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs
  57. 5
      src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs
  58. 33
      src/Squidex.Infrastructure/Orleans/Bootstrap.cs
  59. 27
      src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs
  60. 26
      src/Squidex.Infrastructure/Orleans/GrainOfString.cs
  61. 9
      src/Squidex.Infrastructure/Orleans/IBackgroundGrain.cs
  62. 12
      src/Squidex.Infrastructure/Orleans/J.cs
  63. 11
      src/Squidex.Infrastructure/Orleans/JExtensions.cs
  64. 90
      src/Squidex.Infrastructure/Orleans/J{T}.cs
  65. 11
      src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  66. 31
      src/Squidex.Infrastructure/States/IStateFactory.cs
  67. 140
      src/Squidex.Infrastructure/States/StateFactory.cs
  68. 16
      src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs
  69. 26
      src/Squidex/AppConfiguration.cs
  70. 10
      src/Squidex/AppServices.cs
  71. 27
      src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs
  72. 3
      src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs
  73. 43
      src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs
  74. 27
      src/Squidex/Areas/OrleansDashboard/Startup.cs
  75. 2
      src/Squidex/Config/Constants.cs
  76. 4
      src/Squidex/Config/Domain/AssetServices.cs
  77. 111
      src/Squidex/Config/Domain/EntitiesServices.cs
  78. 20
      src/Squidex/Config/Domain/EventStoreServices.cs
  79. 69
      src/Squidex/Config/Domain/InfrastructureServices.cs
  80. 64
      src/Squidex/Config/Domain/LoggingServices.cs
  81. 40
      src/Squidex/Config/Domain/PubSubServices.cs
  82. 141
      src/Squidex/Config/Domain/ReadServices.cs
  83. 55
      src/Squidex/Config/Domain/RuleServices.cs
  84. 34
      src/Squidex/Config/Domain/SerializationServices.cs
  85. 24
      src/Squidex/Config/Domain/StoreServices.cs
  86. 37
      src/Squidex/Config/Domain/SubscriptionServices.cs
  87. 60
      src/Squidex/Config/Orleans/ClientWrapper.cs
  88. 40
      src/Squidex/Config/Orleans/OrleansServices.cs
  89. 42
      src/Squidex/Config/Orleans/SiloServices.cs
  90. 105
      src/Squidex/Config/Orleans/SiloWrapper.cs
  91. 16
      src/Squidex/Program.cs
  92. 59
      src/Squidex/Squidex.csproj
  93. 6
      src/Squidex/WebStartup.cs
  94. 6
      src/Squidex/app-config/helpers.js
  95. 2
      src/Squidex/app-config/karma.conf.js
  96. 3
      src/Squidex/app-config/webpack.config.js
  97. 5
      src/Squidex/app-config/webpack.run.base.js
  98. 5
      src/Squidex/app-config/webpack.run.dev.js
  99. 2
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts
  100. 23
      src/Squidex/appsettings.json

BIN
libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg

Binary file not shown.

1
libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg.sha512

@ -0,0 +1 @@
oeHEL1XH6DwEv4Rk6JjAABzcpTdBI3Zmoz3tyn+20vBUcvsdmKQMFp8I1rBZmAeOJ9NSvvRYf8LHDM2UtRTbvw==

25
libs/orleansdashboard/2.0.0-rc1/orleansdashboard.nuspec

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>OrleansDashboard</id>
<version>2.0.0-rc1</version>
<authors>OrleansContrib</authors>
<owners>OrleansContrib</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://opensource.org/licenses/MIT</licenseUrl>
<projectUrl>https://github.com/OrleansContrib/OrleansDashboard</projectUrl>
<iconUrl>http://dotnet.github.io/orleans/assets/logo.png</iconUrl>
<description>An admin dashboard for Microsoft Orleans</description>
<copyright>Copyright © 2017</copyright>
<tags>orleans dashboard metrics monitor</tags>
<repository url="https://github.com/OrleansContrib/OrleansDashboard" />
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Microsoft.AspNetCore" version="2.0.1" exclude="Build,Analyzers" />
<dependency id="Microsoft.Orleans.Core" version="2.0.0-rc1" exclude="Build,Analyzers" />
<dependency id="Microsoft.Orleans.OrleansCodeGenerator.Build" version="2.0.0-rc1" exclude="Build,Analyzers" />
<dependency id="Microsoft.Orleans.OrleansRuntime" version="2.0.0-rc1" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
</package>

2
src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -8,7 +8,7 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fody" Version="2.3.24" />
<PackageReference Include="Fody" Version="2.4.6" />
<PackageReference Include="Freezable.Fody" Version="1.8.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />

9
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs

@ -63,7 +63,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
var responseString = await response.Content.ReadAsStringAsync();
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, responseString, TimeSpan.Zero, false);
return (requestDump, null);
Exception ex = null;
if (!response.IsSuccessStatusCode)
{
ex = new HttpRequestException($"Response code does not indicate success: {(int)response.StatusCode} ({response.StatusCode}).");
}
return (requestDump, ex);
}
catch (Exception ex)
{

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs

@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{
protected override bool Triggers(Envelope<AppEvent> @event, ContentChangedTrigger trigger)
{
if (trigger.HandleAll)
if (trigger.HandleAll && @event.Payload is ContentEvent)
{
return true;
}

14
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -13,18 +13,18 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="4.2.1" />
<PackageReference Include="Elasticsearch.Net" Version="6.0.1" />
<PackageReference Include="Algolia.Search" Version="4.2.2" />
<PackageReference Include="Elasticsearch.Net" Version="6.0.2" />
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="NJsonSchema" Version="9.10.19" />
<PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
<PackageReference Include="NJsonSchema" Version="9.10.35" />
<PackageReference Include="NodaTime" Version="2.2.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="WindowsAzure.Storage" Version="8.7.0" />
<PackageReference Include="WindowsAzure.Storage" Version="9.1.0" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

2
src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -15,7 +15,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.4.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.1" />
<PackageReference Include="MongoDB.Driver" Version="2.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />

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

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.Rules;
@ -16,52 +17,50 @@ using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities
{
public sealed class AppProvider : IAppProvider
{
private readonly IGrainFactory grainFactory;
private readonly IAppRepository appRepository;
private readonly IRuleRepository ruleRepository;
private readonly ISchemaRepository schemaRepository;
private readonly IStateFactory stateFactory;
public AppProvider(
IGrainFactory grainFactory,
IAppRepository appRepository,
ISchemaRepository schemaRepository,
IStateFactory stateFactory,
IRuleRepository ruleRepository)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(appRepository, nameof(appRepository));
Guard.NotNull(schemaRepository, nameof(schemaRepository));
Guard.NotNull(stateFactory, nameof(stateFactory));
Guard.NotNull(ruleRepository, nameof(ruleRepository));
this.grainFactory = grainFactory;
this.appRepository = appRepository;
this.schemaRepository = schemaRepository;
this.stateFactory = stateFactory;
this.ruleRepository = ruleRepository;
}
public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id)
{
var app = await stateFactory.GetSingleAsync<AppGrain>(appId);
var app = await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync();
if (!IsFound(app))
if (!IsFound(app.Value))
{
return (null, null);
}
var schema = await stateFactory.GetSingleAsync<SchemaGrain>(id);
var schema = await grainFactory.GetGrain<ISchemaGrain>(id).GetStateAsync();
if (!IsFound(schema) || schema.Snapshot.IsDeleted)
if (!IsFound(schema.Value) || schema.Value.IsDeleted)
{
return (null, null);
}
return (app.Snapshot, schema.Snapshot);
return (app.Value, schema.Value);
}
public async Task<IAppEntity> GetAppAsync(string appName)
@ -73,7 +72,7 @@ namespace Squidex.Domain.Apps.Entities
return null;
}
return (await stateFactory.GetSingleAsync<AppGrain>(appId)).Snapshot;
return (await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync()).Value;
}
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name)
@ -85,19 +84,19 @@ namespace Squidex.Domain.Apps.Entities
return null;
}
return (await stateFactory.GetSingleAsync<SchemaGrain>(schemaId)).Snapshot;
return (await grainFactory.GetGrain<ISchemaGrain>(schemaId).GetStateAsync()).Value;
}
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false)
{
var schema = await stateFactory.GetSingleAsync<SchemaGrain>(id);
var schema = await grainFactory.GetGrain<ISchemaGrain>(id).GetStateAsync();
if (!IsFound(schema) || (schema.Snapshot.IsDeleted && !allowDeleted) || schema.Snapshot.AppId.Id != appId)
if (!IsFound(schema.Value) || (schema.Value.IsDeleted && !allowDeleted) || schema.Value.AppId.Id != appId)
{
return null;
}
return schema.Snapshot;
return schema.Value;
}
public async Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId)
@ -106,9 +105,9 @@ namespace Squidex.Domain.Apps.Entities
var schemas =
await Task.WhenAll(
ids.Select(id => stateFactory.GetSingleAsync<SchemaGrain>(id)));
ids.Select(id => grainFactory.GetGrain<ISchemaGrain>(id).GetStateAsync()));
return schemas.Where(IsFound).Select(s => (ISchemaEntity)s.Snapshot).ToList();
return schemas.Where(s => IsFound(s.Value)).Select(s => (ISchemaEntity)s.Value).ToList();
}
public async Task<List<IRuleEntity>> GetRulesAsync(Guid appId)
@ -117,9 +116,9 @@ namespace Squidex.Domain.Apps.Entities
var rules =
await Task.WhenAll(
ids.Select(id => stateFactory.GetSingleAsync<RuleGrain>(id)));
ids.Select(id => grainFactory.GetGrain<IRuleGrain>(id).GetStateAsync()));
return rules.Where(IsFound).Select(r => (IRuleEntity)r.Snapshot).ToList();
return rules.Where(r => IsFound(r.Value)).Select(r => (IRuleEntity)r.Value).ToList();
}
public async Task<List<IAppEntity>> GetUserApps(string userId)
@ -128,9 +127,9 @@ namespace Squidex.Domain.Apps.Entities
var apps =
await Task.WhenAll(
ids.Select(id => stateFactory.GetSingleAsync<AppGrain>(id)));
ids.Select(id => grainFactory.GetGrain<IAppGrain>(id).GetStateAsync()));
return apps.Where(IsFound).Select(a => (IAppEntity)a.Snapshot).ToList();
return apps.Where(a => IsFound(a.Value)).Select(a => (IAppEntity)a.Value).ToList();
}
private Task<Guid> GetAppIdAsync(string name)
@ -143,9 +142,9 @@ namespace Squidex.Domain.Apps.Entities
return await schemaRepository.FindSchemaIdAsync(appId, name);
}
private static bool IsFound(IDomainObjectGrain app)
private static bool IsFound(IEntityWithVersion entity)
{
return app.Version > EtagVersion.Empty;
return entity.Version > EtagVersion.Empty;
}
}
}

10
src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs

@ -18,13 +18,14 @@ using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps
{
public class AppGrain : DomainObjectGrain<AppState>
public class AppGrain : DomainObjectGrain<AppState>, IAppGrain
{
private readonly InitialPatterns initialPatterns;
private readonly IAppProvider appProvider;
@ -54,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.initialPatterns = initialPatterns;
}
public override Task<object> ExecuteAsync(IAggregateCommand command)
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
switch (command)
{
@ -310,5 +311,10 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
ApplySnapshot(Snapshot.Apply(@event));
}
public Task<J<IAppEntity>> GetStateAsync()
{
return Task.FromResult(new J<IAppEntity>(Snapshot));
}
}
}

6
src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs

@ -9,7 +9,11 @@ using Squidex.Domain.Apps.Core.Apps;
namespace Squidex.Domain.Apps.Entities.Apps
{
public interface IAppEntity : IEntity, IEntityWithVersion
public interface IAppEntity :
IEntity,
IEntityWithCreatedBy,
IEntityWithLastModifiedBy,
IEntityWithVersion
{
string Name { get; }

18
src/Squidex.Domain.Apps.Entities/Apps/IAppGrain.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps
{
public interface IAppGrain : IDomainObjectGrain
{
Task<J<IAppEntity>> GetStateAsync();
}
}

8
src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs

@ -7,24 +7,24 @@
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class AssetCommandMiddleware : GrainCommandMiddleware<AssetCommand, AssetGrain>
public sealed class AssetCommandMiddleware : GrainCommandMiddleware<AssetCommand, IAssetGrain>
{
private readonly IAssetStore assetStore;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
public AssetCommandMiddleware(
IStateFactory stateFactory,
IGrainFactory grainFactory,
IAssetStore assetStore,
IAssetThumbnailGenerator assetThumbnailGenerator)
: base(stateFactory)
: base(grainFactory)
{
Guard.NotNull(assetStore, nameof(assetStore));
Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator));

10
src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs

@ -15,19 +15,20 @@ using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Assets
{
public class AssetGrain : DomainObjectGrain<AssetState>
public class AssetGrain : DomainObjectGrain<AssetState>, IAssetGrain
{
public AssetGrain(IStore<Guid> store)
: base(store)
{
}
public override Task<object> ExecuteAsync(IAggregateCommand command)
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
switch (command)
{
@ -137,5 +138,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
ApplySnapshot(Snapshot.Apply(@event));
}
public Task<J<IAssetEntity>> GetStateAsync()
{
return Task.FromResult(new J<IAssetEntity>(Snapshot));
}
}
}

8
src/Squidex.Infrastructure/EventSourcing/Grains/Messages/GetStatesRequest.cs → src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs

@ -1,13 +1,15 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class GetStatesRequest
public interface IAssetGrain : IDomainObjectGrain
{
}
}

4
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -24,7 +24,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentGrain : DomainObjectGrain<ContentState>
public class ContentGrain : DomainObjectGrain<ContentState>, IContentGrain
{
private readonly IAppProvider appProvider;
private readonly IAssetRepository assetRepository;
@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.contentRepository = contentRepository;
}
public override Task<object> ExecuteAsync(IAggregateCommand command)
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();

30
src/Squidex.Domain.Apps.Entities/Contents/ContentScheduler.cs → src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs

@ -5,24 +5,26 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using NodaTime;
using Orleans;
using Orleans.Runtime;
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;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentScheduler : IRunnable
public sealed class ContentSchedulerGrain : Grain, IContentSchedulerGrain, IRemindable
{
private readonly CompletionTimer timer;
private readonly IContentRepository contentRepository;
private readonly ICommandBus commandBus;
private readonly IClock clock;
public ContentScheduler(
public ContentSchedulerGrain(
IContentRepository contentRepository,
ICommandBus commandBus,
IClock clock)
@ -34,15 +36,24 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.contentRepository = contentRepository;
this.commandBus = commandBus;
this.clock = clock;
}
public override Task OnActivateAsync()
{
DelayDeactivation(TimeSpan.FromDays(1));
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
RegisterTimer(x => PublishAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
timer = new CompletionTimer(5000, x => PublishAsync());
return Task.FromResult(true);
}
public void Run()
public Task ActivateAsync()
{
return TaskHelper.Done;
}
private Task PublishAsync()
public Task PublishAsync()
{
var now = clock.GetCurrentInstant();
@ -53,5 +64,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
return commandBus.PublishAsync(command);
});
}
public Task ReceiveReminder(string reminderName, TickStatus status)
{
return TaskHelper.Done;
}
}
}

9
src/Squidex.Infrastructure/EventSourcing/Grains/Messages/StopConsumerMessage.cs → src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs

@ -1,14 +1,15 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class StopConsumerMessage
public interface IContentGrain : IDomainObjectGrain
{
public string ConsumerName { get; set; }
}
}

15
src/Squidex.Domain.Apps.Entities/Contents/IContentSchedulerGrain.cs

@ -0,0 +1,15 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Contents
{
public interface IContentSchedulerGrain : IBackgroundGrain
{
}
}

11
src/Squidex.Infrastructure/States/InvalidateMessage.cs → src/Squidex.Domain.Apps.Entities/Rules/IRuleDequeuerGrain.cs

@ -1,14 +1,15 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.States
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules
{
public sealed class InvalidateMessage
public interface IRuleDequeuerGrain : IBackgroundGrain
{
public string Key { get; set; }
}
}
}

18
src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules
{
public interface IRuleGrain : IDomainObjectGrain
{
Task<J<IRuleEntity>> GetStateAsync();
}
}

42
src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuer.cs → src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs

@ -7,31 +7,30 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using NodaTime;
using Orleans;
using Orleans.Runtime;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.Timers;
namespace Squidex.Domain.Apps.Entities.Rules
{
public class RuleDequeuer : DisposableObjectBase, IRunnable
public class RuleDequeuerGrain : Grain, IRuleDequeuerGrain, IRemindable
{
private readonly ITargetBlock<IRuleEventEntity> requestBlock;
private readonly IRuleEventRepository ruleEventRepository;
private readonly RuleService ruleService;
private readonly CompletionTimer timer;
private readonly ConcurrentDictionary<Guid, bool> executing = new ConcurrentDictionary<Guid, bool>();
private readonly IClock clock;
private readonly ISemanticLog log;
public RuleDequeuer(RuleService ruleService, IRuleEventRepository ruleEventRepository, ISemanticLog log, IClock clock)
public RuleDequeuerGrain(RuleService ruleService, IRuleEventRepository ruleEventRepository, ISemanticLog log, IClock clock)
{
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository));
Guard.NotNull(ruleService, nameof(ruleService));
@ -48,37 +47,37 @@ namespace Squidex.Domain.Apps.Entities.Rules
requestBlock =
new PartitionedActionBlock<IRuleEventEntity>(HandleAsync, x => x.Job.AggregateId.GetHashCode(),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 });
timer = new CompletionTimer(5000, QueryAsync);
}
protected override void DisposeObject(bool disposing)
public override Task OnActivateAsync()
{
if (disposing)
{
timer.StopAsync().Wait();
DelayDeactivation(TimeSpan.FromDays(1));
requestBlock.Complete();
requestBlock.Completion.Wait();
}
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
RegisterTimer(x => QueryAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
return Task.FromResult(true);
}
public void Run()
public override Task OnDeactivateAsync()
{
requestBlock.Complete();
return requestBlock.Completion;
}
public void Next()
public Task ActivateAsync()
{
timer.SkipCurrentDelay();
return TaskHelper.Done;
}
private async Task QueryAsync(CancellationToken ct)
public async Task QueryAsync()
{
try
{
var now = clock.GetCurrentInstant();
await ruleEventRepository.QueryPendingAsync(now, requestBlock.SendAsync, ct);
await ruleEventRepository.QueryPendingAsync(now, requestBlock.SendAsync);
}
catch (Exception ex)
{
@ -153,5 +152,10 @@ namespace Squidex.Domain.Apps.Entities.Rules
return null;
}
public Task ReceiveReminder(string reminderName, TickStatus status)
{
return TaskHelper.Done;
}
}
}

10
src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs

@ -15,12 +15,13 @@ using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Rules
{
public class RuleGrain : DomainObjectGrain<RuleState>
public sealed class RuleGrain : DomainObjectGrain<RuleState>, IRuleGrain
{
private readonly IAppProvider appProvider;
@ -32,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
this.appProvider = appProvider;
}
public override Task<object> ExecuteAsync(IAggregateCommand command)
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();
@ -125,5 +126,10 @@ namespace Squidex.Domain.Apps.Entities.Rules
{
ApplySnapshot(Snapshot.Apply(@event));
}
public Task<J<IRuleEntity>> GetStateAsync()
{
return Task.FromResult(new J<IRuleEntity>(Snapshot));
}
}
}

18
src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Schemas
{
public interface ISchemaGrain : IDomainObjectGrain
{
Task<J<ISchemaEntity>> GetStateAsync();
}
}

10
src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs

@ -18,12 +18,13 @@ using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Schemas
{
public class SchemaGrain : DomainObjectGrain<SchemaState>
public class SchemaGrain : DomainObjectGrain<SchemaState>, ISchemaGrain
{
private readonly IAppProvider appProvider;
private readonly FieldRegistry registry;
@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
this.registry = registry;
}
public override Task<object> ExecuteAsync(IAggregateCommand command)
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();
@ -300,5 +301,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{
ApplySnapshot(Snapshot.Apply(@event, registry));
}
public Task<J<ISchemaEntity>> GetStateAsync()
{
return Task.FromResult(new J<ISchemaEntity>(Snapshot));
}
}
}

3
src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -14,7 +14,8 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="Microsoft.Orleans.OrleansCodeGenerator.Build" Version="2.0.0-rc2" />
<PackageReference Include="NodaTime" Version="2.2.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />

10
src/Squidex.Infrastructure/EventSourcing/Grains/Messages/ResetConsumerMessage.cs → src/Squidex.Domain.Apps.Entities/SquidexEntities.cs

@ -1,14 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
using System.Reflection;
namespace Squidex.Domain.Apps.Entities
{
public sealed class ResetConsumerMessage
public sealed class SquidexEntities
{
public string ConsumerName { get; set; }
public static readonly Assembly Assembly = typeof(SquidexEntities).Assembly;
}
}

4
src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj

@ -12,8 +12,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="GraphQL" Version="0.17.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.0" />
<PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.1" />
<PackageReference Include="NodaTime" Version="2.2.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.8.0" />

4
src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -13,8 +13,8 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.1" />
<PackageReference Include="IdentityServer4" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.2" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="MongoDB.Driver" Version="2.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />

2
src/Squidex.Domain.Users/Squidex.Domain.Users.csproj

@ -11,7 +11,7 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.2" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />

2
src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj

@ -6,7 +6,7 @@
<ItemGroup>
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="WindowsAzure.Storage" Version="8.7.0" />
<PackageReference Include="WindowsAzure.Storage" Version="9.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />

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

@ -45,6 +45,10 @@ namespace Squidex.Infrastructure.EventSourcing
return TaskHelper.Done;
}
public void WakeUp()
{
}
private EventStoreCatchUpSubscription SubscribeToStream(string streamName)
{
var settings = CatchUpSubscriptionSettings.Default;

2
src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj

@ -8,7 +8,7 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EventStore.ClientAPI.NetCore" Version="4.0.2-rc" />
<PackageReference Include="EventStore.ClientAPI.NetCore" Version="4.1.0.23" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
</ItemGroup>

3
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs

@ -7,6 +7,7 @@
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Infrastructure.EventSourcing
{
@ -16,7 +17,7 @@ namespace Squidex.Infrastructure.EventSourcing
[BsonRequired]
public string Type { get; set; }
[BsonElement]
[BsonJson]
[BsonRequired]
public string Payload { get; set; }

2
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs

@ -26,7 +26,7 @@ namespace Squidex.Infrastructure.EventSourcing
Guard.NotNull(subscriber, nameof(subscriber));
Guard.NotNullOrEmpty(streamFilter, nameof(streamFilter));
return new PollingSubscription(this, notifier, subscriber, streamFilter, position);
return new PollingSubscription(this, subscriber, streamFilter, position);
}
public async Task<IReadOnlyList<StoredEvent>> QueryAsync(string streamName, long streamPosition = 0)

2
src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs

@ -49,4 +49,4 @@ namespace Squidex.Infrastructure.MongoDb
ConventionRegistry.Register("json", pack, t => true);
}
}
}
}

2
src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs

@ -12,7 +12,7 @@ using Newtonsoft.Json;
namespace Squidex.Infrastructure.MongoDb
{
public class BsonJsonSerializer<T> : ClassSerializerBase<T> where T : class
public sealed class BsonJsonSerializer<T> : ClassSerializerBase<T> where T : class
{
private readonly JsonSerializer serializer;

5
src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonWriter.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Globalization;
using MongoDB.Bson.IO;
using NewtonsoftJSonWriter = Newtonsoft.Json.JsonWriter;
@ -134,12 +135,12 @@ namespace Squidex.Infrastructure.MongoDb
public override void WriteValue(DateTime value)
{
bsonWriter.WriteString(value.ToString());
bsonWriter.WriteString(value.ToString(CultureInfo.InvariantCulture));
}
public override void WriteValue(DateTimeOffset value)
{
bsonWriter.WriteString(value.ToString());
bsonWriter.WriteString(value.ToString(CultureInfo.InvariantCulture));
}
public override void WriteValue(byte[] value)

2
src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj

@ -11,7 +11,7 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.4.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.1" />
<PackageReference Include="MongoDB.Driver" Version="2.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />

3
src/Squidex.Infrastructure/Assets/AssetFile.cs

@ -7,6 +7,7 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Assets
{
@ -20,11 +21,11 @@ namespace Squidex.Infrastructure.Assets
public long FileSize { get; }
[JsonConstructor]
public AssetFile(string fileName, string mimeType, long fileSize, Func<Stream> openAction)
{
Guard.NotNullOrEmpty(fileName, nameof(fileName));
Guard.NotNullOrEmpty(mimeType, nameof(mimeType));
Guard.NotNull(openAction, nameof(openAction));
Guard.GreaterEquals(fileSize, 0, nameof(fileSize));
FileName = fileName;

34
src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs

@ -9,12 +9,13 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.Commands
{
public abstract class DomainObjectGrain<T> : IDomainObjectGrain where T : IDomainState, new()
public abstract class DomainObjectGrain<T> : GrainOfGuid, IDomainObjectGrain where T : IDomainState, new()
{
private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>();
private readonly IStore<Guid> store;
@ -49,11 +50,11 @@ namespace Squidex.Infrastructure.Commands
this.store = store;
}
public Task ActivateAsync(Guid key)
public override Task OnActivateAsync(Guid key)
{
id = key;
persistence = store.WithSnapshotsAndEventSourcing<T, Guid>(GetType(), key, ApplySnapshot, ApplyEvent);
persistence = store.WithSnapshotsAndEventSourcing<T, Guid>(GetType(), id, ApplySnapshot, ApplyEvent);
return persistence.ReadAsync();
}
@ -67,7 +68,7 @@ namespace Squidex.Infrastructure.Commands
{
Guard.NotNull(@event, nameof(@event));
@event.SetAggregateId(Id);
@event.SetAggregateId(id);
ApplyEvent(@event);
@ -146,12 +147,20 @@ namespace Squidex.Infrastructure.Commands
if (command.ExpectedVersion != EtagVersion.Any && command.ExpectedVersion != Version)
{
throw new DomainObjectVersionException(Id.ToString(), GetType(), Version, command.ExpectedVersion);
throw new DomainObjectVersionException(id.ToString(), GetType(), Version, command.ExpectedVersion);
}
if (isUpdate && Version < 0)
{
throw new DomainObjectNotFoundException(Id.ToString(), GetType());
try
{
DeactivateOnIdle();
}
catch (InvalidOperationException)
{
}
throw new DomainObjectNotFoundException(id.ToString(), GetType());
}
else if (!isUpdate && Version >= 0)
{
@ -181,7 +190,7 @@ namespace Squidex.Infrastructure.Commands
}
else
{
result = EntityCreatedResult.Create(Id, Version);
result = EntityCreatedResult.Create(id, Version);
}
}
@ -195,10 +204,17 @@ namespace Squidex.Infrastructure.Commands
}
finally
{
ClearUncommittedEvents();
uncomittedEvents.Clear();
}
}
public abstract Task<object> ExecuteAsync(IAggregateCommand command);
public async Task<J<object>> ExecuteAsync(J<IAggregateCommand> command)
{
var result = await ExecuteAsync(command.Value);
return result.AsJ();
}
protected abstract Task<object> ExecuteAsync(IAggregateCommand command);
}
}

36
src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Orleans;
namespace Squidex.Infrastructure.Commands
{
public static class DomainObjectGrainFormatter
{
public static string Format(IGrainCallContext context)
{
if (context.Method == null)
{
return "Unknown";
}
if (string.Equals(context.Method.Name, nameof(IDomainObjectGrain.ExecuteAsync), StringComparison.CurrentCultureIgnoreCase) &&
context.Arguments?.Length == 1 &&
context.Arguments[0] != null)
{
var argumentFullName = context.Arguments[0].ToString();
var argumentParts = argumentFullName.Split('.');
var argumentName = argumentParts[argumentParts.Length - 1];
return $"{nameof(IDomainObjectGrain.ExecuteAsync)}({argumentName})";
}
return context.Method.Name;
}
}
}

14
src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs

@ -7,19 +7,19 @@
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.States;
using Orleans;
namespace Squidex.Infrastructure.Commands
{
public class GrainCommandMiddleware<TCommand, TGrain> : ICommandMiddleware where TCommand : IAggregateCommand where TGrain : IDomainObjectGrain
{
private readonly IStateFactory stateFactory;
private readonly IGrainFactory grainFactory;
public GrainCommandMiddleware(IStateFactory stateFactory)
public GrainCommandMiddleware(IGrainFactory grainFactory)
{
Guard.NotNull(stateFactory, nameof(stateFactory));
Guard.NotNull(grainFactory, nameof(grainFactory));
this.stateFactory = stateFactory;
this.grainFactory = grainFactory;
}
public async virtual Task HandleAsync(CommandContext context, Func<Task> next)
@ -36,11 +36,11 @@ namespace Squidex.Infrastructure.Commands
protected async Task<object> ExecuteCommandAsync(TCommand typedCommand)
{
var grain = await stateFactory.CreateAsync<TGrain>(typedCommand.AggregateId);
var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId);
var result = await grain.ExecuteAsync(typedCommand);
return result;
return result.Value;
}
}
}

12
src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs

@ -5,18 +5,16 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.States;
using Orleans;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Commands
{
public interface IDomainObjectGrain : IStatefulObject<Guid>
public interface IDomainObjectGrain : IGrainWithGuidKey
{
Task<object> ExecuteAsync(IAggregateCommand command);
Task WriteSnapshotAsync();
long Version { get; }
Task<J<object>> ExecuteAsync(J<IAggregateCommand> command);
}
}
}

63
src/Squidex.Infrastructure/Commands/SyncedGrainCommandMiddleware.cs

@ -1,63 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.Commands
{
public class SyncedGrainCommandMiddleware<TCommand, TGrain> : ICommandMiddleware where TCommand : IAggregateCommand where TGrain : IDomainObjectGrain
{
private readonly AsyncLockPool lockPool = new AsyncLockPool(10000);
private readonly IStateFactory stateFactory;
public SyncedGrainCommandMiddleware(IStateFactory stateFactory)
{
Guard.NotNull(stateFactory, nameof(stateFactory));
this.stateFactory = stateFactory;
}
public async virtual Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.Command is TCommand typedCommand)
{
var result = await ExecuteCommandAsync(typedCommand);
context.Complete(result);
}
await next();
}
protected async Task<object> ExecuteCommandAsync(TCommand typedCommand)
{
var id = typedCommand.AggregateId;
using (await lockPool.LockAsync(typedCommand.AggregateId))
{
try
{
var grain = await stateFactory.GetSingleAsync<TGrain>(id);
var result = await grain.ExecuteAsync(typedCommand);
stateFactory.Synchronize<TGrain, Guid>(id);
return result;
}
catch
{
stateFactory.Remove<TGrain, Guid>(id);
throw;
}
}
}
}
}

40
src/Squidex.Infrastructure/EventSourcing/DefaultEventNotifier.cs

@ -1,40 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Infrastructure.EventSourcing
{
public sealed class DefaultEventNotifier : IEventNotifier
{
private static readonly string ChannelName = typeof(DefaultEventNotifier).Name;
private readonly IPubSub pubsub;
public sealed class EventNotification
{
public string StreamName { get; set; }
}
public DefaultEventNotifier(IPubSub pubsub)
{
Guard.NotNull(pubsub, nameof(pubsub));
this.pubsub = pubsub;
}
public void NotifyEventsStored(string streamName)
{
pubsub.Publish(new EventNotification { StreamName = streamName }, true);
}
public IDisposable Subscribe(Action<string> handler)
{
return pubsub.Subscribe<EventNotification>(x => handler?.Invoke(x.StreamName));
}
}
}

133
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs

@ -8,25 +8,30 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Orleans;
using Orleans.Concurrency;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
public class EventConsumerGrain : DisposableObjectBase, IStatefulObject<string>, IEventSubscriber
public class EventConsumerGrain : GrainOfString, IEventConsumerGrain
{
private readonly IEventDataFormatter eventDataFormatter;
private readonly EventConsumerFactory eventConsumerFactory;
private readonly IStore<string> store;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore;
private readonly ISemanticLog log;
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(1);
private TaskScheduler scheduler;
private IPersistence<EventConsumerState> persistence;
private IEventSubscription currentSubscription;
private IEventConsumer eventConsumer;
private IPersistence<EventConsumerState> persistence;
private EventConsumerState state = new EventConsumerState();
public EventConsumerGrain(
EventConsumerFactory eventConsumerFactory,
IStore<string> store,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
@ -36,95 +41,54 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
Guard.NotNull(store, nameof(store));
Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(eventConsumerFactory, nameof(eventConsumerFactory));
this.log = log;
this.store = store;
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.eventConsumerFactory = eventConsumerFactory;
}
protected override void DisposeObject(bool disposing)
{
if (disposing)
{
dispatcher.StopAndWaitAsync().Wait();
}
}
public Task ActivateAsync(string key)
public override Task OnActivateAsync(string key)
{
persistence = store.WithSnapshots<EventConsumerGrain, EventConsumerState, string>(key, s => state = s);
return persistence.ReadAsync();
}
scheduler = TaskScheduler.Current;
protected virtual IEventSubscription CreateSubscription(IEventStore eventStore, string streamFilter, string position)
{
return new RetrySubscription(eventStore, this, streamFilter, position);
}
eventConsumer = eventConsumerFactory(key);
public virtual EventConsumerInfo GetState()
{
return state.ToInfo(this.eventConsumer.Name);
}
persistence = store.WithSnapshots<EventConsumerState, string>(GetType(), eventConsumer.Name, s => state = s);
public virtual void Stop()
{
dispatcher.DispatchAsync(HandleStopAsync).Forget();
}
public virtual void Start()
{
dispatcher.DispatchAsync(HandleStartAsync).Forget();
return persistence.ReadAsync();
}
public virtual void Reset()
public Task<Immutable<EventConsumerInfo>> GetStateAsync()
{
dispatcher.DispatchAsync(HandleResetAsync).Forget();
return Task.FromResult(state.ToInfo(this.eventConsumer.Name).AsImmutable());
}
public virtual void Activate(IEventConsumer eventConsumer)
public Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent)
{
Guard.NotNull(eventConsumer, nameof(eventConsumer));
dispatcher.DispatchAsync(() => HandleSetupAsync(eventConsumer)).Forget();
}
private Task HandleSetupAsync(IEventConsumer consumer)
{
eventConsumer = consumer;
if (!state.IsStopped)
{
Subscribe(state.Position);
}
return TaskHelper.Done;
}
private Task HandleEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
{
if (subscription != currentSubscription)
if (subscription.Value != currentSubscription)
{
return TaskHelper.Done;
}
return DoAndUpdateStateAsync(async () =>
{
var @event = ParseKnownEvent(storedEvent);
var @event = ParseKnownEvent(storedEvent.Value);
if (@event != null)
{
await DispatchConsumerAsync(@event);
}
state = state.Handled(storedEvent.EventPosition);
state = state.Handled(storedEvent.Value.EventPosition);
});
}
private Task HandleErrorAsync(IEventSubscription subscription, Exception exception)
public Task OnErrorAsync(Immutable<IEventSubscription> subscription, Immutable<Exception> exception)
{
if (subscription != currentSubscription)
if (subscription.Value != currentSubscription)
{
return TaskHelper.Done;
}
@ -133,11 +97,21 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{
Unsubscribe();
state = state.Failed(exception);
state = state.Failed(exception.Value);
});
}
private Task HandleStartAsync()
public Task ActivateAsync()
{
if (!state.IsStopped)
{
Subscribe(state.Position);
}
return TaskHelper.Done;
}
public Task StartAsync()
{
if (!state.IsStopped)
{
@ -152,7 +126,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
});
}
private Task HandleStopAsync()
public Task StopAsync()
{
if (state.IsStopped)
{
@ -167,7 +141,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
});
}
private Task HandleResetAsync()
public Task ResetAsync()
{
return DoAndUpdateStateAsync(async () =>
{
@ -181,16 +155,6 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
});
}
Task IEventSubscriber.OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
{
return dispatcher.DispatchAsync(() => HandleEventAsync(subscription, storedEvent));
}
Task IEventSubscriber.OnErrorAsync(IEventSubscription subscription, Exception exception)
{
return dispatcher.DispatchAsync(() => HandleErrorAsync(subscription, exception));
}
private Task DoAndUpdateStateAsync(Action action, [CallerMemberName] string caller = null)
{
return DoAndUpdateStateAsync(() => { action(); return TaskHelper.Done; }, caller);
@ -283,7 +247,11 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
if (currentSubscription == null)
{
currentSubscription?.StopAsync().Forget();
currentSubscription = CreateSubscription(eventStore, eventConsumer.EventsFilter, position);
currentSubscription = CreateSubscription(eventConsumer.EventsFilter, position);
}
else
{
currentSubscription.WakeUp();
}
}
@ -305,5 +273,20 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
return null;
}
}
protected virtual IEventConsumerGrain GetSelf()
{
return this.AsReference<IEventConsumerGrain>();
}
protected virtual IEventSubscription CreateSubscription(IEventStore store, IEventSubscriber subscriber, string streamFilter, string position)
{
return new RetrySubscription(store, subscriber, streamFilter, position);
}
private IEventSubscription CreateSubscription(string streamFilter, string position)
{
return CreateSubscription(eventStore, new WrapperSubscription(GetSelf(), scheduler), streamFilter, position);
}
}
}

90
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs

@ -1,90 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure.EventSourcing.Grains.Messages;
using Squidex.Infrastructure.States;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
public sealed class EventConsumerGrainManager : DisposableObjectBase, IRunnable
{
private readonly IStateFactory factory;
private readonly IPubSub pubSub;
private readonly List<IEventConsumer> consumers;
private readonly List<IDisposable> subscriptions = new List<IDisposable>();
public EventConsumerGrainManager(IEnumerable<IEventConsumer> consumers, IPubSub pubSub, IStateFactory factory)
{
Guard.NotNull(pubSub, nameof(pubSub));
Guard.NotNull(factory, nameof(factory));
Guard.NotNull(consumers, nameof(consumers));
this.pubSub = pubSub;
this.factory = factory;
this.consumers = consumers.ToList();
}
public void Run()
{
var actors = new Dictionary<string, EventConsumerGrain>();
foreach (var consumer in consumers)
{
var actor = factory.CreateAsync<EventConsumerGrain>(consumer.Name).Result;
actors[consumer.Name] = actor;
actor.Activate(consumer);
}
subscriptions.Add(pubSub.Subscribe<StartConsumerMessage>(m =>
{
if (actors.TryGetValue(m.ConsumerName, out var actor))
{
actor.Start();
}
}));
subscriptions.Add(pubSub.Subscribe<StopConsumerMessage>(m =>
{
if (actors.TryGetValue(m.ConsumerName, out var actor))
{
actor.Stop();
}
}));
subscriptions.Add(pubSub.Subscribe<ResetConsumerMessage>(m =>
{
if (actors.TryGetValue(m.ConsumerName, out var actor))
{
actor.Reset();
}
}));
subscriptions.Add(pubSub.ReceiveAsync<GetStatesRequest, GetStatesResponse>(request =>
{
var states = actors.Values.Select(x => x.GetState()).ToArray();
return Task.FromResult(new GetStatesResponse { States = states });
}));
}
protected override void DisposeObject(bool disposing)
{
if (disposing)
{
foreach (var subscription in subscriptions)
{
subscription.Dispose();
}
}
}
}
}

104
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs

@ -0,0 +1,104 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Orleans;
using Orleans.Concurrency;
using Orleans.Core;
using Orleans.Runtime;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
public class EventConsumerManagerGrain : Grain, IEventConsumerManagerGrain, IRemindable
{
private readonly IEnumerable<IEventConsumer> eventConsumers;
public EventConsumerManagerGrain(IEnumerable<IEventConsumer> eventConsumers)
: this(eventConsumers, null, null)
{
}
protected EventConsumerManagerGrain(
IEnumerable<IEventConsumer> eventConsumers,
IGrainIdentity identity,
IGrainRuntime runtime)
: base(identity, runtime)
{
Guard.NotNull(eventConsumers, nameof(eventConsumers));
this.eventConsumers = eventConsumers;
}
public override Task OnActivateAsync()
{
DelayDeactivation(TimeSpan.FromDays(1));
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
RegisterTimer(x => ActivateAsync(null), null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
return Task.FromResult(true);
}
public Task ActivateAsync(string streamName)
{
var tasks =
eventConsumers
.Where(c => streamName == null || Regex.IsMatch(streamName, c.EventsFilter))
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name))
.Select(c => c.ActivateAsync());
return Task.WhenAll(tasks);
}
public async Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync()
{
var tasks =
eventConsumers
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name))
.Select(c => c.GetStateAsync());
var consumerInfos = await Task.WhenAll(tasks);
return new Immutable<List<EventConsumerInfo>>(consumerInfos.Select(r => r.Value).ToList());
}
public Task ResetAsync(string consumerName)
{
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName);
return eventConsumer.ResetAsync();
}
public Task StartAsync(string consumerName)
{
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName);
return eventConsumer.StartAsync();
}
public Task StopAsync(string consumerName)
{
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName);
return eventConsumer.StopAsync();
}
public Task ActivateAsync()
{
return ActivateAsync(null);
}
public Task ReceiveReminder(string reminderName, TickStatus status)
{
return ActivateAsync(null);
}
}
}

29
src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs

@ -0,0 +1,29 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans.Concurrency;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
public interface IEventConsumerGrain : IBackgroundGrain
{
Task<Immutable<EventConsumerInfo>> GetStateAsync();
Task StopAsync();
Task StartAsync();
Task ResetAsync();
Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent);
Task OnErrorAsync(Immutable<IEventSubscription> subscription, Immutable<Exception> exception);
}
}

27
src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs

@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans.Concurrency;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
public interface IEventConsumerManagerGrain : IBackgroundGrain
{
Task ActivateAsync(string streamName);
Task StopAsync(string consumerName);
Task StartAsync(string consumerName);
Task ResetAsync(string consumerName);
Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync();
}
}

40
src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs

@ -0,0 +1,40 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Orleans;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
public sealed class OrleansEventNotifier : IEventNotifier, IInitializable
{
private readonly IGrainFactory factory;
private IEventConsumerManagerGrain eventConsumerManagerGrain;
public OrleansEventNotifier(IGrainFactory factory)
{
Guard.NotNull(factory, nameof(factory));
this.factory = factory;
}
public void Initialize()
{
eventConsumerManagerGrain = factory.GetGrain<IEventConsumerManagerGrain>("Default");
}
public void NotifyEventsStored(string streamName)
{
eventConsumerManagerGrain?.ActivateAsync(streamName);
}
public IDisposable Subscribe(Action<string> handler)
{
return null;
}
}
}

42
src/Squidex.Infrastructure/EventSourcing/Grains/WrapperSubscription.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using Orleans.Concurrency;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
internal sealed class WrapperSubscription : IEventSubscriber
{
private readonly IEventConsumerGrain grain;
private readonly TaskScheduler scheduler;
public WrapperSubscription(IEventConsumerGrain grain, TaskScheduler scheduler)
{
this.grain = grain;
this.scheduler = scheduler ?? TaskScheduler.Default;
}
public Task OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
{
return Dispatch(() => grain.OnEventAsync(subscription.AsImmutable(), storedEvent.AsImmutable()));
}
public Task OnErrorAsync(IEventSubscription subscription, Exception exception)
{
return Dispatch(() => grain.OnErrorAsync(subscription.AsImmutable(), exception.AsImmutable()));
}
private Task Dispatch(Func<Task> task)
{
return Task<Task>.Factory.StartNew(() => task(), CancellationToken.None, TaskCreationOptions.None, scheduler).Unwrap();
}
}
}

4
src/Squidex.Infrastructure/EventSourcing/IEventNotifier.cs

@ -5,14 +5,10 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Infrastructure.EventSourcing
{
public interface IEventNotifier
{
void NotifyEventsStored(string streamName);
IDisposable Subscribe(Action<string> handler);
}
}

2
src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs

@ -11,6 +11,8 @@ namespace Squidex.Infrastructure.EventSourcing
{
public interface IEventSubscription
{
void WakeUp();
Task StopAsync();
}
}

18
src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs

@ -14,10 +14,8 @@ namespace Squidex.Infrastructure.EventSourcing
{
public sealed class PollingSubscription : IEventSubscription
{
private readonly IEventNotifier eventNotifier;
private readonly IEventStore eventStore;
private readonly IEventSubscriber eventSubscriber;
private readonly IDisposable notification;
private readonly CompletionTimer timer;
private readonly Regex streamRegex;
private readonly string streamFilter;
@ -25,17 +23,14 @@ namespace Squidex.Infrastructure.EventSourcing
public PollingSubscription(
IEventStore eventStore,
IEventNotifier eventNotifier,
IEventSubscriber eventSubscriber,
string streamFilter,
string position)
{
Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventNotifier, nameof(eventNotifier));
Guard.NotNull(eventSubscriber, nameof(eventSubscriber));
this.position = position;
this.eventNotifier = eventNotifier;
this.eventStore = eventStore;
this.eventSubscriber = eventSubscriber;
this.streamFilter = streamFilter;
@ -61,20 +56,15 @@ namespace Squidex.Infrastructure.EventSourcing
}
}
});
}
notification = eventNotifier.Subscribe(streamName =>
{
if (streamRegex.IsMatch(streamName))
{
timer.SkipCurrentDelay();
}
});
public void WakeUp()
{
timer.SkipCurrentDelay();
}
public Task StopAsync()
{
notification?.Dispose();
return timer.StopAsync();
}
}

5
src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs

@ -57,6 +57,11 @@ namespace Squidex.Infrastructure.EventSourcing
currentSubscription = null;
}
public void WakeUp()
{
currentSubscription?.WakeUp();
}
private async Task HandleEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
{
if (subscription == currentSubscription)

33
src/Squidex.Infrastructure/Orleans/Bootstrap.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Orleans;
using Orleans.Runtime;
namespace Squidex.Infrastructure.Orleans
{
public sealed class Bootstrap<T> : IStartupTask where T : IBackgroundGrain
{
private readonly IGrainFactory grainFactory;
public Bootstrap(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
this.grainFactory = grainFactory;
}
public Task Execute(CancellationToken cancellationToken)
{
var grain = grainFactory.GetGrain<T>("Default");
return grain.ActivateAsync();
}
}
}

27
src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs

@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.Orleans
{
public abstract class GrainOfGuid : Grain
{
public override Task OnActivateAsync()
{
return OnActivateAsync(this.GetPrimaryKey());
}
public virtual Task OnActivateAsync(Guid key)
{
return TaskHelper.Done;
}
}
}

26
src/Squidex.Infrastructure/Orleans/GrainOfString.cs

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Orleans;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.Orleans
{
public abstract class GrainOfString : Grain
{
public override Task OnActivateAsync()
{
return OnActivateAsync(this.GetPrimaryKeyString());
}
public virtual Task OnActivateAsync(string key)
{
return TaskHelper.Done;
}
}
}

9
src/Squidex.Infrastructure/States/IStatefulObject.cs → src/Squidex.Infrastructure/Orleans/IBackgroundGrain.cs

@ -1,16 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Orleans;
namespace Squidex.Infrastructure.States
namespace Squidex.Infrastructure.Orleans
{
public interface IStatefulObject<TKey>
public interface IBackgroundGrain : IGrainWithStringKey
{
Task ActivateAsync(TKey key);
Task ActivateAsync();
}
}

12
src/Squidex.Infrastructure/EventSourcing/Grains/Messages/StartConsumerMessage.cs → src/Squidex.Infrastructure/Orleans/J.cs

@ -1,14 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
using Newtonsoft.Json;
#pragma warning disable SA1401 // Fields must be private
namespace Squidex.Infrastructure.Orleans
{
public sealed class StartConsumerMessage
public static class J
{
public string ConsumerName { get; set; }
public static JsonSerializer Serializer = new JsonSerializer();
}
}

11
src/Squidex.Infrastructure/EventSourcing/Grains/Messages/GetStatesResponse.cs → src/Squidex.Infrastructure/Orleans/JExtensions.cs

@ -1,14 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
namespace Squidex.Infrastructure.Orleans
{
public sealed class GetStatesResponse
public static class JExtensions
{
public EventConsumerInfo[] States { get; set; }
public static J<T> AsJ<T>(this T value)
{
return new J<T>(value);
}
}
}

90
src/Squidex.Infrastructure/Orleans/J{T}.cs

@ -0,0 +1,90 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Orleans.CodeGeneration;
using Orleans.Serialization;
namespace Squidex.Infrastructure.Orleans
{
public struct J<T>
{
private readonly T value;
public T Value
{
get { return value; }
}
[JsonConstructor]
public J(T value)
{
this.value = value;
}
public static implicit operator T(J<T> value)
{
return value.value;
}
public static implicit operator J<T>(T d)
{
return new J<T>(d);
}
public override string ToString()
{
return value?.ToString() ?? string.Empty;
}
public static Task<J<T>> AsTask(T value)
{
return Task.FromResult<J<T>>(value);
}
[CopierMethod]
public static object Copy(object input, ICopyContext context)
{
return input;
}
[SerializerMethod]
public static void Serialize(object input, ISerializationContext context, Type expected)
{
var stream = new MemoryStream();
using (var writer = new JsonTextWriter(new StreamWriter(stream)))
{
J.Serializer.Serialize(writer, input);
writer.Flush();
}
var outBytes = stream.ToArray();
context.StreamWriter.Write(outBytes.Length);
context.StreamWriter.Write(outBytes);
}
[DeserializerMethod]
public static object Deserialize(Type expected, IDeserializationContext context)
{
var outLength = context.StreamReader.ReadInt();
var outBytes = context.StreamReader.ReadBytes(outLength);
var stream = new MemoryStream(outBytes);
using (var reader = new JsonTextReader(new StreamReader(stream)))
{
return J.Serializer.Deserialize(reader, expected);
}
}
}
}

11
src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -8,10 +8,13 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.0.0-rc2" />
<PackageReference Include="Microsoft.Orleans.OrleansCodeGenerator.Build" Version="2.0.0-rc2" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.0.0-rc2" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
<PackageReference Include="NodaTime" Version="2.2.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0001" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />

31
src/Squidex.Infrastructure/States/IStateFactory.cs

@ -1,31 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
namespace Squidex.Infrastructure.States
{
public interface IStateFactory
{
Task<T> GetSingleAsync<T>(string key) where T : IStatefulObject<string>;
Task<T> GetSingleAsync<T>(Guid key) where T : IStatefulObject<Guid>;
Task<T> GetSingleAsync<T, TKey>(TKey key) where T : IStatefulObject<TKey>;
Task<T> CreateAsync<T>(string key) where T : IStatefulObject<string>;
Task<T> CreateAsync<T>(Guid key) where T : IStatefulObject<Guid>;
Task<T> CreateAsync<T, TKey>(TKey key) where T : IStatefulObject<TKey>;
void Remove<T, TKey>(TKey key) where T : IStatefulObject<TKey>;
void Synchronize<T, TKey>(TKey key) where T : IStatefulObject<TKey>;
}
}

140
src/Squidex.Infrastructure/States/StateFactory.cs

@ -1,140 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
#pragma warning disable RECS0096 // Type parameter is never used
namespace Squidex.Infrastructure.States
{
public sealed class StateFactory : DisposableObjectBase, IInitializable, IStateFactory
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);
private readonly IPubSub pubSub;
private readonly IMemoryCache statesCache;
private readonly IServiceProvider services;
private readonly object lockObject = new object();
private IDisposable pubSubSubscription;
public sealed class ObjectHolder<T, TKey> where T : IStatefulObject<TKey>
{
private readonly Task activationTask;
private readonly T obj;
public ObjectHolder(T obj, TKey key)
{
this.obj = obj;
activationTask = obj.ActivateAsync(key);
}
public async Task<T> ActivateAsync()
{
await activationTask;
return obj;
}
}
public StateFactory(IPubSub pubSub, IMemoryCache statesCache, IServiceProvider services)
{
Guard.NotNull(pubSub, nameof(pubSub));
Guard.NotNull(services, nameof(services));
Guard.NotNull(statesCache, nameof(statesCache));
this.pubSub = pubSub;
this.services = services;
this.statesCache = statesCache;
}
public void Initialize()
{
pubSubSubscription = pubSub.Subscribe<InvalidateMessage>(m =>
{
lock (lockObject)
{
statesCache.Remove(m.Key);
}
});
}
public Task<T> CreateAsync<T>(string key) where T : IStatefulObject<string>
{
return CreateAsync<T, string>(key);
}
public Task<T> CreateAsync<T>(Guid key) where T : IStatefulObject<Guid>
{
return CreateAsync<T, Guid>(key);
}
public async Task<T> CreateAsync<T, TKey>(TKey key) where T : IStatefulObject<TKey>
{
Guard.NotNull(key, nameof(key));
var state = (T)services.GetService(typeof(T));
await state.ActivateAsync(key);
return state;
}
public Task<T> GetSingleAsync<T>(string key) where T : IStatefulObject<string>
{
return GetSingleAsync<T, string>(key);
}
public Task<T> GetSingleAsync<T>(Guid key) where T : IStatefulObject<Guid>
{
return GetSingleAsync<T, Guid>(key);
}
public Task<T> GetSingleAsync<T, TKey>(TKey key) where T : IStatefulObject<TKey>
{
Guard.NotNull(key, nameof(key));
lock (lockObject)
{
if (statesCache.TryGetValue<ObjectHolder<T, TKey>>(key, out var stateObj))
{
return stateObj.ActivateAsync();
}
var state = (T)services.GetService(typeof(T));
stateObj = new ObjectHolder<T, TKey>(state, key);
statesCache.CreateEntry(key)
.SetValue(stateObj)
.SetAbsoluteExpiration(CacheDuration)
.Dispose();
return stateObj.ActivateAsync();
}
}
public void Remove<T, TKey>(TKey key) where T : IStatefulObject<TKey>
{
statesCache.Remove(key);
}
public void Synchronize<T, TKey>(TKey key) where T : IStatefulObject<TKey>
{
pubSub.Publish(new InvalidateMessage { Key = key.ToString() }, false);
}
protected override void DisposeObject(bool disposing)
{
if (disposing && pubSubSubscription != null)
{
pubSubSubscription.Dispose();
}
}
}
}

16
src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs

@ -24,7 +24,7 @@ namespace Squidex.Infrastructure.Tasks
}
public PartitionedActionBlock(Action<TInput> action, Func<TInput, int> partitioner)
: this (ToAsync(action), partitioner, new ExecutionDataflowBlockOptions())
: this (action?.ToAsync(), partitioner, new ExecutionDataflowBlockOptions())
{
}
@ -34,7 +34,7 @@ namespace Squidex.Infrastructure.Tasks
}
public PartitionedActionBlock(Action<TInput> action, Func<TInput, int> partitioner, ExecutionDataflowBlockOptions dataflowBlockOptions)
: this(ToAsync(action), partitioner, dataflowBlockOptions)
: this(action?.ToAsync(), partitioner, dataflowBlockOptions)
{
}
@ -94,17 +94,5 @@ namespace Squidex.Infrastructure.Tasks
{
distributor.Fault(exception);
}
private static Func<TInput, Task> ToAsync(Action<TInput> action)
{
Guard.NotNull(action, nameof(action));
return x =>
{
action(x);
return TaskHelper.Done;
};
}
}
}

26
src/Squidex/AppConfiguration.cs

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Configuration;
namespace Squidex
{
public static class AppConfiguration
{
public static void AddAppConfiguration(this IConfigurationBuilder builder, string environmentName, string[] args)
{
builder.Sources.Clear();
builder.AddJsonFile("appsettings.json", true, true);
builder.AddJsonFile($"appsettings.{environmentName}.json", true);
builder.AddEnvironmentVariables();
builder.AddCommandLine(args);
}
}
}

10
src/Squidex/AppServices.cs

@ -26,17 +26,19 @@ namespace Squidex
services.AddMyAssetServices(config);
services.AddMyAuthentication(config);
services.AddMyEntitiesServices(config);
services.AddMyEventPublishersServices(config);
services.AddMyEventStoreServices(config);
services.AddMyIdentityServer();
services.AddMyInfrastructureServices(config);
services.AddMyInfrastructureServices();
services.AddMyLoggingServices(config);
services.AddMyMigrationServices();
services.AddMyMvc();
services.AddMyPubSubServices(config);
services.AddMyReadServices(config);
services.AddMyRuleServices();
services.AddMySerializers();
services.AddMyStoreServices(config);
services.AddMySwaggerSettings();
services.AddMyWriteServices();
services.AddMySubscriptionServices(config);
services.Configure<MyUrlsOptions>(
config.GetSection("urls"));

27
src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs

@ -5,15 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Orleans;
using Squidex.Areas.Api.Controllers.EventConsumers.Models;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing.Grains.Messages;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
@ -25,12 +24,12 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[SwaggerIgnore]
public sealed class EventConsumersController : ApiController
{
private readonly IPubSub pubSub;
private readonly IEventConsumerManagerGrain eventConsumerManagerGrain;
public EventConsumersController(ICommandBus commandBus, IPubSub pubSub)
public EventConsumersController(ICommandBus commandBus, IGrainFactory grainFactory)
: base(commandBus)
{
this.pubSub = pubSub;
eventConsumerManagerGrain = grainFactory.GetGrain<IEventConsumerManagerGrain>("Default");
}
[HttpGet]
@ -38,9 +37,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[ApiCosts(0)]
public async Task<IActionResult> GetEventConsumers()
{
var entities = await pubSub.RequestAsync<GetStatesRequest, GetStatesResponse>(new GetStatesRequest(), TimeSpan.FromSeconds(2), true);
var entities = await eventConsumerManagerGrain.GetConsumersAsync();
var models = entities.States.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList();
var models = entities.Value.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList();
return Ok(models);
}
@ -48,9 +47,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/start/")]
[ApiCosts(0)]
public IActionResult Start(string name)
public async Task<IActionResult> Start(string name)
{
pubSub.Publish(new StartConsumerMessage { ConsumerName = name }, true);
await eventConsumerManagerGrain.StartAsync(name);
return NoContent();
}
@ -58,9 +57,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/stop/")]
[ApiCosts(0)]
public IActionResult Stop(string name)
public async Task<IActionResult> Stop(string name)
{
pubSub.Publish(new StopConsumerMessage { ConsumerName = name }, true);
await eventConsumerManagerGrain.StopAsync(name);
return NoContent();
}
@ -68,9 +67,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/reset/")]
[ApiCosts(0)]
public IActionResult Reset(string name)
public async Task<IActionResult> Reset(string name)
{
pubSub.Publish(new ResetConsumerMessage { ConsumerName = name }, true);
await eventConsumerManagerGrain.ResetAsync(name);
return NoContent();
}

3
src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs

@ -130,7 +130,8 @@ namespace Squidex.Areas.IdentityServer.Config
ClientSecrets = new List<Secret> { new Secret(Constants.InternalClientSecret) },
RedirectUris = new List<string>
{
urlsOptions.BuildUrl($"{Constants.PortalPrefix}/signin-oidc", false)
urlsOptions.BuildUrl($"{Constants.PortalPrefix}/signin-oidc", false),
urlsOptions.BuildUrl($"{Constants.OrleansPrefix}/signin-oidc", false)
},
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds,
AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials,

43
src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs

@ -0,0 +1,43 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Http;
using Squidex.Shared.Identity;
namespace Squidex.Areas.OrleansDashboard.Middlewares
{
public sealed class OrleansDashboardAuthenticationMiddleware
{
private readonly RequestDelegate next;
public OrleansDashboardAuthenticationMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
var authentication = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (!authentication.Succeeded || !authentication.Principal.IsInRole(SquidexRoles.Administrator))
{
await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
{
RedirectUri = context.Request.PathBase + context.Request.Path
});
}
else
{
await next(context);
}
}
}
}

27
src/Squidex/Areas/OrleansDashboard/Startup.cs

@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.AspNetCore.Builder;
using Orleans;
using Squidex.Areas.OrleansDashboard.Middlewares;
using Squidex.Config;
namespace Squidex.Areas.OrleansDashboard
{
public static class Startup
{
public static void ConfigureOrleansDashboard(this IApplicationBuilder app)
{
app.Map(Constants.OrleansPrefix, orleansApp =>
{
orleansApp.UseAuthentication();
orleansApp.UseMiddleware<OrleansDashboardAuthenticationMiddleware>();
orleansApp.UseOrleansDashboard();
});
}
}
}

2
src/Squidex/Config/Constants.cs

@ -17,6 +17,8 @@ namespace Squidex.Config
public static readonly string ApiScope = "squidex-api";
public static readonly string OrleansPrefix = "/orleans";
public static readonly string PortalPrefix = "/portal";
public static readonly string RoleScope = "role";

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

@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Assets.ImageSharp;
using Squidex.Infrastructure.Log;
namespace Squidex.Config.Domain
@ -45,6 +46,9 @@ namespace Squidex.Config.Domain
.As<IInitializable>();
}
});
services.AddSingletonAs<ImageSharpAssetThumbnailGenerator>()
.As<IAssetThumbnailGenerator>();
}
}
}

111
src/Squidex/Config/Domain/WriteServices.cs → src/Squidex/Config/Domain/EntitiesServices.cs

@ -6,10 +6,12 @@
// ==========================================================================
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Migrate_01;
using Migrate_01.Migrations;
using Orleans;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities;
@ -19,25 +21,55 @@ using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Edm;
using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Users;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Migrations;
using Squidex.Pipeline;
using Squidex.Pipeline.CommandMiddlewares;
namespace Squidex.Config.Domain
{
public static class WriteServices
public static class EntitiesServices
{
public static void AddMyWriteServices(this IServiceCollection services)
public static void AddMyEntitiesServices(this IServiceCollection services, IConfiguration config)
{
services.AddSingletonAs<NoopUserEvents>()
.As<IUserEvents>();
var exposeSourceUrl = config.GetOptionalValue("assetStore:exposeSourceUrl", true);
services.AddSingletonAs<JintScriptEngine>()
.As<IScriptEngine>();
services.AddSingletonAs(c => new GraphQLUrlGenerator(
c.GetRequiredService<IOptions<MyUrlsOptions>>(),
c.GetRequiredService<IAssetStore>(),
exposeSourceUrl))
.As<IGraphQLUrlGenerator>();
services.AddSingletonAs<CachingGraphQLService>()
.As<IGraphQLService>();
services.AddSingletonAs<AppProvider>()
.As<IAppProvider>();
services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>();
services.AddSingletonAs<AppHistoryEventsCreator>()
.As<IHistoryEventsCreator>();
services.AddSingletonAs<ContentHistoryEventsCreator>()
.As<IHistoryEventsCreator>();
services.AddSingletonAs<SchemaHistoryEventsCreator>()
.As<IHistoryEventsCreator>();
services.AddSingletonAs<EdmModelBuilder>()
.AsSelf();
services.AddSingletonAs<InMemoryCommandBus>()
.As<ICommandBus>();
services.AddSingletonAs<ETagCommandMiddleware>()
.As<ICommandMiddleware>();
@ -54,19 +86,19 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<EnrichWithSchemaIdCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<SyncedGrainCommandMiddleware<AppCommand, AppGrain>>()
services.AddSingletonAs<AssetCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<AssetCommandMiddleware>()
services.AddSingletonAs<GrainCommandMiddleware<AppCommand, IAppGrain>>()
.As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<ContentCommand, ContentGrain>>()
services.AddSingletonAs<GrainCommandMiddleware<ContentCommand, IContentGrain>>()
.As<ICommandMiddleware>();
services.AddSingletonAs<SyncedGrainCommandMiddleware<SchemaCommand, SchemaGrain>>()
services.AddSingletonAs<GrainCommandMiddleware<SchemaCommand, ISchemaGrain>>()
.As<ICommandMiddleware>();
services.AddSingletonAs<SyncedGrainCommandMiddleware<RuleCommand, RuleGrain>>()
services.AddSingletonAs<GrainCommandMiddleware<RuleCommand, IRuleGrain>>()
.As<ICommandMiddleware>();
services.AddSingletonAs<CreateBlogCommandMiddleware>()
@ -75,26 +107,10 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<CreateProfileCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddTransientAs<MigrationPath>()
.As<IMigrationPath>();
services.AddTransientAs<ConvertEventStore>()
.As<IMigration>();
services.AddTransientAs<AddPatterns>()
.As<IMigration>();
services.AddTransientAs<RebuildContents>()
.As<IMigration>();
services.AddTransientAs<RebuildSnapshots>()
.As<IMigration>();
services.AddTransientAs<RebuildAssets>()
.As<IMigration>();
services.AddSingletonAs<JintScriptEngine>()
.As<IScriptEngine>();
services.AddTransientAs<Rebuilder>()
.AsSelf();
services.AddSingleton<Func<IGrainCallContext, string>>(DomainObjectGrainFormatter.Format);
services.AddTransientAs<AppGrain>()
.AsSelf();
@ -111,13 +127,13 @@ namespace Squidex.Config.Domain
services.AddTransientAs<SchemaGrain>()
.AsSelf();
services.AddSingleton<InitialPatterns>(c =>
services.AddSingleton(c =>
{
var config = c.GetRequiredService<IOptions<MyUIOptions>>();
var uiOptions = c.GetRequiredService<IOptions<MyUIOptions>>();
var result = new InitialPatterns();
foreach (var pattern in config.Value.RegexSuggestions)
foreach (var pattern in uiOptions.Value.RegexSuggestions)
{
if (!string.IsNullOrWhiteSpace(pattern.Key) &&
!string.IsNullOrWhiteSpace(pattern.Value))
@ -129,5 +145,32 @@ namespace Squidex.Config.Domain
return result;
});
}
public static void AddMyMigrationServices(this IServiceCollection services)
{
services.AddSingletonAs<Migrator>()
.AsSelf();
services.AddTransientAs<MigrationPath>()
.As<IMigrationPath>();
services.AddTransientAs<ConvertEventStore>()
.As<IMigration>();
services.AddTransientAs<AddPatterns>()
.As<IMigration>();
services.AddTransientAs<RebuildContents>()
.As<IMigration>();
services.AddTransientAs<RebuildSnapshots>()
.As<IMigration>();
services.AddTransientAs<RebuildAssets>()
.As<IMigration>();
services.AddTransientAs<Rebuilder>()
.AsSelf();
}
}
}

20
src/Squidex/Config/Domain/EventStoreServices.cs

@ -5,12 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using EventStore.ClientAPI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.States;
namespace Squidex.Config.Domain
{
@ -48,6 +51,23 @@ namespace Squidex.Config.Domain
.As<IEventStore>();
}
});
services.AddSingletonAs<OrleansEventNotifier>()
.As<IEventNotifier>()
.As<IInitializable>();
services.AddSingletonAs<DefaultStreamNameResolver>()
.As<IStreamNameResolver>();
services.AddSingletonAs<DefaultEventDataFormatter>()
.As<IEventDataFormatter>();
services.AddSingletonAs(c =>
{
var allEventConsumers = c.GetServices<IEventConsumer>();
return new EventConsumerFactory(n => allEventConsumers.FirstOrDefault(x => x.Name == n));
});
}
}
}

69
src/Squidex/Config/Domain/InfrastructureServices.cs

@ -5,66 +5,20 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using NodaTime;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Assets.ImageSharp;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.UsageTracking;
using Squidex.Pipeline;
#pragma warning disable RECS0092 // Convert field to readonly
namespace Squidex.Config.Domain
{
public static class InfrastructureServices
{
public static void AddMyInfrastructureServices(this IServiceCollection services, IConfiguration config)
public static void AddMyInfrastructureServices(this IServiceCollection services)
{
if (config.GetValue<bool>("logging:human"))
{
services.AddSingletonAs(c => new Func<IObjectWriter>(() => new JsonLogWriter(Formatting.Indented, true)));
}
else
{
services.AddSingletonAs(c => new Func<IObjectWriter>(() => new JsonLogWriter()));
}
var loggingFile = config.GetValue<string>("logging:file");
if (!string.IsNullOrWhiteSpace(loggingFile))
{
services.AddSingletonAs(new FileChannel(loggingFile))
.As<ILogChannel>()
.As<IInitializable>();
}
services.AddSingletonAs(c => new ApplicationInfoLogAppender(typeof(Program).Assembly, Guid.NewGuid()))
.As<ILogAppender>();
services.AddSingletonAs<ActionContextLogAppender>()
.As<ILogAppender>();
services.AddSingletonAs<TimestampLogAppender>()
.As<ILogAppender>();
services.AddSingletonAs<DebugLogChannel>()
.As<ILogChannel>();
services.AddSingletonAs<ConsoleLogChannel>()
.As<ILogChannel>();
services.AddSingletonAs<SemanticLog>()
.As<ISemanticLog>();
services.AddSingletonAs(SystemClock.Instance)
.As<IClock>();
@ -76,23 +30,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ActionContextAccessor>()
.As<IActionContextAccessor>();
services.AddSingletonAs<InMemoryCommandBus>()
.As<ICommandBus>();
services.AddSingletonAs<DefaultStreamNameResolver>()
.As<IStreamNameResolver>();
services.AddSingletonAs<ImageSharpAssetThumbnailGenerator>()
.As<IAssetThumbnailGenerator>();
services.AddSingletonAs<DefaultEventDataFormatter>()
.As<IEventDataFormatter>();
services.AddSingletonAs<Migrator>()
.AsSelf();
services.AddSingleton(typeof(IStore<>), typeof(Store<>));
}
}
}

64
src/Squidex/Config/Domain/LoggingServices.cs

@ -0,0 +1,64 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Pipeline;
#pragma warning disable RECS0092 // Convert field to readonly
namespace Squidex.Config.Domain
{
public static class LoggingServices
{
private static ILogChannel console = new ConsoleLogChannel();
private static ILogChannel file;
public static void AddMyLoggingServices(this IServiceCollection services, IConfiguration config)
{
if (config.GetValue<bool>("logging:human"))
{
services.AddSingletonAs(c => new Func<IObjectWriter>(() => new JsonLogWriter(Formatting.Indented, true)));
}
else
{
services.AddSingletonAs(c => new Func<IObjectWriter>(() => new JsonLogWriter()));
}
var loggingFile = config.GetValue<string>("logging:file");
if (!string.IsNullOrWhiteSpace(loggingFile))
{
services.AddSingletonAs(file ?? (file = new FileChannel(loggingFile)))
.As<ILogChannel>()
.As<IInitializable>();
}
services.AddSingletonAs(console)
.As<ILogChannel>();
services.AddSingletonAs(c => new ApplicationInfoLogAppender(typeof(Program).Assembly, Guid.NewGuid()))
.As<ILogAppender>();
services.AddSingletonAs<ActionContextLogAppender>()
.As<ILogAppender>();
services.AddSingletonAs<TimestampLogAppender>()
.As<ILogAppender>();
services.AddSingletonAs<DebugLogChannel>()
.As<ILogChannel>();
services.AddSingletonAs<SemanticLog>()
.As<ISemanticLog>();
}
}
}

40
src/Squidex/Config/Domain/PubSubServices.cs

@ -1,40 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using StackExchange.Redis;
namespace Squidex.Config.Domain
{
public static class PubSubServices
{
public static void AddMyPubSubServices(this IServiceCollection services, IConfiguration config)
{
config.ConfigureByOption("pubSub:type", new Options
{
["InMemory"] = () =>
{
services.AddSingletonAs<InMemoryPubSub>()
.As<IPubSub>();
},
["Redis"] = () =>
{
var configuration = config.GetRequiredValue("pubsub:redis:configuration");
var redis = Singletons<IConnectionMultiplexer>.GetOrAddLazy(configuration, s => ConnectionMultiplexer.Connect(s));
services.AddSingletonAs(c => new RedisPubSub(redis, c.GetRequiredService<ISemanticLog>()))
.As<IPubSub>()
.As<IInitializable>();
}
});
}
}
}

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

@ -1,141 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions;
using Squidex.Domain.Apps.Core.HandleRules.Triggers;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Entities.Apps.Services.Implementations;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Edm;
using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.States;
using Squidex.Pipeline;
namespace Squidex.Config.Domain
{
public static class ReadServices
{
public static void AddMyReadServices(this IServiceCollection services, IConfiguration config)
{
var consumeEvents = config.GetOptionalValue("eventStore:consume", false);
if (consumeEvents)
{
services.AddTransient<EventConsumerGrain>();
services.AddSingletonAs<EventConsumerGrainManager>()
.As<IRunnable>();
services.AddSingletonAs<RuleDequeuer>()
.As<IRunnable>();
services.AddSingletonAs<ContentScheduler>()
.As<IRunnable>();
}
var exposeSourceUrl = config.GetOptionalValue("assetStore:exposeSourceUrl", true);
services.AddSingletonAs(c => new GraphQLUrlGenerator(
c.GetRequiredService<IOptions<MyUrlsOptions>>(),
c.GetRequiredService<IAssetStore>(),
exposeSourceUrl))
.As<IGraphQLUrlGenerator>();
services.AddSingletonAs<StateFactory>()
.As<IInitializable>()
.As<IStateFactory>();
services.AddSingletonAs(c => c.GetService<IOptions<MyUsageOptions>>()?.Value?.Plans.OrEmpty());
services.AddSingletonAs<CachingGraphQLService>()
.As<IGraphQLService>();
services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>();
services.AddSingletonAs<ConfigAppPlansProvider>()
.As<IAppPlansProvider>();
services.AddSingletonAs<AssetUserPictureStore>()
.As<IUserPictureStore>();
services.AddSingletonAs<AppHistoryEventsCreator>()
.As<IHistoryEventsCreator>();
services.AddSingletonAs<ContentHistoryEventsCreator>()
.As<IHistoryEventsCreator>();
services.AddSingletonAs<SchemaHistoryEventsCreator>()
.As<IHistoryEventsCreator>();
services.AddSingletonAs<NoopAppPlanBillingManager>()
.As<IAppPlanBillingManager>();
services.AddSingletonAs<AppProvider>()
.As<IAppProvider>();
services.AddSingletonAs<RuleEventFormatter>()
.AsSelf();
services.AddSingletonAs<AssetChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<ContentChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<AlgoliaActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<AzureQueueActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<ElasticSearchActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<FastlyActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<SlackActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<WebhookActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<DefaultEventNotifier>()
.As<IEventNotifier>();
services.AddSingletonAs<RuleEnqueuer>()
.As<IEventConsumer>();
services.AddSingletonAs(c =>
{
var allEventConsumers = c.GetServices<IEventConsumer>();
return new EventConsumerFactory(n => allEventConsumers.FirstOrDefault(x => x.Name == n));
});
services.AddSingletonAs<EdmModelBuilder>()
.AsSelf();
services.AddSingletonAs<RuleService>()
.AsSelf();
}
}
}

55
src/Squidex/Config/Domain/RuleServices.cs

@ -0,0 +1,55 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions;
using Squidex.Domain.Apps.Core.HandleRules.Triggers;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Config.Domain
{
public static class RuleServices
{
public static void AddMyRuleServices(this IServiceCollection services)
{
services.AddSingletonAs<AssetChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<ContentChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<AlgoliaActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<AzureQueueActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<ElasticSearchActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<FastlyActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<SlackActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<WebhookActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<RuleEnqueuer>()
.As<IEventConsumer>();
services.AddSingletonAs<RuleEventFormatter>()
.AsSelf();
services.AddSingletonAs<RuleService>()
.AsSelf();
}
}
}

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

@ -27,15 +27,17 @@ namespace Squidex.Config.Domain
public static class SerializationServices
{
private static readonly TypeNameRegistry TypeNameRegistry =
new TypeNameRegistry()
.MapUnmapped(SquidexCoreModel.Assembly)
.MapUnmapped(SquidexEvents.Assembly)
.MapUnmapped(SquidexInfrastructure.Assembly)
.MapUnmapped(SquidexMigrations.Assembly);
new TypeNameRegistry()
.MapUnmapped(SquidexCoreModel.Assembly)
.MapUnmapped(SquidexEvents.Assembly)
.MapUnmapped(SquidexInfrastructure.Assembly)
.MapUnmapped(SquidexMigrations.Assembly);
private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry);
private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling)
public static readonly JsonSerializerSettings DefaultJsonSettings = new JsonSerializerSettings();
public static readonly JsonSerializer DefaultJsonSerializer;
private static void ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling)
{
settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry);
@ -65,22 +67,24 @@ namespace Squidex.Config.Domain
settings.TypeNameHandling = typeNameHandling;
settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
}
static SerializationServices()
{
ConfigureJson(DefaultJsonSettings, TypeNameHandling.Auto);
DefaultJsonSerializer = JsonSerializer.Create(DefaultJsonSettings);
return settings;
BsonJsonConvention.Register(DefaultJsonSerializer);
}
public static IServiceCollection AddMySerializers(this IServiceCollection services)
{
var serializerSettings = ConfigureJson(new JsonSerializerSettings(), TypeNameHandling.Auto);
var serializerInstance = JsonSerializer.Create(serializerSettings);
services.AddSingletonAs(t => FieldRegistry);
services.AddSingletonAs(t => serializerSettings);
services.AddSingletonAs(t => serializerInstance);
services.AddSingletonAs(t => DefaultJsonSettings);
services.AddSingletonAs(t => DefaultJsonSerializer);
services.AddSingletonAs(t => TypeNameRegistry);
BsonJsonConvention.Register(serializerInstance);
return services;
}

24
src/Squidex/Config/Domain/StoreServices.cs

@ -73,17 +73,6 @@ namespace Squidex.Config.Domain
.As<ISnapshotStore<EventConsumerState, string>>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoUserStore(mongoDatabase))
.As<IUserStore<IUser>>()
.As<IUserFactory>()
.As<IUserResolver>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoRoleStore(mongoDatabase))
.As<IRoleStore<IRole>>()
.As<IRoleFactory>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoPersistedGrantStore(mongoDatabase))
.As<IPersistedGrantStore>()
.As<IInitializable>();
@ -96,6 +85,17 @@ namespace Squidex.Config.Domain
.As<IRuleEventRepository>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoUserStore(mongoDatabase))
.As<IUserStore<IUser>>()
.As<IUserFactory>()
.As<IUserResolver>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoRoleStore(mongoDatabase))
.As<IRoleStore<IRole>>()
.As<IRoleFactory>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoAppRepository(mongoDatabase))
.As<IAppRepository>()
.As<ISnapshotStore<AppState, Guid>>()
@ -133,6 +133,8 @@ namespace Squidex.Config.Domain
.As<IInitializable>();
}
});
services.AddSingleton(typeof(IStore<>), typeof(Store<>));
}
}
}

37
src/Squidex/Config/Domain/SubscriptionServices.cs

@ -0,0 +1,37 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Entities.Apps.Services.Implementations;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
namespace Squidex.Config.Domain
{
public static class SubscriptionServices
{
public static void AddMySubscriptionServices(this IServiceCollection services, IConfiguration config)
{
services.AddSingletonAs(c => c.GetService<IOptions<MyUsageOptions>>()?.Value?.Plans.OrEmpty());
services.AddSingletonAs<ConfigAppPlansProvider>()
.As<IAppPlansProvider>();
services.AddSingletonAs<NoopAppPlanBillingManager>()
.As<IAppPlanBillingManager>();
services.AddSingletonAs<AssetUserPictureStore>()
.As<IUserPictureStore>();
services.AddSingletonAs<NoopUserEvents>()
.As<IUserEvents>();
}
}
}

60
src/Squidex/Config/Orleans/ClientWrapper.cs

@ -0,0 +1,60 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Net;
using Orleans;
using Orleans.Configuration;
using Orleans.Runtime;
using Squidex.Domain.Apps.Entities;
using Squidex.Infrastructure;
namespace Squidex.Config.Orleans
{
public sealed class ClientWrapper : DisposableObjectBase, IInitializable, IDisposable
{
private readonly IClusterClient client;
public IClusterClient Client
{
get { return client; }
}
public ClientWrapper()
{
client = new ClientBuilder()
.UseDashboard()
.UseStaticClustering(options =>
{
options.Gateways.Add(new IPEndPoint(IPAddress.Loopback, 40000).ToGatewayUri());
})
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "squidex";
})
.ConfigureApplicationParts(builder =>
{
builder.AddApplicationPart(SquidexEntities.Assembly);
builder.AddApplicationPart(SquidexInfrastructure.Assembly);
})
.Build();
}
public void Initialize()
{
client.Connect().Wait();
}
protected override void DisposeObject(bool disposing)
{
if (disposing)
{
client.Close().Wait();
}
}
}
}

40
src/Squidex/Config/Orleans/OrleansServices.cs

@ -0,0 +1,40 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Orleans;
using Squidex.Infrastructure;
namespace Squidex.Config.Orleans
{
public static class OrleansServices
{
public static void AddOrleansSilo(this IServiceCollection services)
{
services.AddSingletonAs<SiloWrapper>()
.As<IInitializable>();
}
public static void AddOrleansClient(this IServiceCollection services)
{
services.AddServicesForSelfHostedDashboard(null, options =>
{
options.HideTrace = true;
});
services.AddSingletonAs<ClientWrapper>()
.As<IInitializable>()
.AsSelf();
services.AddSingletonAs(c => c.GetRequiredService<ClientWrapper>().Client)
.As<IClusterClient>();
services.AddSingletonAs(c => c.GetRequiredService<ClientWrapper>().Client)
.As<IGrainFactory>();
}
}
}

42
src/Squidex/Config/Orleans/SiloServices.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Hosting;
namespace Squidex.Config.Orleans
{
public static class SiloServices
{
public static void AddAppSiloServices(this IServiceCollection services, IConfiguration config)
{
config.ConfigureByOption("store:type", new Options
{
["MongoDB"] = () =>
{
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration");
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database");
services.AddMongoDBMembershipTable(c =>
{
c.ConnectionString = mongoConfiguration;
c.CollectionPrefix = "Orleans_";
c.DatabaseName = mongoDatabaseName;
});
services.AddMongoDBReminders(c =>
{
c.ConnectionString = mongoConfiguration;
c.CollectionPrefix = "Orleans_";
c.DatabaseName = mongoDatabaseName;
});
}
});
}
}
}

105
src/Squidex/Config/Orleans/SiloWrapper.cs

@ -0,0 +1,105 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;
using Squidex.Config.Domain;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Log.Adapter;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Config.Orleans
{
public class SiloWrapper : DisposableObjectBase, IInitializable, IDisposable
{
private readonly ISiloHost silo;
internal sealed class Source : IConfigurationSource
{
private readonly IConfigurationProvider configurationProvider;
public Source(IConfigurationProvider configurationProvider)
{
this.configurationProvider = configurationProvider;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return configurationProvider;
}
}
public SiloWrapper(IConfiguration configuration)
{
J.Serializer = SerializationServices.DefaultJsonSerializer;
silo = new SiloHostBuilder()
.UseDashboard(options => options.HostSelf = true)
.AddStartupTask<Bootstrap<IContentSchedulerGrain>>()
.AddStartupTask<Bootstrap<IEventConsumerManagerGrain>>()
.AddStartupTask<Bootstrap<IRuleDequeuerGrain>>()
.ConfigureEndpoints(Dns.GetHostName(), 11111, 40000, listenOnAnyHostAddress: true)
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "squidex";
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("logging"));
builder.AddSemanticLog();
builder.AddFilter("Orleans.Runtime.SiloControl", LogLevel.Warning);
})
.ConfigureApplicationParts(builder =>
{
builder.AddApplicationPart(SquidexEntities.Assembly);
builder.AddApplicationPart(SquidexInfrastructure.Assembly);
})
.ConfigureServices((context, services) =>
{
services.AddAppSiloServices(context.Configuration);
services.AddAppServices(context.Configuration);
services.Configure<ProcessExitHandlingOptions>(options => options.FastKillOnProcessExit = false);
})
.ConfigureAppConfiguration((hostContext, builder) =>
{
if (configuration is IConfigurationRoot root)
{
foreach (var provider in root.Providers)
{
builder.Add(new Source(provider));
}
}
})
.Build();
}
public void Initialize()
{
silo.StartAsync().Wait();
}
protected override void DisposeObject(bool disposing)
{
if (disposing)
{
Task.Run(() => silo.StopAsync()).Wait();
}
}
}
}

16
src/Squidex/Program.cs

@ -7,6 +7,10 @@
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Hosting;
using Squidex.Infrastructure.Log.Adapter;
namespace Squidex
@ -20,13 +24,21 @@ namespace Squidex
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<WebStartup>()
.ConfigureLogging(builder =>
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("logging"));
builder.AddSemanticLog();
})
.ConfigureAppConfiguration((hostContext, builder) =>
{
builder.AddAppConfiguration(hostContext.HostingEnvironment.EnvironmentName, args);
builder.Sources.Clear();
builder.AddJsonFile("appsettings.json", true, true);
builder.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);
builder.AddEnvironmentVariables();
builder.AddCommandLine(args);
})
.Build()
.Run();

59
src/Squidex/Squidex.csproj

@ -48,38 +48,41 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="4.2.1" />
<PackageReference Include="Ben.BlockingDetector" Version="0.0.2" />
<PackageReference Include="Elasticsearch.Net" Version="6.0.1" />
<PackageReference Include="EventStore.ClientAPI.NetCore" Version="4.0.2-rc" />
<PackageReference Include="IdentityServer4" Version="2.1.1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.3.0" />
<PackageReference Include="Algolia.Search" Version="4.2.2" />
<PackageReference Include="Ben.BlockingDetector" Version="0.0.3" />
<PackageReference Include="EventStore.ClientAPI.NetCore" Version="4.1.0.23" />
<PackageReference Include="IdentityServer4" Version="2.1.2" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.4.0" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.0.4" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.3" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.1" />
<PackageReference Include="Microsoft.Orleans.Client" Version="2.0.0-rc1" />
<PackageReference Include="Microsoft.Orleans.Server " Version="2.0.0-beta3" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.3" />
<PackageReference Include="MongoDB.Driver" Version="2.5.0" />
<PackageReference Include="NJsonSchema" Version="9.10.19" />
<PackageReference Include="NJsonSchema" Version="9.10.35" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="11.12.16" />
<PackageReference Include="NSwag.AspNetCore" Version="11.15.4" />
<PackageReference Include="OpenCover" Version="4.6.519" />
<PackageReference Include="Orleans.Providers.MongoDB" Version="2.0.0-rc5" />
<PackageReference Include="OrleansDashboard" Version="2.0.0-rc2a" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="ReportGenerator" Version="3.1.1" />
<PackageReference Include="ReportGenerator" Version="3.1.2" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Linq" Version="4.3.0" />
@ -101,4 +104,8 @@
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.Orleans.Server" Version="2.0.0-rc2" />
</ItemGroup>
</Project>

6
src/Squidex/WebStartup.cs

@ -13,8 +13,10 @@ using Microsoft.Extensions.DependencyInjection;
using Squidex.Areas.Api;
using Squidex.Areas.Frontend;
using Squidex.Areas.IdentityServer;
using Squidex.Areas.OrleansDashboard;
using Squidex.Areas.Portal;
using Squidex.Config.Domain;
using Squidex.Config.Orleans;
using Squidex.Config.Web;
namespace Squidex
@ -30,6 +32,8 @@ namespace Squidex
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddOrleansSilo();
services.AddOrleansClient();
services.AddAppServices(configuration);
return services.BuildServiceProvider();
@ -48,8 +52,8 @@ namespace Squidex
app.ConfigureApi();
app.ConfigurePortal();
app.ConfigureOrleansDashboard();
app.ConfigureIdentityServer();
app.ConfigureFrontend();
}
}

6
src/Squidex/app-config/helpers.js

@ -1,8 +1,4 @@
// ReSharper disable InconsistentNaming
// ReSharper disable PossiblyUnassignedProperty
// ReSharper disable InconsistentNaming
var path = require('path');
var path = require('path');
var appRoot = path.resolve(__dirname, '..');

2
src/Squidex/app-config/karma.conf.js

@ -34,7 +34,7 @@ module.exports = function (config) {
},
/*
* leave Jasmine Spec Runner output visible in browser
* Leave Jasmine Spec Runner output visible in browser
*/
client: {
clearContext: false

3
src/Squidex/app-config/webpack.config.js

@ -1,6 +1,3 @@
// ReSharper disable InconsistentNaming
// ReSharper disable PossiblyUnassignedProperty
var webpack = require('webpack'),
path = require('path'),
HtmlWebpackPlugin = require('html-webpack-plugin'),

5
src/Squidex/app-config/webpack.run.base.js

@ -1,7 +1,4 @@
// ReSharper disable InconsistentNaming
// ReSharper disable PossiblyUnassignedProperty
var webpack = require('webpack'),
 var webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
commonConfig = require('./webpack.config.js'),

5
src/Squidex/app-config/webpack.run.dev.js

@ -1,7 +1,4 @@
// ReSharper disable InconsistentNaming
// ReSharper disable PossiblyUnassignedProperty
var webpackMerge = require('webpack-merge'),
 var webpackMerge = require('webpack-merge'),
ExtractTextPlugin = require('extract-text-webpack-plugin'),
runConfig = require('./webpack.run.base.js'),
helpers = require('./helpers');

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

@ -83,7 +83,7 @@ export class RuleEventsPageComponent implements OnInit {
} else if (status === 'Failed') {
return 'danger';
} else if (status === 'Pending') {
return 'default';
return 'secondary';
} else {
return status.toLowerCase();
}

23
src/Squidex/appsettings.json

@ -42,32 +42,11 @@
}
},
"logging": {
/*
* Setting the flag to true, enables well formatteds json logs.
*/
"human": true
},
/*
* The pub sub mechanmism distributes messages between the nodes.
*/
"pubSub": {
/*
* Define the type of the read store.
*
* Supported: InMemory (for single node only), Redis (for cluster)
*/
"type": "InMemory",
"redis": {
/*
* Connection string to your redis server.
*
* Read More: https://github.com/ServiceStack/ServiceStack.Redis#redis-connection-strings
*/
"configuration": "localhost:6379,resolveDns=1"
}
"human": true
},
"assetStore": {

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

Loading…
Cancel
Save