Browse Source

User mapping.

pull/311/head
Sebastian 7 years ago
parent
commit
5a0bfdd287
  1. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  2. 18
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs
  3. 132
      src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  4. 58
      src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs
  5. 18
      src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs
  6. 5
      src/Squidex.Domain.Apps.Entities/Backup/BackupHandler.cs
  7. 9
      src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs
  8. 41
      src/Squidex.Domain.Apps.Entities/Backup/BackupSerializer.cs
  9. 9
      src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs
  10. 26
      src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  11. 2
      src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs
  12. 12
      src/Squidex.Infrastructure/CollectionExtensions.cs
  13. 16
      src/Squidex.Infrastructure/RefTokenType.cs
  14. 4
      src/Squidex.Infrastructure/States/IStore.cs
  15. 17
      src/Squidex.Infrastructure/States/StoreExtensions.cs
  16. 4
      src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs
  17. 6
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  18. 8
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  19. 2
      tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs
  20. 7
      tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs
  21. 31
      tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs
  22. 4
      tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddlewareTests.cs

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -209,7 +209,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
if (@event.Actor != null)
{
if (@event.Actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase))
if (@event.Actor.Type.Equals(RefTokenType.Client, StringComparison.OrdinalIgnoreCase))
{
return @event.Actor.ToString();
}
@ -227,7 +227,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
if (@event.Actor != null)
{
if (@event.Actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase))
if (@event.Actor.Type.Equals(RefTokenType.Client, StringComparison.OrdinalIgnoreCase))
{
return @event.Actor.ToString();
}

18
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs

@ -43,18 +43,18 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
case JTokenType.Object:
return FromObject(value, engine);
case JTokenType.Array:
{
var arr = (JArray)value;
{
var arr = (JArray)value;
var target = new JsValue[arr.Count];
var target = new JsValue[arr.Count];
for (var i = 0; i < arr.Count; i++)
{
target[i] = Map(arr[i], engine);
}
for (var i = 0; i < arr.Count; i++)
{
target[i] = Map(arr[i], engine);
}
return engine.Array.Construct(target);
}
return engine.Array.Construct(target);
}
}
throw new ArgumentException("Invalid json type.", nameof(value));

