mirror of https://github.com/Squidex/squidex.git
46 changed files with 843 additions and 623 deletions
@ -1,36 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public sealed class AppGrainCleaner<T> : ICleanableAppStorage where T : ICleanableAppGrain |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public string Name |
|||
{ |
|||
get { return typeof(T).Name; } |
|||
} |
|||
|
|||
public AppGrainCleaner(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public Task ClearAsync(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<T>(appId).ClearAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Indexes; |
|||
using Squidex.Domain.Apps.Entities.Apps.State; |
|||
using Squidex.Domain.Apps.Entities.Backup; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps |
|||
{ |
|||
public sealed class BackupApps : BackupHandlerWithStore |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
private readonly HashSet<string> users = new HashSet<string>(); |
|||
private bool isReserved; |
|||
private AppCreated appCreated; |
|||
|
|||
public BackupApps(IStore<Guid> store, IGrainFactory grainFactory) |
|||
: base(store) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public override Task RemoveAsync(Guid appId) |
|||
{ |
|||
return RemoveSnapshotAsync<AppState>(appId); |
|||
} |
|||
|
|||
public async override Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader) |
|||
{ |
|||
switch (@event.Payload) |
|||
{ |
|||
case AppCreated appCreated: |
|||
{ |
|||
this.appCreated = appCreated; |
|||
|
|||
var index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); |
|||
|
|||
if (!(isReserved = await index.ReserveAppAsync(appCreated.AppId.Id, appCreated.AppId.Name))) |
|||
{ |
|||
throw new DomainException("The app id or name is not available."); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case AppContributorAssigned contributorAssigned: |
|||
users.Add(contributorAssigned.ContributorId); |
|||
break; |
|||
|
|||
case AppContributorRemoved contributorRemoved: |
|||
users.Remove(contributorRemoved.ContributorId); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
public override async Task RestoreAsync(Guid appId, BackupReader reader) |
|||
{ |
|||
await grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).AddAppAsync(appCreated.AppId.Id, appCreated.AppId.Name); |
|||
|
|||
foreach (var user in users) |
|||
{ |
|||
await grainFactory.GetGrain<IAppsByUserIndex>(user).AddAppAsync(appCreated.AppId.Id); |
|||
} |
|||
} |
|||
|
|||
public override async Task CleanupRestoreAsync(Guid appId, Exception exception) |
|||
{ |
|||
if (isReserved) |
|||
{ |
|||
var index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); |
|||
|
|||
await index.ReserveAppAsync(appCreated.AppId.Id, appCreated.AppId.Name); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Assets.State; |
|||
using Squidex.Domain.Apps.Entities.Backup; |
|||
using Squidex.Domain.Apps.Entities.Tags; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public sealed class BackupAssets : BackupHandlerWithStore |
|||
{ |
|||
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 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 async Task RemoveAsync(Guid appId) |
|||
{ |
|||
await tagService.ClearAsync(appId, TagGroups.Assets); |
|||
|
|||
await assetRepository.RemoveAsync(appId); |
|||
} |
|||
|
|||
public override Task BackupEventAsync(EventData @event, Guid appId, BackupWriter writer) |
|||
{ |
|||
if (@event.Type == "AssetCreatedEvent" || |
|||
@event.Type == "AssetUpdatedEvent") |
|||
{ |
|||
var parsedEvent = eventDataFormatter.Parse(@event); |
|||
|
|||
switch (parsedEvent.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 RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader) |
|||
{ |
|||
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); |
|||
} |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public override Task RestoreAsync(Guid appId, BackupReader reader) |
|||
{ |
|||
return RebuildManyAsync(assetIds, id => RebuildAsync<AssetState, AssetGrain>(id, (e, s) => s.Apply(e))); |
|||
} |
|||
|
|||
private Task WriteAssetAsync(Guid assetId, long fileVersion, BackupWriter writer) |
|||
{ |
|||
return writer.WriteAttachmentAsync(GetName(assetId, fileVersion), stream => |
|||
{ |
|||
return assetStore.DownloadAsync(assetId.ToString(), fileVersion, null, stream); |
|||
}); |
|||
} |
|||
|
|||
private Task ReadAssetAsync(Guid assetId, long fileVersion, BackupReader reader) |
|||
{ |
|||
return reader.ReadAttachmentAsync(GetName(assetId, fileVersion), async stream => |
|||
{ |
|||
try |
|||
{ |
|||
await assetStore.UploadAsync(assetId.ToString(), fileVersion, null, stream); |
|||
} |
|||
catch (AssetAlreadyExistsException) |
|||
{ |
|||
return; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static string GetName(Guid assetId, long fileVersion) |
|||
{ |
|||
return $"{assetId}_{fileVersion}.asset"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Backup |
|||
{ |
|||
public abstract class BackupHandler |
|||
{ |
|||
public virtual Task RestoreEventAsync(Envelope<IEvent> @event, Guid appId, BackupReader reader) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public virtual Task BackupEventAsync(EventData @event, Guid appId, BackupWriter writer) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public virtual Task RestoreAsync(Guid appId, BackupReader reader) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public virtual Task BackupAsync(Guid appId, BackupWriter writer) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public virtual Task CleanupRestoreAsync(Guid appId, Exception exception) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public virtual Task CompleteRestoreAsync(Guid appId, BackupReader reader) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public virtual Task CompleteBackupAsync(Guid appId, BackupWriter writer) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public virtual Task RemoveAsync(Guid appId) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -1,50 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Backup.Handlers |
|||
{ |
|||
public sealed class RestoreApp : HandlerBase, IRestoreHandler |
|||
{ |
|||
private NamedId<Guid> appId; |
|||
|
|||
public string Name { get; } = "App"; |
|||
|
|||
public RestoreApp(IStore<Guid> store) |
|||
: base(store) |
|||
{ |
|||
} |
|||
|
|||
public Task HandleAsync(Envelope<IEvent> @event, Stream attachment) |
|||
{ |
|||
if (@event.Payload is AppCreated appCreated) |
|||
{ |
|||
appId = appCreated.AppId; |
|||
} |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public Task ProcessAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public Task CompleteAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Assets; |
|||
using Squidex.Domain.Apps.Entities.Assets.State; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Backup.Handlers |
|||
{ |
|||
public sealed class RestoreAssets : HandlerBase, IRestoreHandler |
|||
{ |
|||
private readonly HashSet<Guid> assetIds = new HashSet<Guid>(); |
|||
private readonly IAssetStore assetStore; |
|||
|
|||
public string Name { get; } = "Assets"; |
|||
|
|||
public RestoreAssets(IStore<Guid> store, IAssetStore assetStore) |
|||
: base(store) |
|||
{ |
|||
Guard.NotNull(assetStore, nameof(assetStore)); |
|||
|
|||
this.assetStore = assetStore; |
|||
} |
|||
|
|||
public async Task HandleAsync(Envelope<IEvent> @event, Stream attachment) |
|||
{ |
|||
var assetVersion = 0L; |
|||
var assetId = Guid.Empty; |
|||
|
|||
switch (@event.Payload) |
|||
{ |
|||
case AssetCreated assetCreated: |
|||
assetId = assetCreated.AssetId; |
|||
assetVersion = assetCreated.FileVersion; |
|||
assetIds.Add(assetCreated.AssetId); |
|||
break; |
|||
case AssetUpdated asetUpdated: |
|||
assetId = asetUpdated.AssetId; |
|||
assetVersion = asetUpdated.FileVersion; |
|||
break; |
|||
} |
|||
|
|||
if (attachment != null) |
|||
{ |
|||
await assetStore.UploadAsync(assetId.ToString(), assetVersion, null, attachment); |
|||
} |
|||
} |
|||
|
|||
public Task ProcessAsync() |
|||
{ |
|||
return RebuildManyAsync(assetIds, id => RebuildAsync<AssetState, AssetGrain>(id, (e, s) => s.Apply(e))); |
|||
} |
|||
|
|||
public Task CompleteAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Backup |
|||
{ |
|||
public interface IRestoreHandler |
|||
{ |
|||
string Name { get; } |
|||
|
|||
Task HandleAsync(Envelope<IEvent> @event, Stream attachment); |
|||
|
|||
Task ProcessAsync(); |
|||
|
|||
Task CompleteAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Backup; |
|||
using Squidex.Domain.Apps.Entities.History.Repositories; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.History |
|||
{ |
|||
public sealed class BackupHistory : BackupHandler |
|||
{ |
|||
private readonly IHistoryEventRepository historyEventRepository; |
|||
|
|||
public BackupHistory(IHistoryEventRepository historyEventRepository) |
|||
{ |
|||
Guard.NotNull(historyEventRepository, nameof(historyEventRepository)); |
|||
|
|||
this.historyEventRepository = historyEventRepository; |
|||
} |
|||
|
|||
public override Task RemoveAsync(Guid appId) |
|||
{ |
|||
return historyEventRepository.RemoveAsync(appId); |
|||
} |
|||
} |
|||
} |
|||
@ -1,46 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Orleans; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public class AppGrainCleanerTests |
|||
{ |
|||
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); |
|||
private readonly ICleanableAppGrain index = A.Fake<ICleanableAppGrain>(); |
|||
private readonly Guid appId = Guid.NewGuid(); |
|||
private readonly AppGrainCleaner<ICleanableAppGrain> sut; |
|||
|
|||
public AppGrainCleanerTests() |
|||
{ |
|||
A.CallTo(() => grainFactory.GetGrain<ICleanableAppGrain>(appId, null)) |
|||
.Returns(index); |
|||
|
|||
sut = new AppGrainCleaner<ICleanableAppGrain>(grainFactory); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_provide_name() |
|||
{ |
|||
Assert.Equal(typeof(ICleanableAppGrain).Name, sut.Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_forward_to_index() |
|||
{ |
|||
await sut.ClearAsync(appId); |
|||
|
|||
A.CallTo(() => index.ClearAsync()) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue