Browse Source

App State fixes

pull/169/head
Sebastian Stehle 9 years ago
parent
commit
a1d57f6f49
  1. 53
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs
  2. 70
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs
  3. 143
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  4. 15
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs
  5. 4
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  6. 74
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs
  7. 73
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs
  8. 188
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
  9. 2
      src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs
  10. 23
      src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs
  11. 20
      src/Squidex.Domain.Apps.Read/Apps/Services/IAppProvider.cs
  12. 121
      src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs
  13. 13
      src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs
  14. 49
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs
  15. 6
      src/Squidex.Domain.Apps.Read/EntityMapper.cs
  16. 16
      src/Squidex.Domain.Apps.Read/IAppProvider.cs
  17. 16
      src/Squidex.Domain.Apps.Read/Schemas/ISchemaEventConsumer.cs
  18. 23
      src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs
  19. 20
      src/Squidex.Domain.Apps.Read/Schemas/Services/ISchemaProvider.cs
  20. 131
      src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
  21. 10
      src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs
  22. 18
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs
  23. 7
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs
  24. 59
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs
  25. 45
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs
  26. 20
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs
  27. 14
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrainState.cs
  28. 3
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs
  29. 1
      src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs
  30. 89
      src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppProvider.cs
  31. 71
      src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs
  32. 1
      src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs
  33. 5
      src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs
  34. 8
      src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs
  35. 13
      src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs
  36. 10
      src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs
  37. 18
      src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs
  38. 15
      src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs
  39. 6
      src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs
  40. 13
      src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs
  41. 3
      src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerGrain.cs
  42. 3
      src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerRegistryGrain.cs
  43. 5
      src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerGrain.cs
  44. 5
      src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerRegistryGrain.cs
  45. 9
      src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs
  46. 49
      src/Squidex.Infrastructure/Json/Orleans/J.cs
  47. 78
      src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs
  48. 73
      src/Squidex.Infrastructure/Json/Orleans/JsonState.cs
  49. 5
      src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs
  50. 85
      src/Squidex.Infrastructure/Reflection/SimpleCopier.cs
  51. 114
      src/Squidex.Infrastructure/Reflection/SimpleMapper.cs
  52. 27
      src/Squidex/Config/Domain/ReadServices.cs
  53. 21
      src/Squidex/Config/Domain/StoreServices.cs
  54. 12
      src/Squidex/Config/Identity/LazyClientStore.cs
  55. 12
      src/Squidex/Config/Orleans/ClientServices.cs
  56. 22
      src/Squidex/Config/Orleans/CustomJsonSerializer.cs
  57. 23
      src/Squidex/Config/Orleans/CustomMongoDbStorageProvider.cs
  58. 9
      src/Squidex/Config/Orleans/SiloExtensions.cs
  59. 14
      src/Squidex/Config/Orleans/SiloServices.cs
  60. 10
      src/Squidex/Controllers/Api/Apps/AppsController.cs
  61. 4
      src/Squidex/Controllers/Api/Assets/AssetsController.cs
  62. 2
      src/Squidex/Controllers/Api/EventConsumers/EventConsumersController.cs
  63. 14
      src/Squidex/Controllers/Api/Schemas/SchemasController.cs
  64. 11
      src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs
  65. 7
      src/Squidex/Controllers/ControllerBase.cs
  66. 8
      src/Squidex/Pipeline/AppApiFilter.cs
  67. 14
      src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs
  68. 2
      src/Squidex/Program.cs
  69. 131
      tests/Squidex.Domain.Apps.Read.Tests/Apps/CachingAppProviderTests.cs
  70. 3
      tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs
  71. 2
      tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs
  72. 3
      tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs
  73. 5
      tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs
  74. 5
      tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs

53
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs

@ -1,53 +0,0 @@
// ==========================================================================
// MongoAppEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Apps
{
public sealed class MongoAppEntity : MongoEntity, IAppEntity
{
[BsonRequired]
[BsonElement]
public string Name { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public string PlanId { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public string PlanOwner { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public string[] ContributorIds { get; set; }
[BsonRequired]
[BsonElement]
[BsonJson]
public AppClients Clients { get; set; }
[BsonRequired]
[BsonElement]
[BsonJson]
public AppContributors Contributors { get; set; }
[BsonRequired]
[BsonElement]
[BsonJson]
public LanguagesConfig LanguagesConfig { get; set; }
}
}

70
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs

@ -1,70 +0,0 @@
// ==========================================================================
// MongoAppRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Apps
{
public partial class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, IAppEventConsumer
{
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "Projections_Apps";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection)
{
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ContributorIds));
}
protected override MongoCollectionSettings CollectionSettings()
{
return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority };
}
public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId)
{
var appEntities =
await Collection.Find(s => s.ContributorIds.Contains(subjectId))
.ToListAsync();
return appEntities;
}
public async Task<IAppEntity> FindAppAsync(Guid id)
{
var appEntity =
await Collection.Find(s => s.Id == id)
.FirstOrDefaultAsync();
return appEntity;
}
public async Task<IAppEntity> FindAppAsync(string name)
{
var appEntity =
await Collection.Find(s => s.Name == name)
.FirstOrDefaultAsync();
return appEntity;
}
}
}

143
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs

@ -1,143 +0,0 @@
// ==========================================================================
// MongoAppRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Apps.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Read.MongoDb.Apps
{
public partial class MongoAppRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^app-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected Task On(AppCreated @event, EnvelopeHeaders headers)
{
return Collection.CreateAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
a.Clients = new AppClients();
a.Contributors = new AppContributors();
a.LanguagesConfig = LanguagesConfig.Build(Language.EN);
});
}
protected Task On(AppPlanChanged @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
});
}
protected Task On(AppClientAttached @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppClientRevoked @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppClientRenamed @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppClientUpdated @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Contributors.Apply(@event);
});
}
protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Contributors.Apply(@event);
});
}
protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
}
protected Task On(AppLanguageRemoved @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
}
protected Task On(AppLanguageUpdated @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
}
private async Task UpdateAppAsync(AppEvent @event, EnvelopeHeaders headers, Action<MongoAppEntity> updater)
{
await Collection.UpdateAsync(@event, headers, a =>
{
updater(a);
a.ContributorIds = a.Contributors.Keys.ToArray();
});
}
}
}

15
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs

@ -19,7 +19,6 @@ using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
@ -29,7 +28,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
{
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
private readonly ISchemaProvider schemas;
private readonly IAppProvider appProvider;
protected static FilterDefinitionBuilder<MongoContentEntity> Filter
{
@ -63,13 +62,13 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
}
}
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas)
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider)
{
Guard.NotNull(database, nameof(database));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
this.database = database;
this.schemas = schemas;
this.appProvider = appProvider;
}
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
@ -177,11 +176,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
return contentEntity;
}
private async Task ForSchemaAsync(Guid appId, Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action)
private async Task ForSchemaAsync(NamedId<Guid> appId, Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action)
{
var collection = GetCollection(appId);
var collection = GetCollection(appId.Id);
var schema = await schemas.FindSchemaByIdAsync(schemaId, true);
var schema = await appProvider.GetSchemaAsync(appId.Name, schemaId, true);
if (schema == null)
{

4
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schema) =>
return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) =>
{
return collection.CreateAsync(@event, headers, content =>
{
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schema) =>
return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) =>
{
var idData = @event.Data?.ToIdModel(schema.SchemaDef, true);

74
src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs

@ -1,74 +0,0 @@
// ==========================================================================
// MongoSchemaEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Schemas
{
public sealed class MongoSchemaEntity : MongoEntity, ISchemaEntity
{
[BsonRequired]
[BsonElement]
public string Name { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
[BsonRequired]
[BsonElement]
public RefToken CreatedBy { get; set; }
[BsonRequired]
[BsonElement]
public RefToken LastModifiedBy { get; set; }
[BsonRequired]
[BsonElement]
public bool IsPublished { get; set; }
[BsonRequired]
[BsonElement]
public bool IsDeleted { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string ScriptQuery { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string ScriptCreate { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string ScriptUpdate { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string ScriptDelete { get; set; }
[BsonIgnoreIfNull]
[BsonElement]
public string ScriptChange { get; set; }
[BsonRequired]
[BsonElement]
[BsonJson]
public Schema SchemaDef { get; set; }
}
}

73
src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs

@ -1,73 +0,0 @@
// ==========================================================================
// MongoSchemaRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Schemas
{
public partial class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository, ISchemaEventConsumer
{
private readonly FieldRegistry registry;
public MongoSchemaRepository(IMongoDatabase database, FieldRegistry registry)
: base(database)
{
Guard.NotNull(registry, nameof(registry));
this.registry = registry;
}
protected override string CollectionName()
{
return "Projections_Schemas";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.IsDeleted).Ascending(x => x.Name)));
}
public async Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId)
{
var schemaEntities =
await Collection.Find(s => s.AppId == appId && !s.IsDeleted)
.ToListAsync();
return schemaEntities.OfType<ISchemaEntity>().ToList();
}
public async Task<ISchemaEntity> FindSchemaAsync(Guid appId, string name)
{
var schemaEntity =
await Collection.Find(s => s.AppId == appId && !s.IsDeleted && s.Name == name)
.FirstOrDefaultAsync();
return schemaEntity;
}
public async Task<ISchemaEntity> FindSchemaAsync(Guid schemaId)
{
var schemaEntity =
await Collection.Find(s => s.Id == schemaId)
.FirstOrDefaultAsync();
return schemaEntity;
}
}
}

188
src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs

@ -1,188 +0,0 @@
// ==========================================================================
// MongoSchemaRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Domain.Apps.Events.Schemas.Old;
using Squidex.Domain.Apps.Events.Schemas.Utils;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
#pragma warning disable CS0612 // Type or member is obsolete
namespace Squidex.Domain.Apps.Read.MongoDb.Schemas
{
public partial class MongoSchemaRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^schema-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected Task On(SchemaCreated @event, EnvelopeHeaders headers)
{
return Collection.CreateAsync(@event, headers, s =>
{
s.SchemaDef = SchemaEventDispatcher.Create(@event, registry);
SimpleMapper.Map(@event, s);
});
}
protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldLocked @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldHidden @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldShown @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldDisabled @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldEnabled @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldUpdated @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaFieldsReordered @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaUpdated @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaPublished @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaUnpublished @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldAdded @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event, registry);
});
}
protected Task On(ScriptsConfigured @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
SimpleMapper.Map(@event, s);
});
}
protected Task On(SchemaDeleted @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
s.IsDeleted = true;
});
}
protected Task On(WebhookAdded @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
/* NOOP */
});
}
protected Task On(WebhookDeleted @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
/* NOOP */
});
}
private Task UpdateSchemaAsync(SquidexEvent @event, EnvelopeHeaders headers, Action<MongoSchemaEntity> updater)
{
return Collection.UpdateAsync(@event, headers, s =>
{
updater(s);
s.IsPublished = s.SchemaDef.IsPublished;
});
}
}
}