132
src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -11,32 +11,66 @@ using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class BackupApps : BackupHandlerWithStore
{
private const string UsersFile = "Users.json";
private readonly IGrainFactory grainFactory;
private readonly HashSet<string> users = new HashSet<string>();
private readonly IUserResolver userResolver;
private readonly HashSet<string> activeUsers = new HashSet<string>();
private readonly Dictionary<string, string> usersWithEmail = new Dictionary<string, string>();
private readonly Dictionary<string, RefToken> userMapping = new Dictionary<string, RefToken>();
private bool isReserved;
private bool isActorAssigned;
private AppCreated appCreated;
public override string Name { get; } = "Apps";
public BackupApps(IStore<Guid> store, IGrainFactory grainFactory)
public BackupApps(IStore<Guid> store, IGrainFactory grainFactory, IUserResolver userResolver)
: base(store)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(userResolver, nameof(userResolver));
this.grainFactory = grainFactory;
this.userResolver = userResolver;
}
public override async Task BackupEventAsync(Envelope<IEvent> @event, Guid appId, BackupWriter writer)
{
if (@event.Payload is AppContributorAssigned appContributorAssigned)
{
var userId = appContributorAssigned.ContributorId;
if (!usersWithEmail.ContainsKey(userId))
{
var user = await userResolver.FindByIdOrEmailAsync(userId);
if (user != null)
{
usersWithEmail.Add(userId, user.Email);
}
}
}
}
public async override Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader)
public override Task BackupAsync(Guid appId, BackupWriter writer)
{
return WriterUsersAsync(writer);
}
public async override Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader, RefToken actor)
{
switch (@event.Payload)
{
@ -44,31 +78,103 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
this.appCreated = appCreated;
var index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
await ResolveUsersAsync(reader, actor);
await ReserveAppAsync();
break;
}
if (!(isReserved = await index.ReserveAppAsync(appCreated.AppId.Id, appCreated.AppId.Name)))
case AppContributorAssigned contributorAssigned:
{
if (isActorAssigned)
{
throw new BackupRestoreException("The app id or name is not available.");
contributorAssigned.ContributorId = MapUser(contributorAssigned.ContributorId, actor).Identifier;
}
else
{
isActorAssigned = true;
contributorAssigned.ContributorId = actor.Identifier;
}
activeUsers.Add(contributorAssigned.ContributorId);
break;
}
case AppContributorAssigned contributorAssigned:
users.Add(contributorAssigned.ContributorId);
break;
case AppContributorRemoved contributorRemoved:
users.Remove(contributorRemoved.ContributorId);
break;
{
contributorRemoved.ContributorId = MapUser(contributorRemoved.ContributorId, actor).Identifier;
activeUsers.Remove(contributorRemoved.ContributorId);
break;
}
}
if (@event.Payload is SquidexEvent squidexEvent)
{
squidexEvent.Actor = MapUser(squidexEvent.Actor.Identifier, actor);
}
}
private async Task ReserveAppAsync()
{
var index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
if (!(isReserved = await index.ReserveAppAsync(appCreated.AppId.Id, appCreated.AppId.Name)))
{
throw new BackupRestoreException("The app id or name is not available.");
}
}
private RefToken MapUser(string userId, RefToken fallback)
{
return userMapping.GetOrAdd(userId, fallback);
}
private async Task ResolveUsersAsync(BackupReader reader, RefToken actor)
{
await ReadUsersAsync(reader);
foreach (var kvp in usersWithEmail)
{
var user = await userResolver.FindByIdOrEmailAsync(kvp.Value);
if (user != null)
{
userMapping[kvp.Key] = new RefToken(RefTokenType.Subject, user.Id);
}
else
{
userMapping[kvp.Key] = actor;
}
}
}
private async Task ReadUsersAsync(BackupReader reader)
{
await reader.ReadAttachmentAsync(UsersFile, stream =>
{
stream.SerializeAsJson(usersWithEmail);
return TaskHelper.Done;
});
}
private Task WriterUsersAsync(BackupWriter writer)
{
return writer.WriteAttachmentAsync(UsersFile, stream =>
{
stream.SerializeAsJson(usersWithEmail);
return TaskHelper.Done;
});
}
public override async Task CompleteRestoreAsync(Guid appId, BackupReader reader)
{
await grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).AddAppAsync(appCreated.AppId.Id, appCreated.AppId.Name);
foreach (var user in users)
foreach (var user in activeUsers)
{
await grainFactory.GetGrain<IAppsByUserIndex>(user).AddAppAsync(appCreated.AppId.Id);
}

58
src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs

