Browse Source

Some tests fixed.

pull/261/head
Sebastian Stehle 8 years ago
parent
commit
96ab297ff0
  1. 1
      src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs
  2. 7
      src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs
  3. 5
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  4. 2
      src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs
  5. 70
      src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs
  6. 6
      src/Squidex.Domain.Apps.Entities/Backup/IBackupJob.cs
  7. 8
      src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs
  8. 15
      src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs
  9. 15
      src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs
  10. 22
      src/Squidex.Infrastructure/Assets/FolderAssetStore.cs
  11. 2
      src/Squidex.Infrastructure/Assets/IAssetStore.cs
  12. 5
      src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs
  13. 3
      src/Squidex/Config/Domain/EntitiesServices.cs
  14. 36
      src/Squidex/app/features/settings/pages/backups/backups-page.component.html
  15. 3
      src/Squidex/app/features/settings/pages/backups/backups-page.component.scss
  16. 9
      src/Squidex/app/features/settings/pages/backups/backups-page.component.ts
  17. 4
      src/Squidex/app/framework/angular/date-time.pipes.spec.ts
  18. 8
      src/Squidex/app/framework/utils/duration.spec.ts
  19. 14
      src/Squidex/app/framework/utils/duration.ts
  20. 2
      tests/RunCoverage.ps1
  21. 47
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  22. 6
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  23. 16
      tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs
  24. 8
      tools/Migrate_01/MigrationPath.cs
  25. 54
      tools/Migrate_01/Migrations/ConvertEventStoreAppId.cs

1
src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs

@ -17,7 +17,6 @@ namespace Squidex.Domain.Apps.Core.Apps
{
public sealed class LanguagesConfig : IFieldPartitioning
{
public static readonly LanguagesConfig Empty = new LanguagesConfig(ImmutableDictionary<Language, LanguageConfig>.Empty, null, false);
public static readonly LanguagesConfig English = Build(Language.EN);
private readonly ImmutableDictionary<Language, LanguageConfig> languages;

7
src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs

@ -38,7 +38,10 @@ namespace Squidex.Domain.Apps.Core.Rules
Guard.NotNull(action, nameof(action));
this.trigger = trigger;
this.trigger.Freeze();
this.action = action;
this.action.Freeze();
}
[Pure]
@ -69,6 +72,8 @@ namespace Squidex.Domain.Apps.Core.Rules
throw new ArgumentException("New trigger has another type.", nameof(newTrigger));
}
newTrigger.Freeze();
return Clone(clone =>
{
clone.trigger = newTrigger;
@ -85,6 +90,8 @@ namespace Squidex.Domain.Apps.Core.Rules
throw new ArgumentException("New action has another type.", nameof(newAction));
}
newAction.Freeze();
return Clone(clone =>
{
clone.action = newAction;

5
src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -9,7 +9,6 @@ using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
@ -19,8 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
public class AppState : DomainObjectState<AppState>,
IAppEntity
{
private static readonly LanguagesConfig English = LanguagesConfig.Build(Language.EN);
[JsonProperty]
public string Name { get; set; }
@ -37,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
public AppContributors Contributors { get; set; } = AppContributors.Empty;
[JsonProperty]
public LanguagesConfig LanguagesConfig { get; set; } = English;
public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English;
[JsonProperty]
public bool IsArchived { get; set; }

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

@ -21,7 +21,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Assets
{
public class AssetGrain : DomainObjectGrain<AssetState>, IAssetGrain
public class AssetGrain : SquidexDomainObjectGrain<AssetState>, IAssetGrain
{
public AssetGrain(IStore<Guid> store)
: base(store)

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

@ -29,6 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
public sealed class BackupGrain : Grain, IBackupGrain
{
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;
@ -95,8 +96,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task CleanupAsync()
{
var hasUpdated = false;
foreach (var job in state.Jobs)
{
if (!job.Stopped.HasValue)
@ -104,21 +103,38 @@ namespace Squidex.Domain.Apps.Entities.Backup
await CleanupAsync(job);
job.Stopped = clock.GetCurrentInstant();
job.Failed = true;
job.IsFailed = true;
hasUpdated = true;
await WriteAsync();
}
}
if (hasUpdated)
{
await WriteAsync();
}
}
private async Task CleanupAsync(BackupStateJob job)
{
await backupArchiveLocation.DeleteArchiveAsync(job.Id);
try
{
await backupArchiveLocation.DeleteArchiveAsync(job.Id);
}
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("action", "deleteArchive")
.WriteProperty("status", "failed")
.WriteProperty("backupId", job.Id.ToString()));
}
try
{
await assetStore.DeleteAsync(job.Id.ToString(), 0, null);
}
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("action", "deleteBackup")
.WriteProperty("status", "failed")
.WriteProperty("backupId", job.Id.ToString()));
}
}
public async Task RunAsync()
@ -138,7 +154,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
currentTask = new CancellationTokenSource();
currentJob = job;
state.Jobs.Add(job);
var lastTimestamp = job.Started;
state.Jobs.Insert(0, job);
await WriteAsync();
@ -152,8 +170,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
var eventData = @event.Data;
if (eventData.Type == nameof(AssetCreated) ||
eventData.Type == nameof(AssetUpdated))
if (eventData.Type == "AssetCreatedEvent" ||
eventData.Type == "AssetUpdatedEvent")
{
var parsedEvent = eventDataFormatter.Parse(eventData);
@ -176,11 +194,24 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
await assetStore.DownloadAsync(assetId.ToString(), assetVersion, null, attachmentStream);
});
job.HandledAssets++;
}
else
{
await writer.WriteEventAsync(eventData);
}
job.HandledEvents++;
var now = clock.GetCurrentInstant();
if ((now - lastTimestamp) >= UpdateDuration)
{
lastTimestamp = now;
await WriteAsync();
}
}, SquidexHeaders.AppId, appId.ToString(), null, currentTask.Token);
}
@ -189,16 +220,21 @@ namespace Squidex.Domain.Apps.Entities.Backup
currentTask.Token.ThrowIfCancellationRequested();
await assetStore.UploadAsync(job.Id.ToString(), 0, null, stream, currentTask.Token);
currentTask.Token.ThrowIfCancellationRequested();
}
}
catch
catch (Exception ex)
{
job.Failed = true;
log.LogError(ex, w => w
.WriteProperty("action", "makeBackup")
.WriteProperty("status", "failed")
.WriteProperty("backupId", job.Id.ToString()));
job.IsFailed = true;
}
finally
{
await CleanupAsync(job);
job.Stopped = clock.GetCurrentInstant();
await WriteAsync();

6
src/Squidex.Domain.Apps.Entities/Backup/IBackupJob.cs

@ -18,6 +18,10 @@ namespace Squidex.Domain.Apps.Entities.Backup
Instant? Stopped { get; }
bool Failed { get; }
int HandledEvents { get; }
int HandledAssets { get; }
bool IsFailed { get; }
}
}

