Browse Source

Merge branch 'master' into feature-asset-management

pull/65/head
Sebastian 9 years ago
parent
commit
8800b0ad56
  1. 2
      src/Squidex.Infrastructure/CQRS/DomainObjectBase.cs
  2. 53
      src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs
  3. 43
      src/Squidex.Infrastructure/CQRS/Events/Envelope.cs
  4. 65
      src/Squidex.Infrastructure/CQRS/Events/Envelope_1.cs
  5. 2
      src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs
  6. 2
      src/Squidex.Infrastructure/CQRS/Events/IEventConsumer.cs
  7. 3
      src/Squidex.Infrastructure/Timers/CompletionTimer.cs
  8. 46
      src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  9. 9
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  10. 5
      src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs
  11. 25
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
  12. 3
      src/Squidex.Read/Apps/Repositories/IAppRepository.cs
  13. 3
      src/Squidex.Read/Apps/Services/IAppProvider.cs
  14. 36
      src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs
  15. 3
      src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs
  16. 3
      src/Squidex.Read/Schemas/Services/ISchemaProvider.cs
  17. 35
      src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
  18. 2
      src/Squidex.Write/Apps/AppDomainObject.cs
  19. 2
      src/Squidex/Config/Domain/ReadModule.cs
  20. 25
      src/Squidex/Config/Domain/StoreMongoDbModule.cs
  21. 18
      src/Squidex/Config/Domain/Usages.cs
  22. 5
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  23. 3
      src/Squidex/app/features/content/pages/contents/content-item.component.ts
  24. 3
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  25. 5
      src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.ts
  26. 5
      src/Squidex/app/features/schemas/pages/schema/types/boolean-validation.component.ts
  27. 5
      src/Squidex/app/features/schemas/pages/schema/types/date-time-ui.component.ts
  28. 5
      src/Squidex/app/features/schemas/pages/schema/types/date-time-validation.component.ts
  29. 5
      src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.ts
  30. 5
      src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.ts
  31. 5
      src/Squidex/app/features/schemas/pages/schema/types/json-ui.component.ts
  32. 5
      src/Squidex/app/features/schemas/pages/schema/types/json-validation.component.ts
  33. 5
      src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts
  34. 5
      src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts
  35. 5
      src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts
  36. 5
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  37. 71
      tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs
  38. 1
      tests/Squidex.Infrastructure.Tests/CQRS/Events/EventReceiverTests.cs
  39. 6
      tests/Squidex.Read.Tests/Apps/CachingAppProviderTests.cs
  40. 6
      tests/Squidex.Read.Tests/Schemas/CachingSchemaProviderTests.cs

2
src/Squidex.Infrastructure/CQRS/DomainObjectBase.cs

@ -47,7 +47,7 @@ namespace Squidex.Infrastructure.CQRS
protected void RaiseEvent(IEvent @event)
{
RaiseEvent(Envelope<IEvent>.Create(@event));
RaiseEvent(Envelope.Create(@event));
}
protected void RaiseEvent<TEvent>(Envelope<TEvent> @event) where TEvent : class, IEvent

53
src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs

@ -0,0 +1,53 @@
// ==========================================================================
// CompoundEventConsumer.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class CompoundEventConsumer : IEventConsumer
{
private readonly IEventConsumer[] inners;
public string Name { get; }
public CompoundEventConsumer(IEventConsumer first, params IEventConsumer[] inners)
{
Guard.NotNull(first, nameof(first));
Guard.NotNull(inners, nameof(inners));
this.inners = new[] { first }.Union(inners).ToArray();
Name = first.GetType().Name;
}
public CompoundEventConsumer(string name, params IEventConsumer[] inners)
{
Guard.NotNull(inners, nameof(inners));
Guard.NotNullOrEmpty(name, nameof(name));
this.inners = inners;
Name = name;
}
public Task ClearAsync()
{
return Task.WhenAll(inners.Select(i => i.ClearAsync()));
}
public async Task On(Envelope<IEvent> @event)
{
foreach (var inner in inners)
{
await inner.On(@event);
}
}
}
}