@ -7,9 +7,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Entities.Backup;
@ -25,64 +23,52 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class BackupAssets : BackupHandlerWithStore
{
private static readonly JsonSerializer Serializer = JsonSerializer.Create();
private const string TagsFile = "AssetTags.json";
private readonly HashSet<Guid> assetIds = new HashSet<Guid>();
private readonly IAssetStore assetStore;
private readonly IAssetRepository assetRepository;
private readonly ITagService tagService;
private readonly IEventDataFormatter eventDataFormatter;
public override string Name { get; } = "Assets";
public BackupAssets(IStore<Guid> store,
IEventDataFormatter eventDataFormatter,
IAssetStore assetStore,
IAssetRepository assetRepository,
ITagService tagService)
: base(store)
{
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(assetStore, nameof(assetStore));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(tagService, nameof(tagService));
this.eventDataFormatter = eventDataFormatter;
this.assetStore = assetStore;
this.assetRepository = assetRepository;
this.tagService = tagService;
}
public override Task BackupEventAsync(EventData @event, Guid appId, BackupWriter writer)
public override Task BackupAsync(Guid appId, BackupWriter writer)
{
if (@event.Type == "AssetCreatedEvent" ||
@event.Type == "AssetUpdatedEvent")
{
var parsedEvent = eventDataFormatter.Parse(@event);
return BackupTagsAsync(appId, writer);
}
switch (parsedEvent.Payload)
{
case AssetCreated assetCreated:
return WriteAssetAsync(assetCreated.AssetId, assetCreated.FileVersion, writer);
case AssetUpdated assetUpdated:
return WriteAssetAsync(assetUpdated.AssetId, assetUpdated.FileVersion, writer);
}
public override Task BackupEventAsync(Envelope<IEvent> @event, Guid appId, BackupWriter writer)
{
switch (@event.Payload)
{
case AssetCreated assetCreated:
return WriteAssetAsync(assetCreated.AssetId, assetCreated.FileVersion, writer);
case AssetUpdated assetUpdated:
return WriteAssetAsync(assetUpdated.AssetId, assetUpdated.FileVersion, writer);
}
return TaskHelper.Done;
}
public override Task BackupAsync(Guid appId, BackupWriter writer)
{
return BackupTagsAsync(appId, writer);
}
public override Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader)
public override Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader, RefToken actor)
{
switch (@event.Payload)
{
case AssetCreated assetCreated:
assetIds.Add(assetCreated.AssetId);
return ReadAssetAsync(assetCreated.AssetId, assetCreated.FileVersion, reader);
case AssetUpdated assetUpdated:
return ReadAssetAsync(assetUpdated.AssetId, assetUpdated.FileVersion, reader);
@ -100,27 +86,21 @@ namespace Squidex.Domain.Apps.Entities.Assets
private Task RestoreTagsAsync(Guid appId, BackupReader reader)
{
return reader.ReadAttachmentAsync("AssetTags.json", async stream =>
return reader.ReadAttachmentAsync(TagsFile, stream =>
{
using (var textReader = new StreamReader(stream))
{
var tags = (TagSet)Serializer.Deserialize(textReader, typeof(TagSet));
var tags = stream.DeserializeAsJson<TagSet>();
await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags);
}
return tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags);
});
}
private Task BackupTagsAsync(Guid appId, BackupWriter writer)
{
return writer.WriteAttachmentAsync("AssetTags.json", async stream =>
return writer.WriteAttachmentAsync(TagsFile, async stream =>
{
var tags = await tagService.GetExportableTagsAsync(appId, TagGroups.Assets);
using (var textWriter = new StreamWriter(stream))
{
Serializer.Serialize(textWriter, tags);
}
stream.SerializeAsJson(tags);
});
}
@ -134,6 +114,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
private Task ReadAssetAsync(Guid assetId, long fileVersion, BackupReader reader)
{
assetIds.Add(assetId);
return reader.ReadAttachmentAsync(GetName(assetId, fileVersion), async stream =>
{
try

18
src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs

@ -30,18 +30,18 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
private const int MaxBackups = 10;
private static readonly Duration UpdateDuration = Duration.FromSeconds(1);
private readonly IClock clock;
private readonly IAssetStore assetStore;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IClock clock;
private readonly IEnumerable<BackupHandler> handlers;
private readonly ISemanticLog log;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore;
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly ISemanticLog log;
private readonly IStore<Guid> store;
private CancellationTokenSource currentTask;
private BackupStateJob currentJob;
private Guid appId;
private BackupState state = new BackupState();
private Guid appId;
private IPersistence<BackupState> persistence;
public BackupGrain(
@ -141,15 +141,15 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
using (var writer = new BackupWriter(stream))
{
await eventStore.QueryAsync(async @event =>
await eventStore.QueryAsync(async storedEvent =>
{
var eventData = @event.Data;
var @event = eventDataFormatter.Parse(storedEvent.Data);
writer.WriteEvent(@event);
writer.WriteEvent(storedEvent);
foreach (var handler in handlers)
{
await handler.BackupEventAsync(eventData, appId, writer);
await handler.BackupEventAsync(@event, appId, writer);
}
job.HandledEvents = writer.WrittenEvents;

5
src/Squidex.Domain.Apps.Entities/Backup/BackupHandler.cs

@ -7,6 +7,7 @@
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Tasks;
@ -16,12 +17,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public abstract string Name { get; }
public virtual Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader)
public virtual Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader, RefToken actor)
{
return TaskHelper.Done;
}
public virtual Task BackupEventAsync(EventData @event, Guid appId, BackupWriter writer)
public virtual Task BackupEventAsync(Envelope<IEvent> @event, Guid appId, BackupWriter writer)
{
return TaskHelper.Done;
}

9
src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

@ -9,7 +9,6 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Backup.Archive;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
@ -18,7 +17,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class BackupReader : DisposableObjectBase
{
private static readonly JsonSerializer JsonSerializer = JsonSerializer.CreateDefault();
private readonly ZipArchive archive;
private int readEvents;
private int readAttachments;
@ -81,12 +79,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = eventEntry.Open())
{
using (var textReader = new StreamReader(stream))
{
var storedEvent = (StoredEvent)JsonSerializer.Deserialize(textReader, typeof(StoredEvent));
var storedEvent = stream.DeserializeAsJson<StoredEvent>();
await handler(storedEvent);
}
await handler(storedEvent);
}
readEvents++;

41
src/Squidex.Domain.Apps.Entities/Backup/BackupSerializer.cs

@ -0,0 +1,41 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.IO;
using Newtonsoft.Json;
namespace Squidex.Domain.Apps.Entities.Backup
{
public static class BackupSerializer
{
private static readonly JsonSerializer JsonSerializer = JsonSerializer.CreateDefault();
public static void SerializeAsJson<T>(this Stream stream, T value)
{
using (var writer = new StreamWriter(stream))
{
JsonSerializer.Serialize(writer, value);
}
}
public static T DeserializeAsJson<T>(this Stream stream)
{
using (var reader = new StreamReader(stream))
{
return (T)JsonSerializer.Deserialize(reader, typeof(T));
}
}
public static void DeserializeAsJson<T>(this Stream stream, T result)
{
using (var reader = new StreamReader(stream))
{
JsonSerializer.Populate(reader, result);
}
}
}
}

9
src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs

@ -9,7 +9,6 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Backup.Archive;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
@ -18,7 +17,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class BackupWriter : DisposableObjectBase
{
private static readonly JsonSerializer JsonSerializer = JsonSerializer.CreateDefault();
private readonly ZipArchive archive;
private int writtenEvents;
private int writtenAttachments;
@ -63,14 +61,13 @@ namespace Squidex.Domain.Apps.Entities.Backup
public void WriteEvent(StoredEvent storedEvent)
{
Guard.NotNull(storedEvent, nameof(storedEvent));
var eventEntry = archive.CreateEntry(ArchiveHelper.GetEventPath(writtenEvents));
using (var stream = eventEntry.Open())
{
using (var textWriter = new StreamWriter(stream))
{
JsonSerializer.Serialize(textWriter, storedEvent);
}
stream.SerializeAsJson(storedEvent);
}
writtenEvents++;

26
src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -26,15 +26,15 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class RestoreGrain : GrainOfString, IRestoreGrain
{
private readonly IClock clock;
private readonly IAssetStore assetStore;
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IClock clock;
private readonly IEnumerable<BackupHandler> handlers;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IGrainFactory grainFactory;
private readonly ISemanticLog log;
private readonly IEventStore eventStore;
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IStore<string> store;
private readonly IEnumerable<BackupHandler> handlers;
private RefToken actor;
private RestoreState state = new RestoreState();
private IPersistence<RestoreState> persistence;
@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
public override async Task OnActivateAsync(string key)
{
actor = new RefToken("subject", key);
actor = new RefToken(RefTokenType.Subject, key);
persistence = store.WithSnapshots<RestoreState, string>(GetType(), key, s => state = s);
@ -255,8 +255,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ReadEventsAsync(BackupReader reader)
{
var isOwnerAdded = false;
await reader.ReadEventsAsync(async (storedEvent) =>
{
var @event = eventDataFormatter.Parse(storedEvent.Data);
@ -270,24 +268,16 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
CurrentJob.AppId = appCreated.AppId.Id;
}
else if (@event.Payload is AppContributorAssigned appContributorAssigned)
{
if (!isOwnerAdded)
{
isOwnerAdded = true;
appContributorAssigned.ContributorId = actor.Identifier;
}
}
foreach (var handler in handlers)
{
await handler.RestoreEventAsync(@event, CurrentJob.AppId, reader);
await handler.RestoreEventAsync(@event, CurrentJob.AppId, reader, actor);
}
var eventData = eventDataFormatter.ToEventData(@event, @event.Headers.CommitId());
var eventCommit = new List<EventData> { eventData };
await eventStore.AppendAsync(Guid.NewGuid(), storedEvent.StreamName, new List<EventData> { eventData });
await eventStore.AppendAsync(Guid.NewGuid(), storedEvent.StreamName, eventCommit);
Log($"Read {reader.ReadEvents} events and {reader.ReadAttachments} attachments.", true);
});

2
src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs

@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
this.grainFactory = grainFactory;
}
public override Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader)
public override Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader, RefToken actor)
{
switch (@event.Payload)
{

12
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -167,6 +167,18 @@ namespace Squidex.Infrastructure
return result;
}
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue fallback)
{
if (!dictionary.TryGetValue(key, out var result))
{
result = fallback;
dictionary.Add(key, result);
}
return result;
}
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> creator)
{
if (!dictionary.TryGetValue(key, out var result))

16
src/Squidex.Infrastructure/RefTokenType.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure
{
public static class RefTokenType
{
public const string Subject = "subject";
public const string Client = "client";
}
}

4
src/Squidex.Infrastructure/States/IStore.cs

@ -20,9 +20,5 @@ namespace Squidex.Infrastructure.States
IPersistence<TState> WithSnapshotsAndEventSourcing<TState>(Type owner, TKey key, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent);
ISnapshotStore<TState, TKey> GetSnapshotStore<TState>();
Task ClearSnapshotsAsync<TState>();
Task RemoveSnapshotAsync<TState>(TKey key);
}
}

17
src/Squidex.Infrastructure/States/StoreExtensions.cs

@ -58,5 +58,22 @@ namespace Squidex.Infrastructure.States
{
return store.WithSnapshotsAndEventSourcing(typeof(TOwner), key, applySnapshot.ToAsync(), applyEvent.ToAsync());
}
public static Task ClearSnapshotsAsync<TKey, TState>(this IStore<TKey> store)
{
return store.GetSnapshotStore<TState>().ClearAsync();
}
public static Task RemoveSnapshotAsync<TKey, TState>(this IStore<TKey> store, TKey key)
{
return store.GetSnapshotStore<TState>().RemoveAsync(key);
}
public static async Task<TState> GetSnapshotAsync<TKey, TState>(this IStore<TKey> store, TKey key)
{
var result = await store.GetSnapshotStore<TState>().ReadAsync(key);
return result.Value;
}
}
}

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

@ -56,14 +56,14 @@ namespace Squidex.Pipeline.CommandMiddlewares
{
var subjectId = httpContextAccessor.HttpContext.User.OpenIdSubject();
return subjectId == null ? null : new RefToken("subject", subjectId);
return subjectId == null ? null : new RefToken(RefTokenType.Subject, subjectId);
}
private RefToken FindActorFromClient()
{
var clientId = httpContextAccessor.HttpContext.User.OpenIdClientId();
return clientId == null ? null : new RefToken("client", clientId);
return clientId == null ? null : new RefToken(RefTokenType.Client, clientId);
}
}
}

6
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public void Should_format_email_and_display_name_from_user()
{
var @event = new EnrichedContentEvent { User = user, Actor = new RefToken("subject", "123") };
var @event = new EnrichedContentEvent { User = user, Actor = new RefToken(RefTokenType.Subject, "123") };
var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event);
@ -116,7 +116,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public void Should_return_undefined_if_user_is_not_found()
{
var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") };
var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Subject, "123") };
var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event);
@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public void Should_format_email_and_display_name_from_client()
{
var @event = new EnrichedContentEvent { Actor = new RefToken("client", "android") };
var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Client, "android") };
var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event);