8
src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs

@ -23,6 +23,12 @@ namespace Squidex.Domain.Apps.Entities.Backup.State
public Instant? Stopped { get; set; }
[JsonProperty]
public bool Failed { get; set; }
public int HandledEvents { get; set; }
[JsonProperty]
public int HandledAssets { get; set; }
[JsonProperty]
public bool IsFailed { get; set; }
}
}

15
src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs

@ -114,18 +114,25 @@ namespace Squidex.Infrastructure.Assets
await blobRef.SetMetadataAsync();
}
public async Task UploadAsync(string name, Stream stream, CancellationToken ct = default(CancellationToken))
public Task UploadAsync(string name, Stream stream, CancellationToken ct = default(CancellationToken))
{
var tempBlob = blobContainer.GetBlockBlobReference(name);
await tempBlob.UploadFromStreamAsync(stream, null, null, null, ct);
return tempBlob.UploadFromStreamAsync(stream, null, null, null, ct);
}
public async Task DeleteAsync(string name)
public Task DeleteAsync(string name)
{
var tempBlob = blobContainer.GetBlockBlobReference(name);
await tempBlob.DeleteIfExistsAsync();
return tempBlob.DeleteIfExistsAsync();
}
public Task DeleteAsync(string id, long version, string suffix)
{
var tempBlob = blobContainer.GetBlockBlobReference(GetObjectName(id, version, suffix));
return tempBlob.DeleteIfExistsAsync();
}
private string GetObjectName(string id, long version, string suffix)

15
src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs

@ -104,6 +104,21 @@ namespace Squidex.Infrastructure.Assets
}
}
public async Task DeleteAsync(string id, long version, string suffix)
{
try
{
await storageClient.DeleteObjectAsync(bucketName, GetObjectName(id, version, suffix));
}
catch (GoogleApiException ex)
{
if (ex.HttpStatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
}
private string GetObjectName(string id, long version, string suffix)
{
Guard.NotNullOrEmpty(id, nameof(id));

22
src/Squidex.Infrastructure/Assets/FolderAssetStore.cs

@ -112,20 +112,22 @@ namespace Squidex.Infrastructure.Assets
}
}
public Task DeleteAsync(string id, long version, string suffix)
{
var file = GetFile(id, version, suffix);
file.Delete();
return TaskHelper.Done;
}
public Task DeleteAsync(string name)
{
try
{
var file = GetFile(name);
var file = GetFile(name);
file.Delete();
file.Delete();
return TaskHelper.Done;
}
catch (FileNotFoundException ex)
{
throw new AssetNotFoundException($"Asset {name} not found.", ex);
}
return TaskHelper.Done;
}
private FileInfo GetFile(string id, long version, string suffix)

2
src/Squidex.Infrastructure/Assets/IAssetStore.cs

@ -24,5 +24,7 @@ namespace Squidex.Infrastructure.Assets
Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken));
Task DeleteAsync(string name);
Task DeleteAsync(string id, long version, string suffix);
}
}

5
src/Squidex/Areas/Api/Controllers/Backups/BackupsContentController.cs → src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs

@ -17,15 +17,14 @@ namespace Squidex.Areas.Api.Controllers.Backups
/// <summary>
/// Manages backups for app.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Backups))]
public class BackupsContentController : ApiController
public class BackupContentController : ApiController
{
private readonly IAssetStore assetStore;
public BackupsContentController(ICommandBus commandBus, IAssetStore assetStore)
public BackupContentController(ICommandBus commandBus, IAssetStore assetStore)
: base(commandBus)
{
this.assetStore = assetStore;

3
src/Squidex/Config/Domain/EntitiesServices.cs

@ -161,6 +161,9 @@ namespace Squidex.Config.Domain
services.AddTransientAs<ConvertEventStore>()
.As<IMigration>();
services.AddTransientAs<ConvertEventStoreAppId>()
.As<IMigration>();
services.AddTransientAs<AddPatterns>()
.As<IMigration>();

36
src/Squidex/app/features/settings/pages/backups/backups-page.component.html

@ -1,10 +1,10 @@
<sqx-title message="{app} | Backups | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel desiredWidth="60rem">
<sqx-panel desiredWidth="50rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">
<button class="btn btn-success" [disabled]="backups.length === 0" (click)="startBackup()">
<button class="btn btn-success" [disabled]="backups.length === 10" (click)="startBackup()">
Start Backup
</button>
</div>
@ -23,10 +23,10 @@
Your have reached the maximum number of backups: 10.
</div>
<div class="table-items-row" *ngFor="let backup of backups">
<div class="row">
<div class="table-items-row" *ngFor="let backup of backups; trackBy: trackBy">
<div class="row no-gutter">
<div class="col col-auto">
<div *ngIf="!backup.stopped" class="backup-status backup-status-pending icon-spin">
<div *ngIf="!backup.stopped" class="backup-status backup-status-pending spin">
<i class="icon-hour-glass"></i>
</div>
<div *ngIf="backup.stopped && backup.isFailed" class="backup-status backup-status-failed">
@ -37,32 +37,34 @@
</div>
</div>
<div class="col col-auto">
<div>Started:</div>
<div>Stopped:</div>
<div>
Started:
</div>
<div>
Duration:
</div>
</div>
<div class="col">
<div class="col col-auto">
<div>
{{backup.started.toISOString()}}
</div>
<div *ngIf="backup.stopped">
{{backup.stopped.toISOString()}}
{{getDuration(backup) | sqxDuration}}
</div>
</div>
<div class="col col-auto">
<div>Progress:</div>
<div>Download:</div>
</div>
<div class="col">
<div>
<span title="Archived events">
<strong>{{backup.handledEvents}}</strong> <i class="icon-time"></i>
Events: <strong class="backup-progress">{{backup.handledEvents | sqxKNumber}}</strong>
</span>,
<span title="Archived assets">
<strong>{{backup.handledAssets}}</strong> <i class="icon-media"></i>
Assets: <strong class="backup-progress">{{backup.handledAssets | sqxKNumber}}</strong>
</span>
</div>
<div>
<a *ngIf="backup.stopped && !backup.isFailed" href="{{getDownloadUrl(backup)}}" target="_blank">
<div *ngIf="backup.stopped && !backup.isFailed">
Download:
<a href="{{getDownloadUrl(backup)}}" target="_blank">
Ready
</a>
</div>

3
src/Squidex/app/features/settings/pages/backups/backups-page.component.scss

@ -6,12 +6,13 @@ $cicle-size: 2.8rem;
.backup-status {
& {
@include circle($cicle-size);
line-height: $cicle-size;
line-height: $cicle-size + .1rem;
text-align: center;
font-size: .4 * $cicle-size;
font-weight: normal;
background: $color-border;
color: $color-dark-foreground;
vertical-align: middle;
}
&-pending {

9
src/Squidex/app/features/settings/pages/backups/backups-page.component.ts

@ -14,6 +14,7 @@ import {
BackupDto,
BackupsService,
DateTime,
Duration,
ImmutableArray
} from 'shared';
@ -77,5 +78,13 @@ export class BackupsPageComponent implements OnInit, OnDestroy {
public getDownloadUrl(backup: BackupDto) {
return this.apiUrl.buildUrl(`api/apps/${this.ctx.appName}/backups/${backup.id}`);
}
public getDuration(backup: BackupDto) {
return Duration.create(backup.started, backup.stopped!);
}
public trackBy(index: number, item: BackupDto) {
return item.id;
}
}

4
src/Squidex/app/framework/angular/date-time.pipes.spec.ts

@ -23,12 +23,12 @@ const dateTime = DateTime.parse('2013-10-03T12:13:14.125', DateTime.iso8601());
describe('DurationPipe', () => {
it('should format to standard duration string', () => {
const duration = Duration.create(dateTime, dateTime.addMinutes(10).addDays(13));
const duration = Duration.create(dateTime, dateTime.addMinutes(10).addDays(13).addSeconds(10));
const pipe = new DurationPipe();
const actual = pipe.transform(duration);
const expected = '312:10h';
const expected = '312:10:10';
expect(actual).toBe(expected);
});

8
src/Squidex/app/framework/utils/duration.spec.ts

@ -28,24 +28,24 @@ describe('Duration', () => {
it('should print to string correctly', () => {
const s = DateTime.today();
const d = s.addHours(1).addMinutes(30).addSeconds(60);
const d = s.addHours(12).addMinutes(30).addSeconds(60);
const duration = Duration.create(s, d);
const actual = duration.toString();
const expected = '1:31h';
const expected = '12:31:00';
expect(actual).toBe(expected);
});
it('should print to string correctly for one digit minutes', () => {
const s = DateTime.today();
const d = s.addHours(1).addMinutes(1).addSeconds(60);
const d = s.addHours(1).addMinutes(2).addSeconds(5);
const duration = Duration.create(s, d);
const actual = duration.toString();
const expected = '1:02h';
const expected = '01:02:05';
expect(actual).toBe(expected);
});

14
src/Squidex/app/framework/utils/duration.ts

@ -25,12 +25,24 @@ export class Duration {
public toString(): string {
const duration = moment.duration(this.value);
let hoursString = Math.floor(duration.asHours()).toString();
if (hoursString.length === 1) {
hoursString = `0${hoursString}`;
}
let minutesString = duration.minutes().toString();
if (minutesString.length === 1) {
minutesString = `0${minutesString}`;
}
return Math.floor(duration.asHours()) + ':' + minutesString + 'h';
let secondsString = duration.seconds().toString();
if (secondsString.length === 1) {
secondsString = `0${secondsString}`;
}
return `${hoursString}:${minutesString}:${secondsString}`;
}
}

2
tests/RunCoverage.ps1

@ -47,7 +47,7 @@ if ($all -Or $appsEntities) {
-register:user `
-target:"C:\Program Files\dotnet\dotnet.exe" `
-targetargs:"test $folderWorking\Squidex.Domain.Apps.Entities.Tests\Squidex.Domain.Apps.Entities.Tests.csproj" `
-filter:"+[Squidex.Domain.Apps.Entities*]*" `
-filter:"+[Squidex.Domain.Apps.Entities*]* -[Squidex.Domain.Apps.Entities*]*CodeGen*" `
-skipautoprops `
-output:"$folderWorking\$folderReports\Entities.xml" `
-oldStyle

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

@ -37,6 +37,36 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Assert.True(result is JObject);
}
[Fact]
public void Should_create_route_data()
{
var appId = Guid.NewGuid();
var @event = new ContentCreated
{
AppId = new NamedId<Guid>(appId, "my-app")
};
var result = sut.ToRouteData(AsEnvelope(@event));
Assert.True(result is JObject);
}
[Fact]
public void Should_create_route_data_from_event()
{
var appId = Guid.NewGuid();
var @event = new ContentCreated
{
AppId = new NamedId<Guid>(appId, "my-app")
};
var result = sut.ToRouteData(AsEnvelope(@event), "MyEventName");
Assert.Equal("MyEventName", result["type"]);
}
[Fact]
public void Should_replace_app_information_from_event()
{
@ -165,6 +195,23 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_plain_value_when_found_from_update_event()
{
var @event = new ContentUpdated
{
Data =
new NamedContentData()
.AddField("city",
new ContentFieldData()
.AddValue("iv", "Berlin"))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event));
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_undefined_when_null()
{

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

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
}
[Fact]
public void Should_not_create_trigger_if_no_trigger_handler_registered()
public void Should_not_create_job_if_no_trigger_handler_registered()
{
var ruleConfig = new Rule(new InvalidTrigger(), new WebhookAction());
var ruleEnvelope = Envelope.Create(new ContentCreated());
@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
}
[Fact]
public void Should_not_create_trigger_if_no_action_handler_registered()
public void Should_not_create_job_if_no_action_handler_registered()
{
var ruleConfig = new Rule(new ContentChangedTrigger(), new InvalidAction());
var ruleEnvelope = Envelope.Create(new ContentCreated());
@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public void Should_not_create_if_not_triggered()
{
var ruleConfig = new Rule(new ContentChangedTrigger(), new InvalidAction());
var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction());
var ruleEnvelope = Envelope.Create(new ContentCreated());
A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger))

16
tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs

@ -84,7 +84,7 @@ namespace Squidex.Infrastructure.Assets
}
[Fact]
public async Task Should_ignore_when_deleting_twice()
public async Task Should_ignore_when_deleting_twice_by_name()
{
((IInitializable)Sut).Initialize();
@ -97,6 +97,20 @@ namespace Squidex.Infrastructure.Assets
await Sut.DeleteAsync(tempId);
}
[Fact]
public async Task Should_ignore_when_deleting_twice_by_id()
{
((IInitializable)Sut).Initialize();
var tempId = Id();
var assetData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4 });
await Sut.UploadAsync(tempId, 0, null, assetData);
await Sut.DeleteAsync(tempId, 0, null);
await Sut.DeleteAsync(tempId, 0, null);
}
private static string Id()
{
return Guid.NewGuid().ToString();

8
tools/Migrate_01/MigrationPath.cs

@ -15,7 +15,7 @@ namespace Migrate_01
{
public sealed class MigrationPath : IMigrationPath
{
private const int CurrentVersion = 6;
private const int CurrentVersion = 7;
private readonly IServiceProvider serviceProvider;
public MigrationPath(IServiceProvider serviceProvider)
@ -38,6 +38,12 @@ namespace Migrate_01
migrations.Add(serviceProvider.GetRequiredService<ConvertEventStore>());
}
// Version 7: Introduces AppId for backups.
else if (version < 7)
{
migrations.Add(serviceProvider.GetRequiredService<ConvertEventStoreAppId>());
}
// Version 5: Fixes the broken command architecture and requires a rebuild of all snapshots.
if (version < 5)
{

54
tools/Migrate_01/Migrations/ConvertEventStoreAppId.cs

@ -0,0 +1,54 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations;
namespace Migrate_01.Migrations
{
public sealed class ConvertEventStoreAppId : IMigration
{
private readonly IEventStore eventStore;
public ConvertEventStoreAppId(IEventStore eventStore)
{
this.eventStore = eventStore;
}
public async Task UpdateAsync()
{
if (eventStore is MongoEventStore mongoEventStore)
{
var collection = mongoEventStore.RawCollection;
var filter = Builders<BsonDocument>.Filter;
await collection.Find(new BsonDocument()).ForEachAsync(async commit =>
{
foreach (BsonDocument @event in commit["Events"].AsBsonArray)
{
var data = JObject.Parse(@event["Payload"].AsString);
if (data.TryGetValue("appId", out var appId))
{
@event["Metadata"][SquidexHeaders.AppId] = NamedId<Guid>.Parse(appId.ToString(), Guid.TryParse).Id.ToString();
}
}
await collection.ReplaceOneAsync(filter.Eq("_id", commit["_id"].AsString), commit);
});
}
}
}
}
Loading…
Cancel
Save