diff --git a/src/Squidex.Core/LanguagesConfig.cs b/src/Squidex.Core/LanguagesConfig.cs index 4bf5187b8..39e343451 100644 --- a/src/Squidex.Core/LanguagesConfig.cs +++ b/src/Squidex.Core/LanguagesConfig.cs @@ -81,24 +81,25 @@ namespace Squidex.Core return new LanguagesConfig(newLanguages, master ?? newLanguages.Values.First()); } - public LanguagesConfig Update(Language language, bool isOptional, IEnumerable fallback) + public LanguagesConfig Update(Language language, bool isOptional, bool isMaster, IEnumerable fallback) { ThrowIfNotFound(language); if (isOptional) { - ThrowIfMaster(language, () => $"Cannot cannot make language '{language.Iso2Code}' optional"); + ThrowIfMaster(language, isMaster, () => $"Cannot cannot make language '{language.Iso2Code}' optional"); } - var newLanguages = ValidateLanguages(languages.SetItem(language, new LanguageConfig(language, isOptional, fallback))); + var newLanguage = new LanguageConfig(language, isOptional, fallback); + var newLanguages = ValidateLanguages(languages.SetItem(language, newLanguage)); - return new LanguagesConfig(newLanguages, master); + return new LanguagesConfig(newLanguages, isMaster ? newLanguage : master); } public LanguagesConfig Remove(Language language) { ThrowIfNotFound(language); - ThrowIfMaster(language, () => $"Cannot remove language '{language.Iso2Code}'"); + ThrowIfMaster(language, false, () => $"Cannot remove language '{language.Iso2Code}'"); var newLanguages = languages.Remove(language); @@ -171,9 +172,9 @@ namespace Squidex.Core } } - private void ThrowIfMaster(Language language, Func message) + private void ThrowIfMaster(Language language, bool isMaster, Func message) { - if (master?.Language == language) + if (master?.Language == language || isMaster) { var error = new ValidationError("Language is the master language", "Language"); diff --git a/src/Squidex.Events/Apps/AppLanguageUpdated.cs b/src/Squidex.Events/Apps/AppLanguageUpdated.cs index 10b0afda0..3a2b5ed4c 100644 --- a/src/Squidex.Events/Apps/AppLanguageUpdated.cs +++ b/src/Squidex.Events/Apps/AppLanguageUpdated.cs @@ -18,6 +18,8 @@ namespace Squidex.Events.Apps public bool IsOptional { get; set; } + public bool IsMaster { get; set; } + public List Fallback { get; set; } } } diff --git a/src/Squidex.Events/Apps/AppMasterLanguageSet.cs b/src/Squidex.Events/Apps/AppMasterLanguageSet.cs index a0ae5b073..4407d2d51 100644 --- a/src/Squidex.Events/Apps/AppMasterLanguageSet.cs +++ b/src/Squidex.Events/Apps/AppMasterLanguageSet.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System; using Squidex.Infrastructure; namespace Squidex.Events.Apps diff --git a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs index 3c163beb2..f4180a4c3 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs @@ -54,9 +54,12 @@ namespace Squidex.Infrastructure.CQRS.Commands foreach (var storedEvent in events) { - var envelope = formatter.Parse(storedEvent.Data); + var envelope = TryParseEvent(storedEvent); - domainObject.ApplyEvent(envelope); + if (envelope != null) + { + domainObject.ApplyEvent(envelope); + } } if (expectedVersion != null && domainObject.Version != expectedVersion.Value) @@ -87,5 +90,17 @@ namespace Squidex.Infrastructure.CQRS.Commands throw new DomainObjectVersionException(domainObject.Id.ToString(), domainObject.GetType(), versionCurrent, versionExpected); } } + + private Envelope TryParseEvent(StoredEvent storedEvent) + { + try + { + return formatter.Parse(storedEvent.Data); + } + catch (TypeNameNotFoundException) + { + return null; + } + } } } diff --git a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs index c9bf4a866..08b169e32 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs @@ -218,7 +218,7 @@ namespace Squidex.Infrastructure.CQRS.Events return @event; } - catch (ArgumentException) + catch (TypeNameNotFoundException) { return null; } diff --git a/src/Squidex.Infrastructure/TypeNameNotFoundException.cs b/src/Squidex.Infrastructure/TypeNameNotFoundException.cs new file mode 100644 index 000000000..35c5db9ff --- /dev/null +++ b/src/Squidex.Infrastructure/TypeNameNotFoundException.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// TypeNameNotFoundException.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Infrastructure +{ + public class TypeNameNotFoundException : Exception + { + public TypeNameNotFoundException() + { + } + + public TypeNameNotFoundException(string message) + : base(message) + { + } + + public TypeNameNotFoundException(string message, Exception inner) + : base(message, inner) + { + } + } +} diff --git a/src/Squidex.Infrastructure/TypeNameRegistry.cs b/src/Squidex.Infrastructure/TypeNameRegistry.cs index 08457747d..24c099e04 100644 --- a/src/Squidex.Infrastructure/TypeNameRegistry.cs +++ b/src/Squidex.Infrastructure/TypeNameRegistry.cs @@ -90,7 +90,7 @@ namespace Squidex.Infrastructure if (result == null) { - throw new ArgumentException($"There is no name for type '{type}"); + throw new TypeNameNotFoundException($"There is no name for type '{type}"); } return result; @@ -102,7 +102,7 @@ namespace Squidex.Infrastructure if (result == null) { - throw new ArgumentException($"There is no type for name '{name}"); + throw new TypeNameNotFoundException($"There is no type for name '{name}"); } return result; diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs index 46cc40ff8..050b84bd9 100644 --- a/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs +++ b/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs @@ -22,7 +22,7 @@ namespace Squidex.Read.MongoDb.Apps { public sealed class MongoAppEntity : MongoEntity, IAppEntity { - private LanguagesConfig languagesConfig = LanguagesConfig.Empty; + private LanguagesConfig languagesConfig; [BsonRequired] [BsonElement] @@ -78,7 +78,14 @@ namespace Squidex.Read.MongoDb.Apps private LanguagesConfig CreateLanguagesConfig() { - return LanguagesConfig.Create(Languages.Select(ToLanguageConfig).ToList()).MakeMaster(MasterLanguage); + languagesConfig = LanguagesConfig.Create(Languages.Select(ToLanguageConfig).ToList()); + + if (MasterLanguage != null) + { + languagesConfig = languagesConfig.MakeMaster(MasterLanguage); + } + + return languagesConfig; } private static MongoAppLanguage FromLanguageConfig(LanguageConfig l) diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs index 952a2ffa7..8bcd87252 100644 --- a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs +++ b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs @@ -93,15 +93,7 @@ namespace Squidex.Read.MongoDb.Apps { return Collection.UpdateAsync(@event, headers, a => { - a.UpdateLanguages(c => c.Update(@event.Language, @event.IsOptional, @event.Fallback)); - }); - } - - protected Task On(AppMasterLanguageSet @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(@event, headers, a => - { - a.UpdateLanguages(c => c.MakeMaster(@event.Language)); + a.UpdateLanguages(c => c.Update(@event.Language, @event.IsOptional, @event.IsMaster, @event.Fallback)); }); } diff --git a/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs b/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs index e561c70e2..241ffe479 100644 --- a/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs +++ b/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs @@ -43,6 +43,9 @@ namespace Squidex.Read.Apps AddEventMessage( "removed language {[Language]}"); + AddEventMessage( + "updated language {[Language]}"); + AddEventMessage( "changed master language to {[Language]}"); } @@ -110,6 +113,15 @@ namespace Squidex.Read.Apps .AddParameter("Language", @event.Language)); } + protected Task On(AppLanguageUpdated @event, EnvelopeHeaders headers) + { + const string channel = "settings.languages"; + + return Task.FromResult( + ForEvent(@event, channel) + .AddParameter("Language", @event.Language)); + } + protected Task On(AppMasterLanguageSet @event, EnvelopeHeaders headers) { const string channel = "settings.languages"; diff --git a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs index b983bd71a..4bf422b87 100644 --- a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs +++ b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs @@ -107,6 +107,7 @@ namespace Squidex.Read.Apps.Services.Implementations @event.Payload is AppCreated || @event.Payload is AppLanguageAdded || @event.Payload is AppLanguageRemoved || + @event.Payload is AppLanguageUpdated || @event.Payload is AppMasterLanguageSet) { Remove(((AppEvent)@event.Payload).AppId); diff --git a/src/Squidex.Write/Apps/AppCommandHandler.cs b/src/Squidex.Write/Apps/AppCommandHandler.cs index 54fe46b6f..c449247a4 100644 --- a/src/Squidex.Write/Apps/AppCommandHandler.cs +++ b/src/Squidex.Write/Apps/AppCommandHandler.cs @@ -114,11 +114,6 @@ namespace Squidex.Write.Apps return handler.UpdateAsync(context, a => a.UpdateLanguage(command)); } - protected Task On(SetMasterLanguage command, CommandContext context) - { - return handler.UpdateAsync(context, a => a.SetMasterLanguage(command)); - } - public Task HandleAsync(CommandContext context) { return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context); diff --git a/src/Squidex.Write/Apps/AppDomainObject.cs b/src/Squidex.Write/Apps/AppDomainObject.cs index 12d27f499..1fbe9917c 100644 --- a/src/Squidex.Write/Apps/AppDomainObject.cs +++ b/src/Squidex.Write/Apps/AppDomainObject.cs @@ -88,12 +88,7 @@ namespace Squidex.Write.Apps protected void On(AppLanguageUpdated @event) { - languagesConfig = languagesConfig.Update(@event.Language, @event.IsOptional, @event.Fallback); - } - - protected void On(AppMasterLanguageSet @event) - { - languagesConfig = languagesConfig.MakeMaster(@event.Language); + languagesConfig = languagesConfig.Update(@event.Language, @event.IsOptional, @event.IsMaster, @event.Fallback); } protected override void DispatchEvent(Envelope @event) @@ -205,17 +200,6 @@ namespace Squidex.Write.Apps return this; } - public AppDomainObject SetMasterLanguage(SetMasterLanguage command) - { - Guard.Valid(command, nameof(command), () => "Cannot set master language"); - - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppMasterLanguageSet())); - - return this; - } - private void RaiseEvent(AppEvent @event) { if (@event.AppId == null) diff --git a/src/Squidex.Write/Apps/Commands/SetMasterLanguage.cs b/src/Squidex.Write/Apps/Commands/SetMasterLanguage.cs deleted file mode 100644 index 0b5c06409..000000000 --- a/src/Squidex.Write/Apps/Commands/SetMasterLanguage.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ========================================================================== -// SetMasterLanguage.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Collections.Generic; -using Squidex.Infrastructure; - -namespace Squidex.Write.Apps.Commands -{ - public sealed class SetMasterLanguage : AppAggregateCommand, IValidatable - { - public Language Language { get; set; } - - public void Validate(IList errors) - { - if (Language == null) - { - errors.Add(new ValidationError("Language cannot be null", nameof(Language))); - } - } - } -} diff --git a/src/Squidex.Write/Apps/Commands/UpdateLanguage.cs b/src/Squidex.Write/Apps/Commands/UpdateLanguage.cs index b1497a40c..65603b69c 100644 --- a/src/Squidex.Write/Apps/Commands/UpdateLanguage.cs +++ b/src/Squidex.Write/Apps/Commands/UpdateLanguage.cs @@ -17,6 +17,8 @@ namespace Squidex.Write.Apps.Commands public bool IsOptional { get; set; } + public bool IsMaster { get; set; } + public List Fallback { get; set; } public void Validate(IList errors) diff --git a/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs b/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs index 55ddad316..ea5b76c7b 100644 --- a/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs @@ -66,8 +66,9 @@ namespace Squidex.Controllers.Api.Apps new AppLanguageDto { IsMaster = x == entity.LanguagesConfig.Master, - IsOptional = x.IsOptional - })).ToList(); + IsOptional = x.IsOptional, + Fallback = x.Fallback.ToList() + })).OrderByDescending(x => x.IsMaster).ThenBy(x => x.Iso2Code).ToList(); Response.Headers["ETag"] = new StringValues(entity.Version.ToString()); @@ -114,13 +115,8 @@ namespace Squidex.Controllers.Api.Apps [Route("apps/{app}/languages/{language}")] public async Task Update(string app, string language, [FromBody] UpdateAppLanguageDto model) { - await CommandBus.PublishAsync(SimpleMapper.Map(model, new UpdateLanguage())); - - if (model.IsMaster == true) - { - await CommandBus.PublishAsync(new SetMasterLanguage { Language = ParseLanguage(language) }); - } - + await CommandBus.PublishAsync(SimpleMapper.Map(model, new UpdateLanguage { Language = language })); + return NoContent(); } diff --git a/src/Squidex/Controllers/Api/Apps/Models/AppLanguageDto.cs b/src/Squidex/Controllers/Api/Apps/Models/AppLanguageDto.cs index 97de2029d..b194cd01b 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/AppLanguageDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/AppLanguageDto.cs @@ -6,7 +6,9 @@ // All rights reserved. // ========================================================================== +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure; namespace Squidex.Controllers.Api.Apps.Models { @@ -24,6 +26,12 @@ namespace Squidex.Controllers.Api.Apps.Models [Required] public string EnglishName { get; set; } + /// + /// The fallback languages. + /// + [Required] + public List Fallback { get; set; } + /// /// Indicates if the language is the master language. /// diff --git a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html index f8b161d69..59452d34e 100644 --- a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html +++ b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html @@ -48,13 +48,13 @@ {{eventConsumer.lastHandledEventNumber}} - - - diff --git a/src/Squidex/app/features/administration/pages/users/users-page.component.html b/src/Squidex/app/features/administration/pages/users/users-page.component.html index 1cf3240e4..8354c1c19 100644 --- a/src/Squidex/app/features/administration/pages/users/users-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/users-page.component.html @@ -4,7 +4,7 @@
- @@ -64,10 +64,10 @@ - - @@ -82,10 +82,10 @@ diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.html b/src/Squidex/app/features/assets/pages/assets-page.component.html index f0e825dd3..cf933e6eb 100644 --- a/src/Squidex/app/features/assets/pages/assets-page.component.html +++ b/src/Squidex/app/features/assets/pages/assets-page.component.html @@ -4,7 +4,7 @@
- @@ -56,10 +56,10 @@ diff --git a/src/Squidex/app/features/content/pages/contents/content-item.component.html b/src/Squidex/app/features/content/pages/contents/content-item.component.html index 9cae38ff1..3bcc29e84 100644 --- a/src/Squidex/app/features/content/pages/contents/content-item.component.html +++ b/src/Squidex/app/features/content/pages/contents/content-item.component.html @@ -12,7 +12,7 @@