2
src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs

@ -12,6 +12,8 @@ namespace Squidex.Domain.Apps.Read.Apps
{
public interface IAppEntity : IEntity, IEntityWithVersion
{
string Etag { get; }
string Name { get; }
string PlanId { get; }

23
src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs

@ -1,23 +0,0 @@
// ==========================================================================
// IAppRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Apps.Repositories
{
public interface IAppRepository
{
Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId);
Task<IAppEntity> FindAppAsync(Guid appId);
Task<IAppEntity> FindAppAsync(string name);
}
}

20
src/Squidex.Domain.Apps.Read/Apps/Services/IAppProvider.cs

@ -1,20 +0,0 @@
// ==========================================================================
// IAppProvider.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Apps.Services
{
public interface IAppProvider
{
Task<IAppEntity> FindAppByIdAsync(Guid id);
Task<IAppEntity> FindAppByNameAsync(string name);
}
}

121
src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs

@ -1,121 +0,0 @@
// ==========================================================================
// CachingAppProvider.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations
{
public class CachingAppProvider : CachingProviderBase, IAppProvider, IEventConsumer
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(30);
private readonly IAppRepository repository;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return string.Empty; }
}
public CachingAppProvider(IMemoryCache cache, IAppRepository repository)
: base(cache)
{
Guard.NotNull(cache, nameof(cache));
this.repository = repository;
}
public async Task<IAppEntity> FindAppByIdAsync(Guid appId)
{
var cacheKey = BuildIdCacheKey(appId);
if (!Cache.TryGetValue(cacheKey, out IAppEntity result))
{
result = await repository.FindAppAsync(appId);
Cache.Set(cacheKey, result, CacheDuration);
if (result != null)
{
Cache.Set(BuildNameCacheKey(result.Name), result, CacheDuration);
}
}
return result;
}
public async Task<IAppEntity> FindAppByNameAsync(string name)
{
Guard.NotNullOrEmpty(name, nameof(name));
var cacheKey = BuildNameCacheKey(name);
if (!Cache.TryGetValue(cacheKey, out IAppEntity result))
{
result = await repository.FindAppAsync(name);
Cache.Set(cacheKey, result, CacheDuration);
if (result != null)
{
Cache.Set(BuildIdCacheKey(result.Id), result, CacheDuration);
}
}
return result;
}
public Task On(Envelope<IEvent> @event)
{
void Remove(NamedId<Guid> id)
{
var cacheKeyById = BuildIdCacheKey(id.Id);
var cacheKeyByName = BuildNameCacheKey(id.Name);
Cache.Remove(cacheKeyById);
Cache.Remove(cacheKeyByName);
Cache.Invalidate(cacheKeyById);
Cache.Invalidate(cacheKeyByName);
}
if (@event.Payload is AppEvent appEvent)
{
Remove(appEvent.AppId);
}
return TaskHelper.Done;
}
private static string BuildNameCacheKey(string name)
{
return $"App_Ids_{name}";
}
private static string BuildIdCacheKey(Guid schemaId)
{
return $"App_Names_{schemaId}";
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
}
}

13
src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs

@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Contents.Edm;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
@ -30,23 +29,23 @@ namespace Squidex.Domain.Apps.Read.Contents
public sealed class ContentQueryService : IContentQueryService
{
private readonly IContentRepository contentRepository;
private readonly ISchemaProvider schemas;
private readonly IAppProvider appProvider;
private readonly IScriptEngine scriptEngine;
private readonly EdmModelBuilder modelBuilder;
public ContentQueryService(
IContentRepository contentRepository,
ISchemaProvider schemas,
IAppProvider appProvider,
IScriptEngine scriptEngine,
EdmModelBuilder modelBuilder)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
this.contentRepository = contentRepository;
this.schemas = schemas;
this.appProvider = appProvider;
this.scriptEngine = scriptEngine;
this.modelBuilder = modelBuilder;
}
@ -156,12 +155,12 @@ namespace Squidex.Domain.Apps.Read.Contents
if (Guid.TryParse(schemaIdOrName, out var id))
{
schema = await schemas.FindSchemaByIdAsync(id);
schema = await appProvider.GetSchemaAsync(app.Name, id);
}
if (schema == null)
{
schema = await schemas.FindSchemaByNameAsync(app.Id, schemaIdOrName);
schema = await appProvider.GetSchemaAsync(app.Name, schemaIdOrName);
}
if (schema == null)

49
src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs

@ -11,65 +11,36 @@ using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService, IEventConsumer
public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60);
private readonly IContentQueryService contentQuery;
private readonly IGraphQLUrlGenerator urlGenerator;
private readonly IAssetRepository assetRepository;
private readonly ISchemaRepository schemaRepository;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^(schema-)|(apps-)"; }
}
private readonly IAppProvider appProvider;
public CachingGraphQLService(IMemoryCache cache,
IAppProvider appProvider,
IAssetRepository assetRepository,
IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator,
ISchemaRepository schemaRepository)
IGraphQLUrlGenerator urlGenerator)
: base(cache)
{
Guard.NotNull(schemaRepository, nameof(schemaRepository));
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(contentQuery, nameof(urlGenerator));
Guard.NotNull(contentQuery, nameof(contentQuery));
this.appProvider = appProvider;
this.assetRepository = assetRepository;
this.contentQuery = contentQuery;
this.urlGenerator = urlGenerator;
this.schemaRepository = schemaRepository;
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public Task On(Envelope<IEvent> @event)
{
if (@event.Payload is AppEvent appEvent)
{
Cache.Remove(CreateCacheKey(appEvent.AppId.Id));
}
return TaskHelper.Done;
}
public async Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query)
@ -90,13 +61,13 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
private async Task<GraphQLModel> GetModelAsync(IAppEntity app)
{
var cacheKey = CreateCacheKey(app.Id);
var cacheKey = CreateCacheKey(app.Id, app.Etag);
var modelContext = Cache.Get<GraphQLModel>(cacheKey);
if (modelContext == null)
{
var allSchemas = await schemaRepository.QueryAllAsync(app.Id);
var allSchemas = await appProvider.GetSchemasAsync(app.Name);
modelContext = new GraphQLModel(app, allSchemas.Where(x => x.IsPublished), urlGenerator);
@ -106,9 +77,9 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
return modelContext;
}
private static object CreateCacheKey(Guid appId)
private static object CreateCacheKey(Guid appId, string etag)
{
return $"GraphQLModel_{appId}";
return $"GraphQLModel_{appId}_{etag}";
}
}
}

6
src/Squidex.Domain.Apps.Read/EntityMapper.cs

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Read
{
public static class EntityMapper
{
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : IEntity, new()
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity, new()
{
var entity = new T();
@ -29,13 +29,13 @@ namespace Squidex.Domain.Apps.Read
return entity.Update(@event, headers, updater);
}
public static T Update<T>(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : IEntity, new()
public static T Update<T>(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity, new()
{
SetVersion(headers, entity);
SetLastModified(headers, entity);
SetLastModifiedBy(@event, entity);
updater(entity);
updater?.Invoke(entity);
return entity;
}

16
src/Squidex.Domain.Apps.Read/IAppState.cs → src/Squidex.Domain.Apps.Read/IAppProvider.cs

@ -1,5 +1,5 @@
// ==========================================================================
// IAppState.cs
// IApps.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -15,17 +15,19 @@ using Squidex.Domain.Apps.Read.Schemas;
namespace Squidex.Domain.Apps.Read
{
public interface IAppState
public interface IAppProvider
{
Task<IAppEntity> GetAppAsync(Guid appId);
Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id);
Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false);
Task<IAppEntity> GetAppAsync(string appName);
Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool provideDeleted = false);
Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false);
Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId);
Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false);
Task<List<IRuleEntity>> GetRulesAsync(Guid appId);
Task<List<ISchemaEntity>> GetSchemasAsync(string appName);
Task<List<IRuleEntity>> GetRulesAsync(string appName);
Task<List<IAppEntity>> GetUserApps(string userId);
}

16
src/Squidex.Domain.Apps.Read/Schemas/ISchemaEventConsumer.cs

@ -1,16 +0,0 @@
// ==========================================================================
// ISchemaEventConsumer.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Domain.Apps.Read.Schemas
{
public interface ISchemaEventConsumer : IEventConsumer
{
}
}

23
src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs

@ -1,23 +0,0 @@
// ==========================================================================
// ISchemaRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Schemas.Repositories
{
public interface ISchemaRepository
{
Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId);
Task<ISchemaEntity> FindSchemaAsync(Guid appId, string name);
Task<ISchemaEntity> FindSchemaAsync(Guid schemaId);
}
}

20
src/Squidex.Domain.Apps.Read/Schemas/Services/ISchemaProvider.cs

@ -1,20 +0,0 @@
// ==========================================================================
// ISchemaProvider.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Schemas.Services
{
public interface ISchemaProvider
{
Task<ISchemaEntity> FindSchemaByIdAsync(Guid id, bool provideDeleted = false);
Task<ISchemaEntity> FindSchemaByNameAsync(Guid appId, string name);
}
}

131
src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs

@ -1,131 +0,0 @@
// ==========================================================================
// CachingSchemaProvider.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.Schemas.Services.Implementations
{
public class CachingSchemaProvider : CachingProviderBase, ISchemaProvider, IEventConsumer
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);
private readonly ISchemaRepository repository;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return string.Empty; }
}
public CachingSchemaProvider(IMemoryCache cache, ISchemaRepository repository)
: base(cache)
{
Guard.NotNull(repository, nameof(repository));
this.repository = repository;
}
public async Task<ISchemaEntity> FindSchemaByIdAsync(Guid id, bool provideDeleted = false)
{
var cacheKey = BuildIdCacheKey(id);
if (!Cache.TryGetValue(cacheKey, out ISchemaEntity result))
{
result = await repository.FindSchemaAsync(id);
Cache.Set(cacheKey, result, CacheDuration);
if (result != null)
{
Cache.Set(BuildNameCacheKey(result.AppId, result.Name), result, CacheDuration);
}
}
if (result != null && result.IsDeleted && !provideDeleted)
{
result = null;
}
return result;
}
public async Task<ISchemaEntity> FindSchemaByNameAsync(Guid appId, string name)
{
Guard.NotNullOrEmpty(name, nameof(name));
var cacheKey = BuildNameCacheKey(appId, name);
if (!Cache.TryGetValue(cacheKey, out ISchemaEntity result))
{
result = await repository.FindSchemaAsync(appId, name);
Cache.Set(cacheKey, result, CacheDuration);
if (result != null)
{
Cache.Set(BuildIdCacheKey(result.Id), result, CacheDuration);
}
}
if (result != null && result.IsDeleted)
{
result = null;
}
return result;
}
public Task On(Envelope<IEvent> @event)
{
void Remove(NamedId<Guid> appId, NamedId<Guid> schemaId)
{
var cacheKeyById = BuildIdCacheKey(schemaId.Id);
var cacheKeyByName = BuildNameCacheKey(appId.Id, schemaId.Name);
Cache.Remove(cacheKeyById);
Cache.Remove(cacheKeyByName);
Cache.Invalidate(cacheKeyById);
Cache.Invalidate(cacheKeyByName);
}
if (@event.Payload is SchemaEvent schemaEvent)
{
Remove(schemaEvent.AppId, schemaEvent.SchemaId);
}
return TaskHelper.Done;
}
private static string BuildNameCacheKey(Guid appId, string name)
{
return $"Schema_Ids_{appId}_{name}";
}
private static string BuildIdCacheKey(Guid schemaId)
{
return $"Schema_Names_{schemaId}";
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
}
}

10
src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs → src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs

@ -15,7 +15,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.State
namespace Squidex.Domain.Apps.Read.State.Orleans
{
public sealed class AppStateEventConsumer : IEventConsumer
{
@ -47,23 +47,23 @@ namespace Squidex.Domain.Apps.Read.State
{
if (@event.Payload is AppEvent appEvent)
{
var appGrain = factory.GetGrain<IAppStateGrain>(appEvent.AppId.Id);
var appGrain = factory.GetGrain<IAppStateGrain>(appEvent.AppId.Name);
await appGrain.HandleAsync(new EventMessage { Event = @event });
await appGrain.HandleAsync(@event);
}
if (@event.Payload is AppContributorAssigned contributorAssigned)
{
var userGrain = factory.GetGrain<IAppUserGrain>(contributorAssigned.ContributorId);
await userGrain.AddSchemaAsync(contributorAssigned.AppId.Id);
await userGrain.AddAppAsync(contributorAssigned.AppId.Name);
}
if (@event.Payload is AppContributorRemoved contributorRemoved)
{
var userGrain = factory.GetGrain<IAppUserGrain>(contributorRemoved.ContributorId);
await userGrain.RemoveSchemaAsync(contributorRemoved.AppId.Id);
await userGrain.RemoveAppAsync(contributorRemoved.AppId.Name);
}
}
}

18
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs

@ -13,21 +13,25 @@ using Orleans;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Json.Orleans;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{
public interface IAppStateGrain : IGrainWithGuidKey
public interface IAppStateGrain : IGrainWithStringKey
{
Task<IAppEntity> GetAppAsync();
Task<J<(IAppEntity, ISchemaEntity)>> GetAppWithSchemaAsync(Guid id);
Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false);
Task<J<IAppEntity>> GetAppAsync();
Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false);
Task<J<ISchemaEntity>> GetSchemaAsync(Guid id, bool provideDeleted = false);
Task<List<ISchemaEntity>> GetSchemasAsync();
Task<J<ISchemaEntity>> GetSchemaAsync(string name, bool provideDeleted = false);
Task<List<IRuleEntity>> GetRulesAsync();
Task<J<List<ISchemaEntity>>> GetSchemasAsync();
Task HandleAsync(EventMessage message);
Task<J<List<IRuleEntity>>> GetRulesAsync();
Task HandleAsync(J<Envelope<IEvent>> message);
}
}

7
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs

@ -6,7 +6,6 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
@ -15,10 +14,10 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{
public interface IAppUserGrain : IGrainWithStringKey
{
Task<List<Guid>> GetSchemaIdsAsync();
Task<List<string>> GetSchemaNamesAsync();
Task AddSchemaAsync(Guid schemaId);
Task AddAppAsync(string appName);
Task RemoveSchemaAsync(Guid schemaId);
Task RemoveAppAsync(string appName);
}
}

59
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs

@ -8,8 +8,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Orleans;
using Orleans.Providers;
using Squidex.Domain.Apps.Core.Schemas;
@ -17,59 +17,78 @@ using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Json.Orleans;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
[StorageProvider(ProviderName = "Default")]
public sealed class AppStateGrain : Grain<AppStateGrainState>, IAppStateGrain
public sealed class AppStateGrain : Grain<JsonState<AppStateGrainState>>, IAppStateGrain
{
private readonly FieldRegistry fieldRegistry;
private readonly JsonSerializer serializer;
public AppStateGrain(FieldRegistry fieldRegistry)
public AppStateGrain(FieldRegistry fieldRegistry, JsonSerializer serializer)
{
Guard.NotNull(fieldRegistry, nameof(fieldRegistry));
Guard.NotNull(serializer, nameof(serializer));
this.fieldRegistry = fieldRegistry;
this.serializer = serializer;
}
public Task<IAppEntity> GetAppAsync()
public override Task OnActivateAsync()
{
var value = State.App;
State.SetSerializer(serializer);
return Task.FromResult<IAppEntity>(value);
return base.OnActivateAsync();
}
public Task<List<IRuleEntity>> GetRulesAsync()
public Task<J<(IAppEntity, ISchemaEntity)>> GetAppWithSchemaAsync(Guid id)
{
var value = State.Rules.Values.OfType<IRuleEntity>().ToList();
var schema = State.Value.FindSchema(x => x.Id == id && !x.IsDeleted);
return Task.FromResult(value);
return J<(IAppEntity AppEntity, ISchemaEntity SchemaEntity)>.AsTask((State.Value.GetApp(), schema));
}
public Task<List<ISchemaEntity>> GetSchemasAsync()
public Task<J<IAppEntity>> GetAppAsync()
{
var value = State.Schemas.Values.Where(x => !x.IsDeleted).OfType<ISchemaEntity>().ToList();
var value = State.Value.GetApp();
return Task.FromResult(value);
return J<IAppEntity>.AsTask(value);
}
public Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false)
public Task<J<List<IRuleEntity>>> GetRulesAsync()
{
var value = State.Schemas.Values.FirstOrDefault(x => x.Id == id && (!x.IsDeleted || provideDeleted));
var value = State.Value.FindRules();
return Task.FromResult<ISchemaEntity>(value);
return J<List<IRuleEntity>>.AsTask(value);
}
public Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false)
public Task<J<List<ISchemaEntity>>> GetSchemasAsync()
{
var value = State.Schemas.Values.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted));
var value = State.Value.FindSchemas(x => !x.IsDeleted);
return Task.FromResult<ISchemaEntity>(value);
return J<List<ISchemaEntity>>.AsTask(value);
}
public Task HandleAsync(EventMessage message)
public Task<J<ISchemaEntity>> GetSchemaAsync(Guid id, bool provideDeleted = false)
{
State.Apply(message.Event, fieldRegistry);
var value = State.Value.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted));
return J<ISchemaEntity>.AsTask(value);
}
public Task<J<ISchemaEntity>> GetSchemaAsync(string name, bool provideDeleted = false)
{
var value = State.Value.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted));
return J<ISchemaEntity>.AsTask(value);
}
public Task HandleAsync(J<Envelope<IEvent>> message)
{
State = State.Update(v => v.Apply(message, fieldRegistry));
return WriteStateAsync();
}

45
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Schemas;
@ -18,6 +19,9 @@ using Squidex.Domain.Apps.Events.Rules.Utils;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Domain.Apps.Events.Schemas.Old;
using Squidex.Domain.Apps.Events.Schemas.Utils;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Reflection;
@ -37,6 +41,26 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
[JsonProperty]
public Dictionary<Guid, JsonSchemaEntity> Schemas { get; set; }
public IAppEntity GetApp()
{
return App;
}
public ISchemaEntity FindSchema(Func<JsonSchemaEntity, bool> filter)
{
return Schemas.Values.FirstOrDefault(filter);
}
public List<IRuleEntity> FindRules()
{
return Rules.Values.OfType<IRuleEntity>().ToList();
}
public List<ISchemaEntity> FindSchemas(Func<JsonSchemaEntity, bool> filter)
{
return Schemas.Values.Where(filter).OfType<ISchemaEntity>().ToList();
}
public void Reset()
{
Rules = new Dictionary<Guid, JsonRuleEntity>();
@ -54,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
App = EntityMapper.Create<JsonAppEntity>(@event, envelope.Headers, a =>
{
SimpleMapper.Map(envelope, a);
SimpleMapper.Map(@event, a);
a.Clients = new AppClients();
a.Contributors = new AppContributors();
@ -68,7 +92,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
case AppPlanChanged @event:
App.Update(@event, envelope.Headers, a =>
{
SimpleMapper.Map(envelope, a);
SimpleMapper.Map(@event, a);
});
break;
@ -258,19 +282,18 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
break;
case WebhookAdded @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
/* NOOP */
});
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers);
break;
case WebhookDeleted @event:
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s =>
{
/* NOOP */
});
Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers);
break;
}
if (App != null)
{
App.Etag = Guid.NewGuid().ToString();
}
}
}
}
}

