diff --git a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs b/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs index d2b7f4bd4..7aeb72a9a 100644 --- a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs @@ -18,6 +18,7 @@ using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Reflection; // ReSharper disable ClassNeverInstantiated.Local // ReSharper disable UnusedMember.Local +// ReSharper disable InvertIf namespace Squidex.Infrastructure.MongoDb.EventStore { @@ -104,15 +105,11 @@ namespace Squidex.Infrastructure.MongoDb.EventStore public async Task AppendEventsAsync(Guid commitId, string streamName, int expectedVersion, IEnumerable events) { - var allCommits = - await Collection.Find(c => c.EventStream == streamName) - .Project(Projection.Include(x => x.EventCount)) - .ToListAsync(); + var currentVersion = await GetEventVersionAsync(streamName); - var currentVersion = allCommits.Sum(x => x["EventCount"].ToInt32()) - 1; if (currentVersion != expectedVersion) { - throw new InvalidOperationException($"Current version: {currentVersion}, expected version: {expectedVersion}"); + throw new WrongEventVersionException(currentVersion, expectedVersion); } var now = DateTime.UtcNow; @@ -130,8 +127,37 @@ namespace Squidex.Infrastructure.MongoDb.EventStore { commit.EventCount = commit.Events.Count; - await Collection.InsertOneAsync(commit); + try + { + await Collection.InsertOneAsync(commit); + } + catch (MongoWriteException e) + { + if (e.WriteError?.Category == ServerErrorCategory.DuplicateKey) + { + currentVersion = await GetEventVersionAsync(streamName); + + if (currentVersion != expectedVersion) + { + throw new WrongEventVersionException(currentVersion, expectedVersion); + } + } + + throw; + } } } + + private async Task GetEventVersionAsync(string streamName) + { + var allCommits = + await Collection.Find(c => c.EventStream == streamName) + .Project(Projection.Include(x => x.EventCount)) + .ToListAsync(); + + var currentVersion = allCommits.Sum(x => x["EventCount"].ToInt32()) - 1; + + return currentVersion; + } } } diff --git a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs index afa7ed2ce..981520ebf 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs @@ -85,7 +85,14 @@ namespace Squidex.Infrastructure.CQRS.Commands var eventsToSave = events.Select(x => formatter.ToEventData(x, commitId)).ToList(); - await eventStore.AppendEventsAsync(commitId, streamName, versionExpected, eventsToSave); + try + { + await eventStore.AppendEventsAsync(commitId, streamName, versionExpected, eventsToSave); + } + catch (WrongEventVersionException e) + { + throw new DomainObjectVersionException(domainObject.Id.ToString(), domainObject.GetType(), versionCurrent, versionExpected); + } foreach (var eventData in eventsToSave) { diff --git a/src/Squidex.Infrastructure/CQRS/Events/WrongEventVersionException.cs b/src/Squidex.Infrastructure/CQRS/Events/WrongEventVersionException.cs new file mode 100644 index 000000000..ccdc0dab8 --- /dev/null +++ b/src/Squidex.Infrastructure/CQRS/Events/WrongEventVersionException.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// WrongEventVersionException.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Infrastructure.CQRS.Events +{ + public class WrongEventVersionException : Exception + { + private readonly int currentVersion; + private readonly int expectedVersion; + + public int CurrentVersion + { + get { return currentVersion; } + } + + public int ExpectedVersion + { + get { return expectedVersion; } + } + + public WrongEventVersionException(int currentVersion, int expectedVersion) + : base(FormatMessage(currentVersion, expectedVersion)) + { + this.currentVersion = currentVersion; + + this.expectedVersion = expectedVersion; + } + + private static string FormatMessage(int currentVersion, int expectedVersion) + { + return $"Requested version {expectedVersion}, but found {currentVersion}."; + } + } +} diff --git a/src/Squidex.Infrastructure/DomainObjectVersionException.cs b/src/Squidex.Infrastructure/DomainObjectVersionException.cs index 2f0abab08..9a269cbbc 100644 --- a/src/Squidex.Infrastructure/DomainObjectVersionException.cs +++ b/src/Squidex.Infrastructure/DomainObjectVersionException.cs @@ -35,7 +35,7 @@ namespace Squidex.Infrastructure private static string FormatMessage(string id, Type type, int currentVersion, int expectedVersion) { - return $"Request version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}."; + return $"Requested version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}."; } } } diff --git a/src/Squidex/Pipeline/WebpackMiddleware.cs b/src/Squidex/Pipeline/WebpackMiddleware.cs index d8c3e2ca0..e2b51efd7 100644 --- a/src/Squidex/Pipeline/WebpackMiddleware.cs +++ b/src/Squidex/Pipeline/WebpackMiddleware.cs @@ -68,7 +68,7 @@ namespace Squidex.Pipeline } } } - else + else if (context.Response.StatusCode != 304) { await buffer.CopyToAsync(body); } diff --git a/src/Squidex/app/app.component.html b/src/Squidex/app/app.component.html index 8012c5ac6..668ebbd76 100644 --- a/src/Squidex/app/app.component.html +++ b/src/Squidex/app/app.component.html @@ -3,7 +3,7 @@
-
Loading awesomeness
+
Loading Squidex
diff --git a/src/Squidex/app/app.component.spec.ts b/src/Squidex/app/app.component.spec.ts deleted file mode 100644 index 30748747c..000000000 --- a/src/Squidex/app/app.component.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -/* tslint:disable ordered-imports */ -import { RouterModule, provideRoutes } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; - -import { AppComponent } from './app.component'; - -describe('App', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - imports: [ - RouterModule, - RouterTestingModule - ], - providers: [ - provideRoutes([]) - ] - }); - }); - - it('should work', () => { - const fixture = TestBed.createComponent(AppComponent); - - expect(fixture.componentInstance instanceof AppComponent).toBe(true, 'should create AppComponent'); - }); -}); \ No newline at end of file diff --git a/src/Squidex/app/app.module.ts b/src/Squidex/app/app.module.ts index 688b17d77..5d6e80929 100644 --- a/src/Squidex/app/app.module.ts +++ b/src/Squidex/app/app.module.ts @@ -21,7 +21,6 @@ import { AuthService, CurrencyConfig, DecimalSeparatorConfig, - DragService, HistoryService, LanguageService, LocalStoreService, @@ -78,7 +77,6 @@ export function configCurrency() { AppsService, AppMustExistGuard, AuthService, - DragService, HistoryService, LanguageService, LocalStoreService, diff --git a/src/Squidex/app/features/apps/pages/apps-page.component.html b/src/Squidex/app/features/apps/pages/apps-page.component.html index 4d2c6bffa..0e79b9491 100644 --- a/src/Squidex/app/features/apps/pages/apps-page.component.html +++ b/src/Squidex/app/features/apps/pages/apps-page.component.html @@ -1,9 +1,21 @@ - -
+ + + +

You are not collaborating to any app yet

+ +
+
+
+

{{app.name}}

+ + Edit +
+
+