8
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -141,9 +141,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
Id = id,
Version = 1,
Created = now,
CreatedBy = new RefToken("subject", "user1"),
CreatedBy = new RefToken(RefTokenType.Subject, "user1"),
LastModified = now,
LastModifiedBy = new RefToken("subject", "user2"),
LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"),
Data = data
};
@ -159,9 +159,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
Id = id,
Version = 1,
Created = now,
CreatedBy = new RefToken("subject", "user1"),
CreatedBy = new RefToken(RefTokenType.Subject, "user1"),
LastModified = now,
LastModifiedBy = new RefToken("subject", "user2"),
LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"),
FileName = "MyFile.png",
FileSize = 1024,
FileVersion = 123,

2
tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs

@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers
private readonly IPersistence<TState> persistence1 = A.Fake<IPersistence<TState>>();
private readonly IPersistence persistence2 = A.Fake<IPersistence>();
protected RefToken User { get; } = new RefToken("subject", Guid.NewGuid().ToString());
protected RefToken User { get; } = new RefToken(RefTokenType.Subject, Guid.NewGuid().ToString());
protected Guid AppId { get; } = Guid.NewGuid();

7
tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs

@ -71,6 +71,13 @@ namespace Squidex.Infrastructure
[Fact]
public void GetOrAdd_should_return_default_and_add_it_if_key_not_exists()
{
Assert.Equal(24, valueDictionary.GetOrAdd(12, 24));
Assert.Equal(24, valueDictionary[12]);
}
[Fact]
public void GetOrAdd_should_return_default_and_add_it_with_fallback_if_key_not_exists()
{
Assert.Equal(24, valueDictionary.GetOrAdd(12, x => 24));
Assert.Equal(24, valueDictionary[12]);

31
tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs

@ -33,15 +33,6 @@ namespace Squidex.Infrastructure.States
sut = new Store<string>(eventStore, eventDataFormatter, services, streamNameResolver);
}
[Fact]
public async Task Should_call_snapshot_store_on_clear()
{
await sut.ClearSnapshotsAsync<int>();
A.CallTo(() => snapshotStore.ClearAsync())
.MustHaveHappened();
}
[Fact]
public async Task Should_read_from_store()
{
@ -161,10 +152,19 @@ namespace Squidex.Infrastructure.States
.MustHaveHappened();
}
[Fact]
public async Task Should_call_snapshot_store_on_clear()
{
await sut.ClearSnapshotsAsync<string, int>();
A.CallTo(() => snapshotStore.ClearAsync())
.MustHaveHappened();
}
[Fact]
public async Task Should_delete_snapshot_but_not_events_when_deleted_from_store()
{
await sut.RemoveSnapshotAsync<int>(key);
await sut.RemoveSnapshotAsync<string, int>(key);
A.CallTo(() => eventStore.DeleteStreamAsync(A<string>.Ignored))
.MustNotHaveHappened();
@ -172,5 +172,16 @@ namespace Squidex.Infrastructure.States
A.CallTo(() => snapshotStore.RemoveAsync(key))
.MustHaveHappened();
}
[Fact]
public async Task Should_get_snapshot()
{
A.CallTo(() => snapshotStore.ReadAsync(key))
.Returns((123, -1));
var result = await sut.GetSnapshotAsync<string, int>(key);
Assert.Equal(123, result);
}
}
}

4
tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddlewareTests.cs

@ -66,7 +66,7 @@ namespace Squidex.Pipeline.CommandMiddlewares
await sut.HandleAsync(context);
Assert.Equal(new RefToken("subject", "me"), command.Actor);
Assert.Equal(new RefToken(RefTokenType.Subject, "me"), command.Actor);
}
[Fact]
@ -79,7 +79,7 @@ namespace Squidex.Pipeline.CommandMiddlewares
await sut.HandleAsync(context);
Assert.Equal(new RefToken("client", "my-client"), command.Actor);
Assert.Equal(new RefToken(RefTokenType.Client, "my-client"), command.Actor);
}
[Fact]

Loading…
Cancel
Save