20
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs

@ -6,36 +6,34 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Orleans.Providers;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
[StorageProvider(ProviderName = "Default")]
public sealed class AppUserGrain : Grain<HashSet<Guid>>, IAppUserGrain
public sealed class AppUserGrain : Grain<AppUserGrainState>, IAppUserGrain
{
public Task AddSchemaAsync(Guid schemaId)
public Task AddAppAsync(string appName)
{
State.Add(schemaId);
State.AppNames.Add(appName);
return TaskHelper.Done;
return WriteStateAsync();
}
public Task RemoveSchemaAsync(Guid schemaId)
public Task RemoveAppAsync(string appName)
{
State.Remove(schemaId);
State.AppNames.Remove(appName);
return TaskHelper.Done;
return WriteStateAsync();
}
public Task<List<Guid>> GetSchemaIdsAsync()
public Task<List<string>> GetSchemaNamesAsync()
{
return Task.FromResult(State.ToList());
return Task.FromResult(State.AppNames.ToList());
}
}
}

14
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/EventMessage.cs → src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrainState.cs

@ -1,19 +1,19 @@
// ==========================================================================
// EventMessage.cs
// AppUserGrainState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Orleans.Concurrency;
using Squidex.Infrastructure.CQRS.Events;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
{
[Immutable]
public sealed class EventMessage
public sealed class AppUserGrainState
{
public Envelope<IEvent> Event { get; set; }
[JsonProperty]
public HashSet<string> AppNames { get; set; } = new HashSet<string>();
}
}

3
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs

@ -20,6 +20,9 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
[JsonProperty]
public string PlanId { get; set; }
[JsonProperty]
public string Etag { get; set; }
[JsonProperty]
public string PlanOwner { get; set; }

1
src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs

@ -49,6 +49,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations
[JsonProperty]
public Schema SchemaDef { get; set; }
[JsonIgnore]
public bool IsPublished
{
get { return SchemaDef.IsPublished; }

89
src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppProvider.cs

@ -0,0 +1,89 @@
// ==========================================================================
// OrleansApps.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.State.Orleans.Grains;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.State.Orleans
{
public sealed class OrleansAppProvider : IAppProvider
{
private readonly IGrainFactory factory;
public OrleansAppProvider(IGrainFactory factory)
{
Guard.NotNull(factory, nameof(factory));
this.factory = factory;
}
public async Task<IAppEntity> GetAppAsync(string appName)
{
var result = await factory.GetGrain<IAppStateGrain>(appName).GetAppAsync();
return result.Value;
}
public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id)
{
var result = await factory.GetGrain<IAppStateGrain>(appName).GetAppWithSchemaAsync(id);
return result.Value;
}
public async Task<List<IRuleEntity>> GetRulesAsync(string appName)
{
var result = await factory.GetGrain<IAppStateGrain>(appName).GetRulesAsync();
return result.Value;
}
public async Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false)
{
var result = await factory.GetGrain<IAppStateGrain>(appName).GetSchemaAsync(id, provideDeleted);
return result.Value;
}
public async Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false)
{
var result = await factory.GetGrain<IAppStateGrain>(appName).GetSchemaAsync(name, provideDeleted);
return result.Value;
}
public async Task<List<ISchemaEntity>> GetSchemasAsync(string appName)
{
var result = await factory.GetGrain<IAppStateGrain>(appName).GetSchemasAsync();
return result.Value;
}
public async Task<List<IAppEntity>> GetUserApps(string userId)
{
var schemaIds = await factory.GetGrain<IAppUserGrain>(userId).GetSchemaNamesAsync();
var tasks =
schemaIds
.Select(x => factory.GetGrain<IAppStateGrain>(x))
.Select(x => x.GetAppAsync());
var apps = await Task.WhenAll(tasks);
return apps.Select(a => a.Value).Where(a => a != null).ToList();
}
}
}

71
src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs

@ -1,71 +0,0 @@
// ==========================================================================
// OrleansAppState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{
public sealed class OrleansAppState : IAppState
{
private readonly IGrainFactory factory;
public OrleansAppState(IGrainFactory factory)
{
Guard.NotNull(factory, nameof(factory));
this.factory = factory;
}
public Task<IAppEntity> GetAppAsync(Guid appId)
{
return factory.GetGrain<IAppStateGrain>(appId).GetAppAsync();
}
public Task<List<IRuleEntity>> GetRulesAsync(Guid appId)
{
return factory.GetGrain<IAppStateGrain>(appId).GetRulesAsync();
}
public Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false)
{
return factory.GetGrain<IAppStateGrain>(appId).GetSchemaAsync(id, provideDeleted);
}
public Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool provideDeleted = false)
{
return factory.GetGrain<IAppStateGrain>(appId).GetSchemaAsync(name, provideDeleted);
}
public Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId)
{
return factory.GetGrain<IAppStateGrain>(appId).GetSchemasAsync();
}
public async Task<List<IAppEntity>> GetUserApps(string userId)
{
var schemaIds = await factory.GetGrain<IAppUserGrain>(userId).GetSchemaIdsAsync();
var tasks =
schemaIds
.Select(x => factory.GetGrain<IAppStateGrain>(x))
.Select(x => x.GetAppAsync());
var apps = await Task.WhenAll(tasks);
return apps.Where(a => a != null).ToList();
}
}
}

1
src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs

@ -8,6 +8,7 @@
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Domain.Apps.Write.Apps.Guards;

5
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs

@ -9,6 +9,7 @@
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure;
@ -17,13 +18,13 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards
{
public static class GuardApp
{
public static Task CanCreate(CreateApp command, IAppProvider apps)
public static Task CanCreate(CreateApp command, IAppProvider appProvider)
{
Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot create app.", async error =>
{
if (await apps.FindAppByNameAsync(command.Name) != null)
if (await appProvider.GetAppAsync(command.Name) != null)
{
error(new ValidationError($"An app with name '{command.Name}' already exists", nameof(command.Name)));
}

8
src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs

@ -9,10 +9,9 @@
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Domain.Apps.Write.Contents.Guards;
using Squidex.Infrastructure;
@ -27,26 +26,22 @@ namespace Squidex.Domain.Apps.Write.Contents
private readonly IAppProvider appProvider;
private readonly IAssetRepository assetRepository;
private readonly IContentRepository contentRepository;
private readonly ISchemaProvider schemas;
private readonly IScriptEngine scriptEngine;
public ContentCommandMiddleware(
IAggregateHandler handler,
IAppProvider appProvider,
IAssetRepository assetRepository,
ISchemaProvider schemas,
IScriptEngine scriptEngine,
IContentRepository contentRepository)
{
Guard.NotNull(handler, nameof(handler));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(contentRepository, nameof(contentRepository));
this.handler = handler;
this.schemas = schemas;
this.appProvider = appProvider;
this.scriptEngine = scriptEngine;
this.assetRepository = assetRepository;
@ -154,7 +149,6 @@ namespace Squidex.Domain.Apps.Write.Contents
content,
command,
appProvider,
schemas,
scriptEngine,
assetRepository,
message);

13
src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs

@ -13,12 +13,11 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.EnrichContent;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
@ -43,25 +42,21 @@ namespace Squidex.Domain.Apps.Write.Contents
ContentDomainObject content,
ContentCommand command,
IAppProvider appProvider,
ISchemaProvider schemas,
IScriptEngine scriptEngine,
IAssetRepository assetRepository,
Func<string> message)
{
var taskForApp = appProvider.FindAppByIdAsync(command.AppId.Id);
var taskForSchema = schemas.FindSchemaByIdAsync(command.SchemaId.Id);
await Task.WhenAll(taskForApp, taskForSchema);
var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(command.AppId.Name, command.SchemaId.Id);
var context = new ContentOperationContext();
context.appEntity = taskForApp.Result;
context.appEntity = appEntity;
context.assetRepository = assetRepository;
context.contentRepository = contentRepository;
context.content = content;
context.command = command;
context.message = message;
context.schemaEntity = taskForSchema.Result;
context.schemaEntity = schemaEntity;
context.scriptEngine = scriptEngine;
return context;

10
src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs

@ -8,7 +8,7 @@
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Write.Rules.Commands;
using Squidex.Infrastructure;
@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards
{
public static class GuardRule
{
public static Task CanCreate(CreateRule command, ISchemaProvider schemas)
public static Task CanCreate(CreateRule command, IAppProvider appProvider)
{
Guard.NotNull(command, nameof(command));
@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards
}
else
{
var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas);
var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider);
errors.Foreach(error);
}
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards
});
}
public static Task CanUpdate(UpdateRule command, ISchemaProvider schemas)
public static Task CanUpdate(UpdateRule command, IAppProvider appProvider)
{
Guard.NotNull(command, nameof(command));
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards
if (command.Trigger != null)
{
var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas);
var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider);
errors.Foreach(error);
}

18
src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs

@ -6,31 +6,33 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Rules.Guards
{
public sealed class RuleTriggerValidator : IRuleTriggerVisitor<Task<IEnumerable<ValidationError>>>
{
public ISchemaProvider Schemas { get; }
public Func<Guid, Task<ISchemaEntity>> SchemaProvider { get; }
public RuleTriggerValidator(ISchemaProvider schemas)
public RuleTriggerValidator(Func<Guid, Task<ISchemaEntity>> schemaProvider)
{
Schemas = schemas;
SchemaProvider = schemaProvider;
}
public static Task<IEnumerable<ValidationError>> ValidateAsync(RuleTrigger action, ISchemaProvider schemas)
public static Task<IEnumerable<ValidationError>> ValidateAsync(string appName, RuleTrigger action, IAppProvider appProvider)
{
Guard.NotNull(action, nameof(action));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
var visitor = new RuleTriggerValidator(schemas);
var visitor = new RuleTriggerValidator(x => appProvider.GetSchemaAsync(appName, x));
return action.Accept(visitor);
}
@ -41,7 +43,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards
{
var schemaErrors = await Task.WhenAll(
trigger.Schemas.Select(async s =>
await Schemas.FindSchemaByIdAsync(s.SchemaId) == null
await SchemaProvider(s.SchemaId) == null
? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(trigger.Schemas))
: null));

15
src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs

@ -8,7 +8,7 @@
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Write.Rules.Commands;
using Squidex.Domain.Apps.Write.Rules.Guards;
using Squidex.Infrastructure;
@ -20,22 +20,23 @@ namespace Squidex.Domain.Apps.Write.Rules
public class RuleCommandMiddleware : ICommandMiddleware
{
private readonly IAggregateHandler handler;
private readonly ISchemaProvider schemas;
private readonly IAppProvider appProvider;
public RuleCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas)
public RuleCommandMiddleware(IAggregateHandler handler, IAppProvider appProvider)
{
Guard.NotNull(handler, nameof(handler));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
this.handler = handler;
this.schemas = schemas;
this.appProvider = appProvider;
}
protected Task On(CreateRule command, CommandContext context)
{
return handler.CreateAsync<RuleDomainObject>(context, async w =>
{
await GuardRule.CanCreate(command, schemas);
await GuardRule.CanCreate(command, appProvider);
w.Create(command);
});
@ -45,7 +46,7 @@ namespace Squidex.Domain.Apps.Write.Rules
{
return handler.UpdateAsync<RuleDomainObject>(context, async c =>
{
await GuardRule.CanUpdate(command, schemas);
await GuardRule.CanUpdate(command, appProvider);
c.Update(command);
});

6
src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs

@ -10,7 +10,7 @@ using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Write.Schemas.Commands;
using Squidex.Infrastructure;
@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards
{
public static class GuardSchema
{
public static Task CanCreate(CreateSchema command, ISchemaProvider schemas)
public static Task CanCreate(CreateSchema command, IAppProvider appProvider)
{
Guard.NotNull(command, nameof(command));
@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards
error(new ValidationError("Name must be a valid slug.", nameof(command.Name)));
}
if (await schemas.FindSchemaByNameAsync(command.AppId.Id, command.Name) != null)
if (await appProvider.GetSchemaAsync(command.AppId.Name, command.Name) != null)
{
error(new ValidationError($"A schema with name '{command.Name}' already exists", nameof(command.Name)));
}

13
src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs

@ -9,7 +9,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Write.Schemas.Commands;
using Squidex.Domain.Apps.Write.Schemas.Guards;
using Squidex.Infrastructure;
@ -20,23 +20,24 @@ namespace Squidex.Domain.Apps.Write.Schemas
{
public class SchemaCommandMiddleware : ICommandMiddleware
{
private readonly ISchemaProvider schemas;
private readonly IAppProvider appProvider;
private readonly IAggregateHandler handler;
public SchemaCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas)
public SchemaCommandMiddleware(IAggregateHandler handler, IAppProvider appProvider)
{
Guard.NotNull(handler, nameof(handler));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
this.handler = handler;
this.schemas = schemas;
this.appProvider = appProvider;
}
protected Task On(CreateSchema command, CommandContext context)
{
return handler.CreateAsync<SchemaDomainObject>(context, async s =>
{
await GuardSchema.CanCreate(command, schemas);
await GuardSchema.CanCreate(command, appProvider);
s.Create(command);

3
src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerGrain.cs

@ -8,12 +8,13 @@
using System.Threading.Tasks;
using Orleans;
using Orleans.Concurrency;
namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains
{
public interface IEventConsumerGrain : IGrainWithStringKey, IEventSubscriber
{
Task<EventConsumerInfo> GetStateAsync();
Task<Immutable<EventConsumerInfo>> GetStateAsync();
Task ActivateAsync();

3
src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerRegistryGrain.cs

@ -9,6 +9,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
using Orleans.Concurrency;
namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains
{
@ -22,6 +23,6 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains
Task ResetAsync(string consumerName);
Task<List<EventConsumerInfo>> GetConsumersAsync();
Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync();
}
}

5
src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerGrain.cs

@ -9,6 +9,7 @@
using System;
using System.Threading.Tasks;
using Orleans;
using Orleans.Concurrency;
using Orleans.Providers;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks;
@ -170,9 +171,9 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation
return dispatcher.StartNew(() => this.HandleClosedAsync(subscription)).Unwrap();
}
public Task<EventConsumerInfo> GetStateAsync()
public Task<Immutable<EventConsumerInfo>> GetStateAsync()
{
return Task.FromResult(State.ToInfo(this.GetPrimaryKeyString()));
return Task.FromResult(new Immutable<EventConsumerInfo>(State.ToInfo(this.GetPrimaryKeyString())));
}
private Task DoAndUpdateStateAsync(Action action)

5
src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerRegistryGrain.cs

@ -12,6 +12,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Orleans;
using Orleans.Concurrency;
using Orleans.Runtime;
using Squidex.Infrastructure.Tasks;
@ -48,14 +49,14 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation
return Task.WhenAll(tasks);
}
public Task<List<EventConsumerInfo>> GetConsumersAsync()
public Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync()
{
var tasks =
eventConsumers
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name))
.Select(c => c.GetStateAsync());
return Task.WhenAll(tasks).ContinueWith(x => x.Result.ToList());
return Task.WhenAll(tasks).ContinueWith(x => new Immutable<List<EventConsumerInfo>>(x.Result.Select(r => r.Value).ToList()));
}
public Task ReceiveReminder(string reminderName, TickStatus status)

9
src/Squidex.Domain.Apps.Read/Apps/IAppEventConsumer.cs → src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs

@ -1,16 +1,15 @@
// ==========================================================================
// IAppEventConsumer.cs
// IJsonValue.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Domain.Apps.Read.Apps
namespace Squidex.Infrastructure.Json.Orleans
{
public interface IAppEventConsumer : IEventConsumer
public interface IJsonValue
{
object Value { get; }
}
}

49
src/Squidex.Infrastructure/Json/Orleans/J.cs

@ -0,0 +1,49 @@
// ==========================================================================
// J.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Orleans
{
public struct J<T> : IJsonValue
{
private readonly T value;
public T Value
{
get { return value; }
}
object IJsonValue.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 static Task<J<T>> AsTask(T value)
{
return Task.FromResult<J<T>>(value);
}
}
}

78
src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs

@ -0,0 +1,78 @@
// ==========================================================================
// JsonExternalSerializer.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Orleans.Runtime;
using Orleans.Serialization;
namespace Squidex.Infrastructure.Json.Orleans
{
public class JsonExternalSerializer : IExternalSerializer
{
private readonly JsonSerializer jsonSerializer;
public JsonExternalSerializer(JsonSerializer jsonSerializer)
{
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
this.jsonSerializer = jsonSerializer;
}
public void Initialize(Logger logger)
{
}
public bool IsSupportedType(Type itemType)
{
return itemType.GetInterfaces().Contains(typeof(IJsonValue));
}
public object DeepCopy(object source, ICopyContext context)
{
return source;
}
public object Deserialize(Type expectedType, IDeserializationContext context)
{
var outLength = context.StreamReader.ReadInt();
var outBytes = context.StreamReader.ReadBytes(outLength);
var json = Encoding.Default.GetString(outBytes);
var stream = new MemoryStream(outBytes);
using (var reader = new JsonTextReader(new StreamReader(stream)))
{
return jsonSerializer.Deserialize(reader, expectedType);
}
}
public void Serialize(object item, ISerializationContext context, Type expectedType)
{
var stream = new MemoryStream();
using (var writer = new JsonTextWriter(new StreamWriter(stream)))
{
jsonSerializer.Serialize(writer, item);
writer.Flush();
}
var outBytes = stream.ToArray();
var json = Encoding.Default.GetString(outBytes, 0, outBytes.Length);
context.StreamWriter.Write(outBytes.Length);
context.StreamWriter.Write(outBytes);
}
}
}

73
src/Squidex.Infrastructure/Json/Orleans/JsonState.cs

@ -0,0 +1,73 @@
// ==========================================================================
// JsonState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.Json.Orleans
{
public sealed class JsonState<TValue> where TValue : class, new()
{
private JsonSerializer serializer = JsonSerializer.CreateDefault();
private TValue value;
[JsonProperty]
public JToken Json { get; set; }
[JsonIgnore]
public TValue Value
{
get
{
if (value == null)
{
value = ReadState();
}
return value;
}
set
{
this.value = value;
}
}
public void SetSerializer(JsonSerializer serializer)
{
if (serializer != null)
{
this.serializer = serializer;
}
}
public JsonState<TValue> Update(Action<TValue> updater)
{
var value = ReadState();
updater(value);
var json = JObject.FromObject(value, serializer);
return new JsonState<TValue> { serializer = serializer, Json = json, Value = value };
}
private TValue ReadState()
{
if (Json != null)
{
return Json.ToObject<TValue>(serializer);
}
else
{
return new TValue();
}
}
}
}

5
src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs

@ -64,5 +64,10 @@ namespace Squidex.Infrastructure.Reflection
return flattenProperties.ToArray();
}
public static bool Implements<T>(this Type type)
{
return type.GetInterfaces().Contains(typeof(T));
}
}
}

85
src/Squidex.Infrastructure/Reflection/SimpleCopier.cs

@ -0,0 +1,85 @@
// ==========================================================================
// SimpleCopier.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure.Reflection
{
public static class SimpleCopier
{
private struct PropertyMapper
{
private readonly IPropertyAccessor accessor;
private readonly Func<object, object> converter;
public PropertyMapper(IPropertyAccessor accessor, Func<object, object> converter)
{
this.accessor = accessor;
this.converter = converter;
}
public void MapProperty(object source, object target)
{
var value = converter(accessor.Get(source));
accessor.Set(target, value);
}
}
private static class ClassCopier<T> where T : class, new()
{
private static readonly List<PropertyMapper> Mappers = new List<PropertyMapper>();
static ClassCopier()
{
var type = typeof(T);
foreach (var property in type.GetPublicProperties())
{
if (!property.CanWrite || !property.CanRead)
{
continue;
}
var accessor = new PropertyAccessor(type, property);
if (property.PropertyType.Implements<ICloneable>())
{
Mappers.Add(new PropertyMapper(accessor, x => x == null ? x : ((ICloneable)x).Clone()));
}
else
{
Mappers.Add(new PropertyMapper(accessor, x => x));
}
}
}
public static T Copy(T source)
{
var destination = new T();
foreach (var mapper in Mappers)
{
mapper.MapProperty(source, destination);
}
return destination;
}
}
public static T Clone<T>(T source) where T : class, new()
{
Guard.NotNull(source, nameof(source));
return ClassCopier<T>.Copy(source);
}
}
}

114
src/Squidex.Infrastructure/Reflection/SimpleMapper.cs

@ -21,13 +21,16 @@ namespace Squidex.Infrastructure.Reflection
{
private readonly Type targetType;
public ConversionPropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor, Type targetType)
: base(srcAccessor, dstAccessor)
public ConversionPropertyMapper(
IPropertyAccessor sourceAccessor,
IPropertyAccessor targetAccessor,
Type targetType)
: base(sourceAccessor, targetAccessor)
{
this.targetType = targetType;
}
public override void MapProperty(object source, object destination, CultureInfo culture)
public override void MapProperty(object source, object target, CultureInfo culture)
{
var value = GetValue(source);
@ -41,7 +44,7 @@ namespace Squidex.Infrastructure.Reflection
{
converted = Convert.ChangeType(value, targetType, culture);
SetValue(destination, converted);
SetValue(target, converted);
}
catch (InvalidCastException)
{
@ -49,7 +52,7 @@ namespace Squidex.Infrastructure.Reflection
{
converted = value.ToString();
SetValue(destination, converted);
SetValue(target, converted);
}
}
}
@ -57,91 +60,78 @@ namespace Squidex.Infrastructure.Reflection
private class PropertyMapper
{
private readonly IPropertyAccessor srcAccessor;
private readonly IPropertyAccessor dstAccessor;
private readonly IPropertyAccessor sourceAccessor;
private readonly IPropertyAccessor targetAccessor;
public PropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor)
public PropertyMapper(IPropertyAccessor sourceAccessor, IPropertyAccessor targetAccessor)
{
this.srcAccessor = srcAccessor;
this.dstAccessor = dstAccessor;
this.sourceAccessor = sourceAccessor;
this.targetAccessor = targetAccessor;
}
public virtual void MapProperty(object source, object destination, CultureInfo culture)
public virtual void MapProperty(object source, object target, CultureInfo culture)
{
var value = GetValue(source);
SetValue(destination, value);
SetValue(target, value);
}
protected void SetValue(object destination, object value)
{
dstAccessor.Set(destination, value);
targetAccessor.Set(destination, value);
}
protected object GetValue(object source)
{
return srcAccessor.Get(source);
return sourceAccessor.Get(source);
}
}
private static class ClassMapper<TSource, TDestination>
where TSource : class
where TDestination : class
private static class ClassMapper<TSource, TTarget> where TSource : class where TTarget : class
{
private static readonly PropertyMapper[] Mappers;
private static readonly Type[] Convertibles =
{
typeof(bool),
typeof(byte),
typeof(char),
typeof(decimal),
typeof(float),
typeof(double),
typeof(short),
typeof(int),
typeof(long),
typeof(string)
};
private static readonly List<PropertyMapper> Mappers = new List<PropertyMapper>();
static ClassMapper()
{
var dstType = typeof(TDestination);
var srcType = typeof(TSource);
var sourceClassType = typeof(TSource);
var sourceProperties =
sourceClassType.GetPublicProperties()
.Where(x => x.CanRead).ToList();
var destinationProperties = dstType.GetPublicProperties();
var targetClassType = typeof(TTarget);
var targetProperties =
targetClassType.GetPublicProperties()
.Where(x => x.CanWrite).ToList();
var newMappers = new List<PropertyMapper>();
foreach (var srcProperty in srcType.GetPublicProperties().Where(x => x.CanRead))
foreach (var sourceProperty in sourceProperties)
{
var dstProperty = destinationProperties.FirstOrDefault(x => x.Name == srcProperty.Name);
var targetProperty = targetProperties.FirstOrDefault(x => x.Name == sourceProperty.Name);
if (dstProperty == null || !dstProperty.CanWrite)
if (targetProperty == null)
{
continue;
}
var srcPropertyType = srcProperty.PropertyType;
var dstPropertyType = dstProperty.PropertyType;
var sourceType = sourceProperty.PropertyType;
var targetType = targetProperty.PropertyType;
if (srcPropertyType == dstPropertyType)
if (sourceType == targetType)
{
newMappers.Add(new PropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty)));
Mappers.Add(new PropertyMapper(
new PropertyAccessor(sourceClassType, sourceProperty),
new PropertyAccessor(targetClassType, targetProperty)));
}
else
else if (targetType.Implements<IConvertible>() && sourceType.Implements<IConvertible>())
{
if (Convertibles.Contains(dstPropertyType))
{
newMappers.Add(new ConversionPropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty), dstPropertyType));
}
Mappers.Add(new ConversionPropertyMapper(
new PropertyAccessor(sourceClassType, sourceProperty),
new PropertyAccessor(targetClassType, targetProperty),
targetType));
}
}
Mappers = newMappers.ToArray();
}
public static TDestination MapClass(TSource source, TDestination destination, CultureInfo culture)
public static TTarget MapClass(TSource source, TTarget destination, CultureInfo culture)
{
foreach (var mapper in Mappers)
{
@ -152,29 +142,29 @@ namespace Squidex.Infrastructure.Reflection
}
}
public static TDestination Map<TSource, TDestination>(TSource source)
public static TTarget Map<TSource, TTarget>(TSource source)
where TSource : class
where TDestination : class, new()
where TTarget : class, new()
{
return Map(source, new TDestination(), CultureInfo.CurrentCulture);
return Map(source, new TTarget(), CultureInfo.CurrentCulture);
}
public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
public static TTarget Map<TSource, TTarget>(TSource source, TTarget target)
where TSource : class
where TDestination : class
where TTarget : class
{
return Map(source, destination, CultureInfo.CurrentCulture);
return Map(source, target, CultureInfo.CurrentCulture);
}
public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination, CultureInfo culture)
public static TTarget Map<TSource, TTarget>(TSource source, TTarget target, CultureInfo culture)
where TSource : class
where TDestination : class
where TTarget : class
{
Guard.NotNull(source, nameof(source));
Guard.NotNull(culture, nameof(culture));
Guard.NotNull(destination, nameof(destination));
Guard.NotNull(target, nameof(target));
return ClassMapper<TSource, TDestination>.MapClass(source, destination, culture);
return ClassMapper<TSource, TTarget>.MapClass(source, target, culture);
}
}
}

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