43
src/Squidex.Infrastructure/CQRS/Events/Envelope.cs

@ -11,55 +11,18 @@ using NodaTime;
namespace Squidex.Infrastructure.CQRS.Events
{
public class Envelope<TPayload> where TPayload : class
public static class Envelope
{
private readonly EnvelopeHeaders headers;
private readonly TPayload payload;
public EnvelopeHeaders Headers
{
get { return headers; }
}
public TPayload Payload
{
get { return payload; }
}
public Envelope(TPayload payload)
: this(payload, new EnvelopeHeaders())
{
}
public Envelope(TPayload payload, PropertiesBag bag)
: this(payload, new EnvelopeHeaders(bag))
{
}
public Envelope(TPayload payload, EnvelopeHeaders headers)
{
Guard.NotNull(payload, nameof(payload));
Guard.NotNull(headers, nameof(headers));
this.payload = payload;
this.headers = headers;
}
public static Envelope<TPayload> Create(TPayload payload)
public static Envelope<IEvent> Create<TPayload>(TPayload payload) where TPayload : IEvent
{
var eventId = Guid.NewGuid();
var envelope =
new Envelope<TPayload>(payload)
new Envelope<IEvent>(payload)
.SetEventId(eventId)
.SetTimestamp(SystemClock.Instance.GetCurrentInstant());
return envelope;
}
public Envelope<TOther> To<TOther>() where TOther : class
{
return new Envelope<TOther>(payload as TOther, headers.Clone());
}
}
}

65
src/Squidex.Infrastructure/CQRS/Events/Envelope_1.cs

@ -0,0 +1,65 @@
// ==========================================================================
// Envelope.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using NodaTime;
namespace Squidex.Infrastructure.CQRS.Events
{
public class Envelope<TPayload> where TPayload : class
{
private readonly EnvelopeHeaders headers;
private readonly TPayload payload;
public EnvelopeHeaders Headers
{
get { return headers; }
}
public TPayload Payload
{
get { return payload; }
}
public Envelope(TPayload payload)
: this(payload, new EnvelopeHeaders())
{
}
public Envelope(TPayload payload, PropertiesBag bag)
: this(payload, new EnvelopeHeaders(bag))
{
}
public Envelope(TPayload payload, EnvelopeHeaders headers)
{
Guard.NotNull(payload, nameof(payload));
Guard.NotNull(headers, nameof(headers));
this.payload = payload;
this.headers = headers;
}
public static Envelope<TPayload> Create(TPayload payload)
{
var eventId = Guid.NewGuid();
var envelope =
new Envelope<TPayload>(payload)
.SetEventId(eventId)
.SetTimestamp(SystemClock.Instance.GetCurrentInstant());
return envelope;
}
public Envelope<TOther> To<TOther>() where TOther : class
{
return new Envelope<TOther>(payload as TOther, headers.Clone());
}
}
}

2
src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs

@ -76,7 +76,7 @@ namespace Squidex.Infrastructure.CQRS.Events
return;
}
var consumerName = eventConsumer.GetType().Name;
var consumerName = eventConsumer.Name;
var consumerStarted = false;
timer = new CompletionTimer(delay, async ct =>

2
src/Squidex.Infrastructure/CQRS/Events/IEventConsumer.cs

@ -12,6 +12,8 @@ namespace Squidex.Infrastructure.CQRS.Events
{
public interface IEventConsumer
{
string Name { get; }
Task ClearAsync();
Task On(Envelope<IEvent> @event);

3
src/Squidex.Infrastructure/Timers/CompletionTimer.cs

@ -7,6 +7,7 @@
// ==========================================================================
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
@ -45,7 +46,7 @@ namespace Squidex.Infrastructure.Timers
}
catch (TaskCanceledException)
{
Console.WriteLine("Task in TriggerTimer has been cancelled.");
Debug.WriteLine("Task in TriggerTimer has been cancelled.");
}
}
}

46
src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs

@ -6,9 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Events;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
@ -20,36 +18,27 @@ namespace Squidex.Read.MongoDb.Apps
{
public partial class MongoAppRepository
{
public event Action<NamedId<Guid>> AppSaved;
public string Name
{
get { return GetType().Name; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected async Task On(AppCreated @event, EnvelopeHeaders headers)
protected Task On(AppCreated @event, EnvelopeHeaders headers)
{
await Collection.CreateAsync(@event, headers, a =>
return Collection.CreateAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
});
AppSaved?.Invoke(@event.AppId);
}
protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
{
var contributor = a.Contributors.GetOrAddNew(@event.ContributorId);
SimpleMapper.Map(@event, contributor);
});
}
protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
return Collection.UpdateAsync(@event, headers, a =>
{
a.Contributors.Remove(@event.ContributorId);
});
@ -57,7 +46,7 @@ namespace Squidex.Read.MongoDb.Apps
protected Task On(AppClientAttached @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
return Collection.UpdateAsync(@event, headers, a =>
{
a.Clients[@event.Id] = SimpleMapper.Map(@event, new MongoAppClientEntity());
});
@ -65,7 +54,7 @@ namespace Squidex.Read.MongoDb.Apps
protected Task On(AppClientRevoked @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
return Collection.UpdateAsync(@event, headers, a =>
{
a.Clients.Remove(@event.Id);
});
@ -73,7 +62,7 @@ namespace Squidex.Read.MongoDb.Apps
protected Task On(AppClientRenamed @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
return Collection.UpdateAsync(@event, headers, a =>
{
a.Clients[@event.Id].Name = @event.Name;
});
@ -81,7 +70,7 @@ namespace Squidex.Read.MongoDb.Apps
protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
return Collection.UpdateAsync(@event, headers, a =>
{
a.Languages.Add(@event.Language.Iso2Code);
});
@ -89,7 +78,7 @@ namespace Squidex.Read.MongoDb.Apps
protected Task On(AppLanguageRemoved @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
return Collection.UpdateAsync(@event, headers, a =>
{
a.Languages.Remove(@event.Language.Iso2Code);
});
@ -97,17 +86,20 @@ namespace Squidex.Read.MongoDb.Apps
protected Task On(AppMasterLanguageSet @event, EnvelopeHeaders headers)
{
return UpdateAsync(@event, headers, a =>
return Collection.UpdateAsync(@event, headers, a =>
{
a.MasterLanguage = @event.Language.Iso2Code;
});
}
public async Task UpdateAsync(AppEvent @event, EnvelopeHeaders headers, Action<MongoAppEntity> updater)
protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers)
{
await Collection.UpdateAsync(@event, headers, updater);
return Collection.UpdateAsync(@event, headers, a =>
{
var contributor = a.Contributors.GetOrAddNew(@event.ContributorId);
AppSaved?.Invoke(@event.AppId);
SimpleMapper.Map(@event, contributor);
});
}
}
}

9
src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -32,6 +32,11 @@ namespace Squidex.Read.MongoDb.Contents
}
}
public string Name
{
get { return GetType().Name; }
}
public async Task ClearAsync()
{
using (var collections = await database.ListCollectionsAsync())
@ -127,11 +132,11 @@ namespace Squidex.Read.MongoDb.Contents
});
}
private async Task ForSchemaIdAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Task> action)
private Task ForSchemaIdAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Task> action)
{
var collection = GetCollection(schemaId);
await action(collection);
return action(collection);
}
private IMongoCollection<MongoContentEntity> GetCollection(Guid schemaId)

5
src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs

@ -67,6 +67,11 @@ namespace Squidex.Read.MongoDb.History
return entities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList();
}
public string Name
{
get { return GetType().Name; }
}
public async Task On(Envelope<IEvent> @event)
{
foreach (var creator in creators)

25
src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs

@ -8,12 +8,10 @@
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Core.Schemas;
using Squidex.Events;
using Squidex.Events.Schemas;
using Squidex.Events.Schemas.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
@ -23,20 +21,21 @@ namespace Squidex.Read.MongoDb.Schemas
{
public partial class MongoSchemaRepository
{
public event Action<NamedId<Guid>, NamedId<Guid>> SchemaSaved;
public string Name
{
get { return GetType().Name; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected async Task On(SchemaCreated @event, EnvelopeHeaders headers)
protected Task On(SchemaCreated @event, EnvelopeHeaders headers)
{
var schema = SchemaEventDispatcher.Dispatch(@event);
await Collection.CreateAsync(@event, headers, s => { UpdateSchema(s, schema); SimpleMapper.Map(@event, s); });
SchemaSaved?.Invoke(@event.AppId, @event.SchemaId);
return Collection.CreateAsync(@event, headers, s => { UpdateSchema(s, schema); SimpleMapper.Map(@event, s); });
}
protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
@ -89,18 +88,14 @@ namespace Squidex.Read.MongoDb.Schemas
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s, registry));
}
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers)
protected Task On(SchemaDeleted @event, EnvelopeHeaders headers)
{
await Collection.UpdateAsync(@event, headers, e => e.IsDeleted = true);
SchemaSaved?.Invoke(@event.AppId, @event.SchemaId);
return Collection.UpdateAsync(@event, headers, e => e.IsDeleted = true);
}
private async Task UpdateSchema(SchemaEvent @event, EnvelopeHeaders headers, Func<Schema, Schema> updater)
private Task UpdateSchema(SquidexEvent @event, EnvelopeHeaders headers, Func<Schema, Schema> updater)
{
await Collection.UpdateAsync(@event, headers, e => UpdateSchema(e, updater));
SchemaSaved?.Invoke(@event.AppId, @event.SchemaId);
return Collection.UpdateAsync(@event, headers, e => UpdateSchema(e, updater));
}
private void UpdateSchema(MongoSchemaEntity entity, Func<Schema, Schema> updater)

3
src/Squidex.Read/Apps/Repositories/IAppRepository.cs

@ -9,14 +9,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Read.Apps.Repositories
{
public interface IAppRepository
{
event Action<NamedId<Guid>> AppSaved;
Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId);
Task<IAppEntity> FindAppAsync(Guid appId);

3
src/Squidex.Read/Apps/Services/IAppProvider.cs

@ -8,7 +8,6 @@
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Read.Apps.Services
{
@ -17,7 +16,5 @@ namespace Squidex.Read.Apps.Services
Task<IAppEntity> FindAppByIdAsync(Guid id);
Task<IAppEntity> FindAppByNameAsync(string name);
void Remove(NamedId<Guid> id);
}
}

36
src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs

@ -9,8 +9,12 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Events;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Utils;
@ -18,11 +22,16 @@ using Squidex.Read.Utils;
namespace Squidex.Read.Apps.Services.Implementations
{
public class CachingAppProvider : CachingProviderBase, IAppProvider
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 CachingAppProvider(IMemoryCache cache, IAppRepository repository)
: base(cache)
{
@ -71,7 +80,9 @@ namespace Squidex.Read.Apps.Services.Implementations
return result;
}
public void Remove(NamedId<Guid> id)
public Task On(Envelope<IEvent> @event)
{
void Remove(NamedId<Guid> id)
{
var cacheKeyById = BuildIdCacheKey(id.Id);
var cacheKeyByName = BuildNameCacheKey(id.Name);
@ -83,6 +94,22 @@ namespace Squidex.Read.Apps.Services.Implementations
Cache.Invalidate(cacheKeyByName);
}
if (@event.Payload is AppClientAttached ||
@event.Payload is AppClientRenamed ||
@event.Payload is AppClientRevoked ||
@event.Payload is AppContributorAssigned ||
@event.Payload is AppContributorRemoved ||
@event.Payload is AppCreated ||
@event.Payload is AppLanguageAdded ||
@event.Payload is AppLanguageRemoved ||
@event.Payload is AppMasterLanguageSet)
{
Remove(((AppEvent)@event.Payload).AppId);
}
return TaskHelper.Done;
}
private static string BuildNameCacheKey(string name)
{
return $"App_Ids_{name}";
@ -92,5 +119,10 @@ namespace Squidex.Read.Apps.Services.Implementations
{
return $"App_Names_{schemaId}";
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
}
}

3
src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs

@ -9,14 +9,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Read.Schemas.Repositories
{
public interface ISchemaRepository
{
event Action<NamedId<Guid>, NamedId<Guid>> SchemaSaved;
Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId);
Task<IReadOnlyList<ISchemaEntityWithSchema>> QueryAllWithSchemaAsync(Guid appId);

3
src/Squidex.Read/Schemas/Services/ISchemaProvider.cs

@ -8,7 +8,6 @@
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Read.Schemas.Services
{
@ -17,7 +16,5 @@ namespace Squidex.Read.Schemas.Services
Task<ISchemaEntityWithSchema> FindSchemaByIdAsync(Guid id, bool provideDeleted = false);
Task<ISchemaEntityWithSchema> FindSchemaByNameAsync(Guid appId, string name);
void Remove(NamedId<Guid> appId, NamedId<Guid> schemaId);
}
}

35
src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs

@ -9,8 +9,11 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Utils;
@ -19,11 +22,16 @@ using Squidex.Read.Utils;
namespace Squidex.Read.Schemas.Services.Implementations
{
public class CachingSchemaProvider : CachingProviderBase, ISchemaProvider
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 CachingSchemaProvider(IMemoryCache cache, ISchemaRepository repository)
: base(cache)
{
@ -82,7 +90,9 @@ namespace Squidex.Read.Schemas.Services.Implementations
return result;
}
public void Remove(NamedId<Guid> appId, NamedId<Guid> schemaId)
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);
@ -94,6 +104,22 @@ namespace Squidex.Read.Schemas.Services.Implementations
Cache.Invalidate(cacheKeyByName);
}
if (@event.Payload is FieldEvent fieldEvent)
{
Remove(fieldEvent.AppId, fieldEvent.SchemaId);
}
else if (@event.Payload is SchemaDeleted schemaDeletedEvent)
{
Remove(schemaDeletedEvent.AppId, schemaDeletedEvent.SchemaId);
}
else if (@event.Payload is SchemaUpdated schemaUpdatedEvent)
{
Remove(schemaUpdatedEvent.AppId, schemaUpdatedEvent.SchemaId);
}
return TaskHelper.Done;
}
private static string BuildNameCacheKey(Guid appId, string name)
{
return $"Schema_Ids_{appId}_{name}";
@ -103,5 +129,10 @@ namespace Squidex.Read.Schemas.Services.Implementations
{
return $"Schema_Names_{schemaId}";
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
}
}