@ -13,6 +13,7 @@ using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.ActionHandlers;
using Squidex.Domain.Apps.Core.HandleRules.Triggers;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Apps.Services.Implementations;
@ -23,9 +24,7 @@ using Squidex.Domain.Apps.Read.Contents.GraphQL;
using Squidex.Domain.Apps.Read.History;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations;
using Squidex.Domain.Apps.Read.State;
using Squidex.Domain.Apps.Read.State.Orleans;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
@ -55,15 +54,9 @@ namespace Squidex.Config.Domain
services.AddSingleton<ContentQueryService>()
.As<IContentQueryService>();
services.AddSingleton<CachingAppProvider>()
.As<IAppProvider>();
services.AddSingleton<ConfigAppPlansProvider>()
.As<IAppPlansProvider>();
services.AddSingleton<CachingSchemaProvider>()
.As<ISchemaProvider>();
services.AddSingleton<AssetUserPictureStore>()
.As<IUserPictureStore>();
@ -85,6 +78,9 @@ namespace Squidex.Config.Domain
services.AddSingleton<RuleDequeuer>()
.As<IExternalSystem>();
services.AddSingleton<OrleansAppProvider>()
.As<IAppProvider>();
services.AddSingleton<AppStateEventConsumer>()
.As<IEventConsumer>();
@ -100,19 +96,6 @@ namespace Squidex.Config.Domain
services.AddSingleton(c => new CompoundEventConsumer(c.GetServices<IAssetEventConsumer>().ToArray()))
.As<IEventConsumer>();
services.AddSingleton(c =>
new CompoundEventConsumer(
c.GetServices<IAppEventConsumer>().OfType<IEventConsumer>()
.Concat(c.GetRequiredService<CachingAppProvider>()).ToArray()))
.As<IEventConsumer>();
services.AddSingleton(c =>
new CompoundEventConsumer(
c.GetServices<ISchemaEventConsumer>().OfType<IEventConsumer>()
.Concat(c.GetRequiredService<CachingGraphQLService>())
.Concat(c.GetRequiredService<CachingSchemaProvider>()).ToArray()))
.As<IEventConsumer>();
services.AddSingleton(c =>
{
var allEventConsumers = c.GetServices<IEventConsumer>();

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

@ -11,24 +11,17 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Assets;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.History;
using Squidex.Domain.Apps.Read.History.Repositories;
using Squidex.Domain.Apps.Read.MongoDb.Apps;
using Squidex.Domain.Apps.Read.MongoDb.Assets;
using Squidex.Domain.Apps.Read.MongoDb.Contents;
using Squidex.Domain.Apps.Read.MongoDb.History;
using Squidex.Domain.Apps.Read.MongoDb.Rules;
using Squidex.Domain.Apps.Read.MongoDb.Schemas;
using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Users;
using Squidex.Domain.Users.MongoDb;
using Squidex.Domain.Users.MongoDb.Infrastructure;
@ -74,7 +67,7 @@ namespace Squidex.Config.Domain
.As<IUsageStore>()
.As<IExternalSystem>();
services.AddSingleton(c => new MongoContentRepository(mongoContentDatabase, c.GetService<ISchemaProvider>()))
services.AddSingleton(c => new MongoContentRepository(mongoContentDatabase, c.GetService<IAppProvider>()))
.As<IContentRepository>()
.As<IEventConsumer>();
@ -87,16 +80,6 @@ namespace Squidex.Config.Domain
.As<IEventConsumer>()
.As<IExternalSystem>();
services.AddSingleton(c => new MongoAppRepository(mongoDatabase))
.As<IAppRepository>()
.As<IAppEventConsumer>()
.As<IExternalSystem>();
services.AddSingleton(c => new MongoSchemaRepository(mongoDatabase, c.GetRequiredService<FieldRegistry>()))
.As<ISchemaRepository>()
.As<ISchemaEventConsumer>()
.As<IExternalSystem>();
services.AddSingleton(c => new MongoAssetStatsRepository(mongoDatabase))
.As<IAssetStatsRepository>()
.As<IAssetEventConsumer>()

12
src/Squidex/Config/Identity/LazyClientStore.cs

@ -14,22 +14,22 @@ using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read;
using Squidex.Infrastructure;
namespace Squidex.Config.Identity
{
public class LazyClientStore : IClientStore
{
private readonly IAppProvider appProvider;
private readonly IAppProvider appState;
private readonly Dictionary<string, Client> staticClients = new Dictionary<string, Client>(StringComparer.OrdinalIgnoreCase);
public LazyClientStore(IOptions<MyUrlsOptions> urlsOptions, IAppProvider appProvider)
public LazyClientStore(IOptions<MyUrlsOptions> urlsOptions, IAppProvider appState)
{
Guard.NotNull(urlsOptions, nameof(urlsOptions));
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(appState, nameof(appState));
this.appProvider = appProvider;
this.appState = appState;
CreateStaticClients(urlsOptions);
}
@ -50,7 +50,7 @@ namespace Squidex.Config.Identity
return null;
}
var app = await appProvider.FindAppByNameAsync(token[0]);
var app = await appState.GetAppAsync(token[0]);
var appClient = app?.Clients.GetOrDefault(token[1]);

12
src/Squidex/Config/Orleans/ClientServices.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Orleans;
using Orleans.Runtime.Configuration;
@ -24,8 +25,10 @@ namespace Squidex.Config.Orleans
services.AddSingleton(c =>
{
var configuration = ClientConfiguration.LocalhostSilo();
var client = new ClientBuilder()
.UseConfiguration(ClientConfiguration.LocalhostSilo())
.UseConfiguration(ClientConfiguration.LocalhostSilo().WithJsonSerializer())
.AddApplicationPartsFromReferences(typeof(AppStateGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(EventConsumerGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(XmlRepositoryGrain).Assembly)
@ -36,5 +39,12 @@ namespace Squidex.Config.Orleans
return client;
});
}
public static ClientConfiguration WithJsonSerializer(this ClientConfiguration config)
{
config.SerializationProviders.Add(typeof(CustomJsonSerializer).GetTypeInfo());
return config;
}
}
}

22
src/Squidex/Config/Orleans/CustomJsonSerializer.cs

@ -0,0 +1,22 @@
// ==========================================================================
// CustomJsonSerializer.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Config.Domain;
using Squidex.Infrastructure.Json.Orleans;
namespace Squidex.Config.Orleans
{
public class CustomJsonSerializer : JsonExternalSerializer
{
public CustomJsonSerializer()
: base(JsonSerializer.Create(SerializationServices.DefaultJsonSettings))
{
}
}
}

23
src/Squidex/Config/Orleans/CustomMongoDbStorageProvider.cs

@ -0,0 +1,23 @@
// ==========================================================================
// CustomMongoDbStorageProvider.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using Orleans.Providers;
using Orleans.Providers.MongoDB.StorageProviders;
using Squidex.Config.Domain;
namespace Squidex.Config.Orleans
{
public sealed class CustomMongoDbStorageProvider : MongoStorageProvider
{
protected override JsonSerializerSettings ReturnSerializerSettings(IProviderRuntime providerRuntime, IProviderConfiguration config)
{
return SerializationServices.DefaultJsonSettings;
}
}
}

9
src/Squidex/Config/Orleans/SiloExtensions.cs

@ -6,8 +6,10 @@
// All rights reserved.
// ==========================================================================
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Orleans.Hosting;
using Orleans.Runtime.Configuration;
namespace Squidex.Config.Orleans
{
@ -22,5 +24,12 @@ namespace Squidex.Config.Orleans
return builder;
}
public static ClusterConfiguration WithJsonSerializer(this ClusterConfiguration config)
{
config.Globals.SerializationProviders.Add(typeof(CustomJsonSerializer).GetTypeInfo());
return config;
}
}
}

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

@ -9,26 +9,14 @@
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Orleans;
using Orleans.Providers;
using Orleans.Providers.MongoDB.StorageProviders;
using Orleans.Runtime.Configuration;
using Squidex.Config.Domain;
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains;
namespace Squidex.Config.Orleans
{
public static class SiloServices
{
public sealed class CustomMongoDbStorageProvider : MongoStorageProvider
{
protected override JsonSerializerSettings ReturnSerializerSettings(IProviderRuntime providerRuntime, IProviderConfiguration config)
{
return SerializationServices.DefaultJsonSettings;
}
}
public static void AddAppSiloServices(this IServiceCollection services, IConfiguration config)
{
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration");
@ -54,7 +42,7 @@ namespace Squidex.Config.Orleans
clusterConfiguration.AddMongoDBStorageProvider<CustomMongoDbStorageProvider>("Default", c =>
{
c.ConnectionString = mongoConfiguration;
c.CollectionPrefix = "Orleans_";
c.CollectionPrefix = "States_";
c.DatabaseName = mongoDatabaseName;
c.UseJsonFormat = true;
});

10
src/Squidex/Controllers/Api/Apps/AppsController.cs

@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Controllers.Api.Apps.Models;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure.CQRS.Commands;
@ -31,15 +31,15 @@ namespace Squidex.Controllers.Api.Apps
[SwaggerTag(nameof(Apps))]
public sealed class AppsController : ControllerBase
{
private readonly IAppRepository appRepository;
private readonly IAppProvider appProvider;
private readonly IAppPlansProvider appPlansProvider;
public AppsController(ICommandBus commandBus,
IAppRepository appRepository,
IAppProvider appProvider,
IAppPlansProvider appPlansProvider)
: base(commandBus)
{
this.appRepository = appRepository;
this.appProvider = appProvider;
this.appPlansProvider = appPlansProvider;
}
@ -61,7 +61,7 @@ namespace Squidex.Controllers.Api.Apps
{
var subject = HttpContext.User.OpenIdSubject();
var apps = await appRepository.QueryAllAsync(subject);
var apps = await appProvider.GetUserApps(subject);
var response = apps.Select(a =>
{

4
src/Squidex/Controllers/Api/Assets/AssetsController.cs

@ -102,8 +102,8 @@ namespace Squidex.Controllers.Api.Assets
}
}
var taskForItems = assetRepository.QueryAsync(AppId, mimeTypeList, idsList, query, take, skip);
var taskForCount = assetRepository.CountAsync(AppId, mimeTypeList, idsList, query);
var taskForItems = assetRepository.QueryAsync(App.Id, mimeTypeList, idsList, query, take, skip);
var taskForCount = assetRepository.CountAsync(App.Id, mimeTypeList, idsList, query);
await Task.WhenAll(taskForItems, taskForCount);

2
src/Squidex/Controllers/Api/EventConsumers/EventConsumersController.cs

@ -38,7 +38,7 @@ namespace Squidex.Controllers.Api.EventConsumers
{
var entities = await eventConsumerRegistryGrain.GetConsumersAsync();
var models = entities.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList();
var models = entities.Value.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList();
return Ok(models);
}

14
src/Squidex/Controllers/Api/Schemas/SchemasController.cs

@ -15,8 +15,8 @@ using NSwag.Annotations;
using Squidex.Controllers.Api.Schemas.Models;
using Squidex.Controllers.Api.Schemas.Models.Converters;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Domain.Apps.Write.Schemas.Commands;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
@ -33,12 +33,12 @@ namespace Squidex.Controllers.Api.Schemas
[SwaggerTag(nameof(Schemas))]
public sealed class SchemasController : ControllerBase
{
private readonly ISchemaRepository schemaRepository;
private readonly IAppProvider appProvider;
public SchemasController(ICommandBus commandBus, ISchemaRepository schemaRepository)
public SchemasController(ICommandBus commandBus, IAppProvider appProvider)
: base(commandBus)
{
this.schemaRepository = schemaRepository;
this.appProvider = appProvider;
}
/// <summary>
@ -56,7 +56,7 @@ namespace Squidex.Controllers.Api.Schemas
[ApiCosts(0)]
public async Task<IActionResult> GetSchemas(string app)
{
var schemas = await schemaRepository.QueryAllAsync(AppId);
var schemas = await appProvider.GetSchemasAsync(AppName);
var response = schemas.Select(s => s.ToModel()).ToList();
@ -83,11 +83,11 @@ namespace Squidex.Controllers.Api.Schemas
if (Guid.TryParse(name, out var id))
{
entity = await schemaRepository.FindSchemaAsync(id);
entity = await appProvider.GetSchemaAsync(AppName, id);
}
else
{
entity = await schemaRepository.FindSchemaAsync(AppId, name);
entity = await appProvider.GetSchemaAsync(AppName, name);
}
if (entity == null || entity.IsDeleted)

11
src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs

@ -10,7 +10,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Controllers.ContentApi.Generator;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Domain.Apps.Read;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Pipeline;
@ -21,13 +21,14 @@ namespace Squidex.Controllers.ContentApi
[SwaggerIgnore]
public sealed class ContentSwaggerController : ControllerBase
{
private readonly ISchemaRepository schemaRepository;
private readonly IAppProvider appProvider;
private readonly SchemasSwaggerGenerator schemasSwaggerGenerator;
public ContentSwaggerController(ICommandBus commandBus, ISchemaRepository schemaRepository, SchemasSwaggerGenerator schemasSwaggerGenerator)
public ContentSwaggerController(ICommandBus commandBus, IAppProvider appProvider, SchemasSwaggerGenerator schemasSwaggerGenerator)
: base(commandBus)
{
this.schemaRepository = schemaRepository;
this.appProvider = appProvider;
this.schemasSwaggerGenerator = schemasSwaggerGenerator;
}
@ -46,7 +47,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(0)]
public async Task<IActionResult> GetSwagger(string app)
{
var schemas = await schemaRepository.QueryAllAsync(App.Id);
var schemas = await appProvider.GetSchemasAsync(AppName);
var swaggerDocument = await schemasSwaggerGenerator.Generate(App, schemas);

7
src/Squidex/Controllers/ControllerBase.cs

@ -41,12 +41,9 @@ namespace Squidex.Controllers
}
}
protected Guid AppId
protected string AppName
{
get
{
return App.Id;
}
get { return App.Name; }
}
}
}

8
src/Squidex/Pipeline/AppApiFilter.cs

@ -17,13 +17,13 @@ namespace Squidex.Pipeline
{
public sealed class AppApiFilter : IAsyncActionFilter
{
private readonly IAppProvider appProvider;
private readonly Domain.Apps.Read.IAppProvider appState;
private readonly IAppPlansProvider appPlanProvider;
private readonly IUsageTracker usageTracker;
public AppApiFilter(IAppProvider appProvider, IAppPlansProvider appPlanProvider, IUsageTracker usageTracker)
public AppApiFilter(Domain.Apps.Read.IAppProvider appState, IAppPlansProvider appPlanProvider, IUsageTracker usageTracker)
{
this.appProvider = appProvider;
this.appState = appState;
this.appPlanProvider = appPlanProvider;
this.usageTracker = usageTracker;
@ -35,7 +35,7 @@ namespace Squidex.Pipeline
if (!string.IsNullOrWhiteSpace(appName))
{
var app = await appProvider.FindAppByNameAsync(appName);
var app = await appState.GetAppAsync(appName);
if (app == null)
{

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

@ -1,5 +1,5 @@
// ==========================================================================
// EnrichWithSchemaIdHandler.cs
// EnrichWithSchemaIdCommandMiddleware.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -9,8 +9,8 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Write;
using Squidex.Domain.Apps.Write.Schemas;
using Squidex.Infrastructure;
@ -20,12 +20,12 @@ namespace Squidex.Pipeline.CommandMiddlewares
{
public sealed class EnrichWithSchemaIdCommandMiddleware : ICommandMiddleware
{
private readonly ISchemaProvider schemas;
private readonly IAppProvider appProvider;
private readonly IActionContextAccessor actionContextAccessor;
public EnrichWithSchemaIdCommandMiddleware(ISchemaProvider schemas, IActionContextAccessor actionContextAccessor)
public EnrichWithSchemaIdCommandMiddleware(IAppProvider appProvider, IActionContextAccessor actionContextAccessor)
{
this.schemas = schemas;
this.appProvider = appProvider;
this.actionContextAccessor = actionContextAccessor;
}
@ -44,11 +44,11 @@ namespace Squidex.Pipeline.CommandMiddlewares
if (Guid.TryParse(schemaName, out var id))
{
schema = await schemas.FindSchemaByIdAsync(id);
schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Name, id);
}
else
{
schema = await schemas.FindSchemaByNameAsync(schemaCommand.AppId.Id, schemaName);
schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Name, schemaName);
}
if (schema == null)

2
src/Squidex/Program.cs

@ -27,7 +27,7 @@ namespace Squidex
.AddApplicationPartsFromReferences(typeof(AppStateGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(EventConsumerGrain).Assembly)
.AddApplicationPartsFromReferences(typeof(XmlRepositoryGrain).Assembly)
.UseConfiguration(ClusterConfiguration.LocalhostPrimarySilo(33333))
.UseConfiguration(ClusterConfiguration.LocalhostPrimarySilo(33333).WithJsonSerializer())
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices((context, services) =>
{

131
tests/Squidex.Domain.Apps.Read.Tests/Apps/CachingAppProviderTests.cs

@ -1,131 +0,0 @@
// ==========================================================================
// CachingAppProviderTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Domain.Apps.Read.Apps.Services.Implementations;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Xunit;
namespace Squidex.Domain.Apps.Read.Apps
{
public class CachingAppProviderTests
{
private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private readonly IAppRepository repository = A.Fake<IAppRepository>();
private readonly CachingAppProvider sut;
private readonly IAppEntity appV1 = A.Dummy<IAppEntity>();
private readonly IAppEntity appV2 = A.Dummy<IAppEntity>();
private readonly NamedId<Guid> appId = new NamedId<Guid>(Guid.NewGuid(), "my-app");
public CachingAppProviderTests()
{
A.CallTo(() => appV1.Id).Returns(appId.Id);
A.CallTo(() => appV1.Name).Returns(appId.Name);
A.CallTo(() => appV2.Id).Returns(appId.Id);
A.CallTo(() => appV2.Name).Returns(appId.Name);
sut = new CachingAppProvider(cache, repository);
}
[Fact]
public void Should_return_empty_for_events_filter()
{
Assert.Equal(string.Empty, sut.EventsFilter);
}
[Fact]
public void Should_return_empty_for_name()
{
Assert.Equal(typeof(CachingAppProvider).Name, sut.Name);
}
[Fact]
public void Should_do_nothing_when_clearing()
{
Assert.NotNull(sut.ClearAsync());
}
[Fact]
public async Task Should_also_retrieve_app_by_name_if_retrieved_by_id_before()
{
A.CallTo(() => repository.FindAppAsync(appId.Id))
.Returns(appV1);
await ProvideAppByIdAsync(appV1);
await ProvideAppByNameAsync(appV1);
A.CallTo(() => repository.FindAppAsync(appId.Id)).MustHaveHappened();
A.CallTo(() => repository.FindAppAsync(appId.Name)).MustNotHaveHappened();
}
[Fact]
public async Task Should_also_retrieve_app_by_id_if_retrieved_by_name_before()
{
A.CallTo(() => repository.FindAppAsync(appId.Name))
.Returns(appV1);
await ProvideAppByNameAsync(appV1);
await ProvideAppByIdAsync(appV1);
A.CallTo(() => repository.FindAppAsync(appId.Id)).MustNotHaveHappened();
A.CallTo(() => repository.FindAppAsync(appId.Name)).MustHaveHappened();
}
[Fact]
public async Task Should_clear_cache_for_id_after_update_event()
{
A.CallTo(() => repository.FindAppAsync(appId.Id))
.Returns(appV2);
A.CallTo(() => repository.FindAppAsync(appId.Id))
.Returns(appV1).Once();
await ProvideAppByIdAsync(appV1);
sut.On(Envelope.Create(new AppLanguageAdded { AppId = appId }).To<IEvent>()).Wait();
await ProvideAppByIdAsync(appV2);
A.CallTo(() => repository.FindAppAsync(appId.Id)).MustHaveHappened(Repeated.Exactly.Times(2));
}
[Fact]
public async Task Should_clear_cache_for_name_after_update_event()
{
A.CallTo(() => repository.FindAppAsync(appId.Name))
.Returns(appV2);
A.CallTo(() => repository.FindAppAsync(appId.Name))
.Returns(appV1).Once();
await ProvideAppByNameAsync(appV1);
sut.On(Envelope.Create(new AppLanguageAdded { AppId = appId }).To<IEvent>()).Wait();
await ProvideAppByNameAsync(appV2);
A.CallTo(() => repository.FindAppAsync(appId.Name)).MustHaveHappened(Repeated.Exactly.Times(2));
}
private async Task ProvideAppByIdAsync(IAppEntity app)
{
Assert.Equal(app, await sut.FindAppByIdAsync(appId.Id));
}
private async Task ProvideAppByNameAsync(IAppEntity app)
{
Assert.Equal(app, await sut.FindAppByNameAsync(appId.Name));
}
}
}

3
tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs

@ -17,6 +17,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime.Extensions;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Apps;
@ -77,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.Contents
new GeolocationFieldProperties()));
A.CallTo(() => app.Id).Returns(appId);
A.CallTo(() => app.PartitionResolver).Returns(x => InvariantPartitioning.Instance);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE));
A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.Name).Returns(schemaDef.Name);

2
tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Read.Contents
var app = A.Dummy<IAppEntity>();
A.CallTo(() => app.Id).Returns(Guid.NewGuid());
A.CallTo(() => app.Version).Returns(3);
A.CallTo(() => app.PartitionResolver).Returns(languagesConfig.ToResolver());
A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig);
edmModel = builder.BuildEdmModel(schema, app);
}

3
tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs

@ -9,6 +9,7 @@
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Apps.Services.Implementations;
@ -37,7 +38,7 @@ namespace Squidex.Domain.Apps.Write.Apps
{
app = new AppDomainObject(AppId, -1);
A.CallTo(() => appProvider.FindAppByNameAsync(AppName))
A.CallTo(() => appProvider.GetAppAsync(AppName))
.Returns((IAppEntity)null);
A.CallTo(() => userResolver.FindByIdAsync(contributorId))

5
tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs

@ -9,6 +9,7 @@
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
@ -26,7 +27,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards
public GuardAppTests()
{
A.CallTo(() => apps.FindAppByNameAsync("new-app"))
A.CallTo(() => apps.GetAppAsync("new-app"))
.Returns(Task.FromResult<IAppEntity>(null));
A.CallTo(() => users.FindByIdAsync(A<string>.Ignored))
@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards
[Fact]
public Task CanCreate_should_throw_exception_if_name_already_in_use()
{
A.CallTo(() => apps.FindAppByNameAsync("new-app"))
A.CallTo(() => apps.GetAppAsync("new-app"))
.Returns(A.Fake<IAppEntity>());
var command = new CreateApp { Name = "new-app" };

5
tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs

@ -15,8 +15,8 @@ using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Read;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
@ -73,9 +73,8 @@ namespace Squidex.Domain.Apps.Write.Contents
sut = new ContentCommandMiddleware(Handler, appProvider, A.Dummy<IAssetRepository>(), schemas, scriptEngine, A.Dummy<IContentRepository>());
A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig);
A.CallTo(() => app.PartitionResolver).Returns(languagesConfig.ToResolver());
A.CallTo(() => appProvider.FindAppByIdAsync(AppId)).Returns(app);
A.CallTo(() => appProvider.GetAppAsync(AppName)).Returns(app);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
A.CallTo(() => schema.ScriptCreate).Returns("<create-script>");

Loading…
Cancel
Save