2
src/Squidex.Write/Apps/AppDomainObject.cs

@ -207,7 +207,7 @@ namespace Squidex.Write.Apps
@event.AppId = new NamedId<Guid>(Id, name);
}
base.RaiseEvent(Envelope<IEvent>.Create(@event));
RaiseEvent(Envelope.Create(@event));
}
private static AppLanguageAdded CreateInitialLanguage(NamedId<Guid> id)

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

@ -35,10 +35,12 @@ namespace Squidex.Config.Domain
{
builder.RegisterType<CachingAppProvider>()
.As<IAppProvider>()
.AsSelf()
.SingleInstance();
builder.RegisterType<CachingSchemaProvider>()
.As<ISchemaProvider>()
.AsSelf()
.SingleInstance();
builder.RegisterType<AppHistoryEventsCreator>()

25
src/Squidex/Config/Domain/StoreMongoDbModule.cs

@ -17,6 +17,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Apps.Services.Implementations;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.History.Repositories;
using Squidex.Read.MongoDb.Apps;
@ -26,6 +27,7 @@ using Squidex.Read.MongoDb.Infrastructure;
using Squidex.Read.MongoDb.Schemas;
using Squidex.Read.MongoDb.Users;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Schemas.Services.Implementations;
using Squidex.Read.Users.Repositories;
namespace Squidex.Config.Domain
@ -85,7 +87,8 @@ namespace Squidex.Config.Domain
IndexChecks.EnsureUniqueIndexOnNormalizedUserName(usersCollection);
return new UserStore<IdentityUser>(usersCollection);
}).SingleInstance();
})
.SingleInstance();
builder.Register<IRoleStore<IdentityRole>>(c =>
{
@ -94,7 +97,8 @@ namespace Squidex.Config.Domain
IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(rolesCollection);
return new RoleStore<IdentityRole>(rolesCollection);
}).SingleInstance();
})
.SingleInstance();
builder.RegisterType<MongoUserRepository>()
.As<IUserRepository>()
@ -129,7 +133,6 @@ namespace Squidex.Config.Domain
builder.RegisterType<MongoSchemaRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseName))
.As<ISchemaRepository>()
.As<IEventConsumer>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
@ -141,6 +144,22 @@ namespace Squidex.Config.Domain
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.Register(c =>
new CompoundEventConsumer(
c.Resolve<MongoSchemaRepository>(),
c.Resolve<CachingSchemaProvider>()))
.As<IEventConsumer>()
.AsSelf()
.SingleInstance();
builder.Register(c =>
new CompoundEventConsumer(
c.Resolve<MongoAppRepository>(),
c.Resolve<CachingAppProvider>()))
.As<IEventConsumer>()
.AsSelf()
.SingleInstance();
}
}
}

18
src/Squidex/Config/Domain/Usages.cs

@ -11,10 +11,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Apps.Services;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Schemas.Services;
namespace Squidex.Config.Domain
{
@ -31,20 +27,6 @@ namespace Squidex.Config.Domain
receiver?.Subscribe(catchConsumer);
}
var appProvider = app.ApplicationServices.GetRequiredService<IAppProvider>();
app.ApplicationServices.GetRequiredService<IAppRepository>().AppSaved += appId =>
{
appProvider.Remove(appId);
};
var schemaProvider = app.ApplicationServices.GetRequiredService<ISchemaProvider>();
app.ApplicationServices.GetRequiredService<ISchemaRepository>().SchemaSaved += (appId, schemaId) =>
{
schemaProvider.Remove(appId, schemaId);
};
return app;
}

5
src/Squidex/app/features/content/pages/content/content-field.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { AppLanguageDto, FieldDto } from 'shared';
@ -13,7 +13,8 @@ import { AppLanguageDto, FieldDto } from 'shared';
@Component({
selector: 'sqx-content-field',
styleUrls: ['./content-field.component.scss'],
templateUrl: './content-field.component.html'
templateUrl: './content-field.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentFieldComponent implements OnInit {
@Input()

3
src/Squidex/app/features/content/pages/contents/content-item.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import {
AppComponentBase,
@ -24,6 +24,7 @@ import {
selector: '[sqxContent]',
styleUrls: ['./content-item.component.scss'],
templateUrl: './content-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
fadeAnimation
]

3
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
@ -19,6 +19,7 @@ import {
selector: 'sqx-field',
styleUrls: ['./field.component.scss'],
templateUrl: './field.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
fadeAnimation
]

5
src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { BooleanFieldPropertiesDto } from 'shared';
@ -13,7 +13,8 @@ import { BooleanFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-boolean-ui',
styleUrls: ['boolean-ui.component.scss'],
templateUrl: 'boolean-ui.component.html'
templateUrl: 'boolean-ui.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BooleanUIComponent implements OnInit {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/boolean-validation.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
@ -14,7 +14,8 @@ import { BooleanFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-boolean-validation',
styleUrls: ['boolean-validation.component.scss'],
templateUrl: 'boolean-validation.component.html'
templateUrl: 'boolean-validation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BooleanValidationComponent implements OnInit {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/date-time-ui.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
@ -14,7 +14,8 @@ import { FloatConverter, NumberFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-date-time-ui',
styleUrls: ['date-time-ui.component.scss'],
templateUrl: 'date-time-ui.component.html'
templateUrl: 'date-time-ui.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DateTimeUIComponent implements OnInit {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/date-time-validation.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
@ -14,7 +14,8 @@ import { NumberFieldPropertiesDto, ValidatorsEx } from 'shared';
@Component({
selector: 'sqx-date-time-validation',
styleUrls: ['date-time-validation.component.scss'],
templateUrl: 'date-time-validation.component.html'
templateUrl: 'date-time-validation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DateTimeValidationComponent implements OnInit {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { GeolocationFieldPropertiesDto } from 'shared';
@ -13,7 +13,8 @@ import { GeolocationFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-geolocation-ui',
styleUrls: ['geolocation-ui.component.scss'],
templateUrl: 'geolocation-ui.component.html'
templateUrl: 'geolocation-ui.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class GeolocationUIComponent implements OnInit {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { GeolocationFieldPropertiesDto } from 'shared';
@ -13,7 +13,8 @@ import { GeolocationFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-geolocation-validation',
styleUrls: ['geolocation-validation.component.scss'],
templateUrl: 'geolocation-validation.component.html'
templateUrl: 'geolocation-validation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class GeolocationValidationComponent {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/json-ui.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { JsonFieldPropertiesDto } from 'shared';
@ -13,7 +13,8 @@ import { JsonFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-json-ui',
styleUrls: ['json-ui.component.scss'],
templateUrl: 'json-ui.component.html'
templateUrl: 'json-ui.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class JsonUIComponent {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/json-validation.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { JsonFieldPropertiesDto } from 'shared';
@ -13,7 +13,8 @@ import { JsonFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-json-validation',
styleUrls: ['json-validation.component.scss'],
templateUrl: 'json-validation.component.html'
templateUrl: 'json-validation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class JsonValidationComponent {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';
@ -14,7 +14,8 @@ import { FloatConverter, NumberFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-number-ui',
styleUrls: ['number-ui.component.scss'],
templateUrl: 'number-ui.component.html'
templateUrl: 'number-ui.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NumberUIComponent implements OnDestroy, OnInit {
private editorSubscription: Subscription;

5
src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
@ -14,7 +14,8 @@ import { NumberFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-number-validation',
styleUrls: ['number-validation.component.scss'],
templateUrl: 'number-validation.component.html'
templateUrl: 'number-validation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NumberValidationComponent implements OnInit {
@Input()

5
src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';
@ -14,7 +14,8 @@ import { StringFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-string-ui',
styleUrls: ['string-ui.component.scss'],
templateUrl: 'string-ui.component.html'
templateUrl: 'string-ui.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StringUIComponent implements OnDestroy, OnInit {
private editorSubscription: Subscription;

5
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';
@ -14,7 +14,8 @@ import { StringFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-string-validation',
styleUrls: ['string-validation.component.scss'],
templateUrl: 'string-validation.component.html'
templateUrl: 'string-validation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StringValidationComponent implements OnDestroy, OnInit {
private patternSubscription: Subscription;

71
tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs

@ -0,0 +1,71 @@
// ==========================================================================
// CompoundEventConsumerTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Moq;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Infrastructure.CQRS.Events
{
public class CompoundEventConsumerTests
{
private readonly Mock<IEventConsumer> consumer1 = new Mock<IEventConsumer>();
private readonly Mock<IEventConsumer> consumer2 = new Mock<IEventConsumer>();
private sealed class MyEvent : IEvent
{
}
[Fact]
public void Should_return_given_name()
{
var sut = new CompoundEventConsumer("consumer-name");
Assert.Equal("consumer-name", sut.Name);
}
[Fact]
public void Should_return_first_inner_name()
{
var sut = new CompoundEventConsumer(consumer1.Object, consumer2.Object);
Assert.Equal(consumer1.Object.GetType().Name, sut.Name);
}
[Fact]
public async Task Should_clear_all_consumers()
{
consumer1.Setup(x => x.ClearAsync()).Returns(TaskHelper.Done).Verifiable();
consumer2.Setup(x => x.ClearAsync()).Returns(TaskHelper.Done).Verifiable();
var sut = new CompoundEventConsumer("consumer-name", consumer1.Object, consumer2.Object);
await sut.ClearAsync();
consumer1.VerifyAll();
consumer2.VerifyAll();
}
[Fact]
public async Task Should_invoke_all_consumers()
{
var @event = Envelope.Create(new MyEvent());
consumer1.Setup(x => x.On(@event)).Returns(TaskHelper.Done).Verifiable();
consumer2.Setup(x => x.On(@event)).Returns(TaskHelper.Done).Verifiable();
var sut = new CompoundEventConsumer("consumer-name", consumer1.Object, consumer2.Object);
await sut.On(@event);
consumer1.VerifyAll();
consumer2.VerifyAll();
}
}
}

1
tests/Squidex.Infrastructure.Tests/CQRS/Events/EventReceiverTests.cs

@ -90,6 +90,7 @@ namespace Squidex.Infrastructure.CQRS.Events
eventStore.Setup(x => x.GetEventsAsync(2)).Returns(events.ToObservable());
eventConsumer.Setup(x => x.Name).Returns(consumerName);
eventConsumerInfoRepository.Setup(x => x.FindAsync(consumerName)).Returns(Task.FromResult<IEventConsumerInfo>(consumerInfo));
formatter.Setup(x => x.Parse(eventData1)).Returns(envelope1);

6
tests/Squidex.Read.Tests/Apps/CachingAppProviderTests.cs

@ -11,7 +11,9 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Moq;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Apps.Services.Implementations;
using Xunit;
@ -80,7 +82,7 @@ namespace Squidex.Read.Apps
await ProvideAppById(appV1);
sut.Remove(appId);
sut.On(Envelope.Create(new AppLanguageAdded {AppId = appId }).To<IEvent>()).Wait();
await ProvideAppById(appV2);
@ -96,7 +98,7 @@ namespace Squidex.Read.Apps
await ProvideAppByName(appV1);
sut.Remove(appId);
sut.On(Envelope.Create(new AppLanguageAdded { AppId = appId }).To<IEvent>()).Wait();
await ProvideAppByName(appV2);

6
tests/Squidex.Read.Tests/Schemas/CachingSchemaProviderTests.cs

@ -11,7 +11,9 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Moq;
using Squidex.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Schemas.Services.Implementations;
using Xunit;
@ -83,7 +85,7 @@ namespace Squidex.Read.Schemas
await ProvideSchemaById(schemaV1);
sut.Remove(appId, schemaId);
sut.On(Envelope.Create(new FieldAdded { AppId = appId, SchemaId = schemaId })).Wait();
await ProvideSchemaById(schemaV2);
@ -99,7 +101,7 @@ namespace Squidex.Read.Schemas
await ProvideSchemaByName(schemaV1);
sut.Remove(appId, schemaId);
sut.On(Envelope.Create(new SchemaUpdated { AppId = appId, SchemaId = schemaId })).Wait();
await ProvideSchemaByName(schemaV2);

Loading…
Cancel
Save