diff --git a/src/Squidex.Events/Apps/AppClientAttached.cs b/src/Squidex.Events/Apps/AppClientAttached.cs index 2528655e9..a26510767 100644 --- a/src/Squidex.Events/Apps/AppClientAttached.cs +++ b/src/Squidex.Events/Apps/AppClientAttached.cs @@ -15,9 +15,9 @@ namespace Squidex.Events.Apps [TypeName("AppClientAttachedEvent")] public sealed class AppClientAttached : IEvent { - public string ClientId { get; set; } + public string Id { get; set; } - public string ClientSecret { get; set; } + public string Secret { get; set; } public DateTime ExpiresUtc { get; set; } } diff --git a/src/Squidex.Events/Apps/AppClientRenamed.cs b/src/Squidex.Events/Apps/AppClientRenamed.cs index 73cdc423b..87ab285cb 100644 --- a/src/Squidex.Events/Apps/AppClientRenamed.cs +++ b/src/Squidex.Events/Apps/AppClientRenamed.cs @@ -14,7 +14,7 @@ namespace Squidex.Events.Apps [TypeName("AppClientRenamedEvent")] public sealed class AppClientRenamed : IEvent { - public string ClientId { get; set; } + public string Id { get; set; } public string Name { get; set; } } diff --git a/src/Squidex.Events/Apps/AppClientRevoked.cs b/src/Squidex.Events/Apps/AppClientRevoked.cs index b04b28e10..1ce890b80 100644 --- a/src/Squidex.Events/Apps/AppClientRevoked.cs +++ b/src/Squidex.Events/Apps/AppClientRevoked.cs @@ -14,6 +14,6 @@ namespace Squidex.Events.Apps [TypeName("AppClientRevokedEvent")] public sealed class AppClientRevoked : IEvent { - public string ClientId { get; set; } + public string Id { get; set; } } } diff --git a/src/Squidex.Events/Apps/AppLanguagesConfigured.cs b/src/Squidex.Events/Apps/AppLanguageAdded.cs similarity index 66% rename from src/Squidex.Events/Apps/AppLanguagesConfigured.cs rename to src/Squidex.Events/Apps/AppLanguageAdded.cs index 1261e47d5..12807bc64 100644 --- a/src/Squidex.Events/Apps/AppLanguagesConfigured.cs +++ b/src/Squidex.Events/Apps/AppLanguageAdded.cs @@ -1,20 +1,19 @@ // ========================================================================== -// AppLanguagesConfigured.cs +// AppLanguageAdded.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using System.Collections.Generic; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; namespace Squidex.Events.Apps { - [TypeName("AppLanguagesConfiguredEvent")] - public sealed class AppLanguagesConfigured : IEvent + [TypeName("AppLanguageAddedEvent")] + public sealed class AppLanguageAdded : IEvent { - public List Languages { get; set; } + public Language Language { get; set; } } } diff --git a/src/Squidex.Events/Apps/AppLanguageRemoved.cs b/src/Squidex.Events/Apps/AppLanguageRemoved.cs new file mode 100644 index 000000000..a234fd592 --- /dev/null +++ b/src/Squidex.Events/Apps/AppLanguageRemoved.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// AppLanguageRemoved.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; + +namespace Squidex.Events.Apps +{ + [TypeName("AppLanguageRemovedEvent")] + public sealed class AppLanguageRemoved : IEvent + { + public Language Language { get; set; } + } +} diff --git a/src/Squidex.Events/Apps/AppMasterLanguageSet.cs b/src/Squidex.Events/Apps/AppMasterLanguageSet.cs new file mode 100644 index 000000000..b87f14384 --- /dev/null +++ b/src/Squidex.Events/Apps/AppMasterLanguageSet.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// AppMasterLanguageSet.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; + +namespace Squidex.Events.Apps +{ + [TypeName("AppMasterLanguageSetEvent")] + public sealed class AppMasterLanguageSet : IEvent + { + public Language Language { get; set; } + } +} diff --git a/src/Squidex.Infrastructure/DomainObjectDeletedException.cs b/src/Squidex.Infrastructure/DomainObjectDeletedException.cs index 699ce8c50..17d27bdf0 100644 --- a/src/Squidex.Infrastructure/DomainObjectDeletedException.cs +++ b/src/Squidex.Infrastructure/DomainObjectDeletedException.cs @@ -19,7 +19,7 @@ namespace Squidex.Infrastructure private static string FormatMessage(string id, Type type) { - return $"Domain object \'{id}\' (type {type}) not deleted."; + return $"Domain object \'{id}\' (type {type}) already deleted."; } } } diff --git a/src/Squidex.Infrastructure/DomainObjectException.cs b/src/Squidex.Infrastructure/DomainObjectException.cs index eac709bfe..fcfdf2419 100644 --- a/src/Squidex.Infrastructure/DomainObjectException.cs +++ b/src/Squidex.Infrastructure/DomainObjectException.cs @@ -17,18 +17,12 @@ namespace Squidex.Infrastructure public string TypeName { - get - { - return typeName; - } + get { return typeName; } } public string Id { - get - { - return id; - } + get { return id; } } protected DomainObjectException(string message, string id, Type type) @@ -41,10 +35,7 @@ namespace Squidex.Infrastructure { this.id = id; - if (type != null) - { - typeName = type.Name; - } + typeName = type?.Name; } } } diff --git a/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs b/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs index 5fd6aaee1..450ca6963 100644 --- a/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs +++ b/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs @@ -17,9 +17,19 @@ namespace Squidex.Infrastructure { } + public DomainObjectNotFoundException(string id, string collection, Type type) + : base(FormatMessage(id, collection, type), id, type) + { + } + private static string FormatMessage(string id, Type type) { return $"Domain object \'{id}\' (type {type}) not found."; } + + private static string FormatMessage(string id, string collection, Type type) + { + return $"Domain object \'{id}\' not found on {type}.{collection}"; + } } } diff --git a/src/Squidex.Infrastructure/DomainObjectVersionException.cs b/src/Squidex.Infrastructure/DomainObjectVersionException.cs index 0dcde4c19..2f0abab08 100644 --- a/src/Squidex.Infrastructure/DomainObjectVersionException.cs +++ b/src/Squidex.Infrastructure/DomainObjectVersionException.cs @@ -17,18 +17,12 @@ namespace Squidex.Infrastructure public int CurrentVersion { - get - { - return currentVersion; - } + get { return currentVersion; } } public int ExpectedVersion { - get - { - return expectedVersion; - } + get { return expectedVersion; } } public DomainObjectVersionException(string id, Type type, int currentVersion, int expectedVersion) diff --git a/src/Squidex.Read/Apps/IAppClientEntity.cs b/src/Squidex.Read/Apps/IAppClientEntity.cs index 475a532fe..b009b11fb 100644 --- a/src/Squidex.Read/Apps/IAppClientEntity.cs +++ b/src/Squidex.Read/Apps/IAppClientEntity.cs @@ -12,11 +12,11 @@ namespace Squidex.Read.Apps { public interface IAppClientEntity { - string Name { get; } + string Id { get; } - string ClientId { get; } + string Name { get; } - string ClientSecret { get; } + string Secret { get; } DateTime ExpiresUtc { get; } } diff --git a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs index f52ee132a..dac9d46c3 100644 --- a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs +++ b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs @@ -69,7 +69,7 @@ namespace Squidex.Read.Apps.Services.Implementations @event.Payload is AppClientAttached || @event.Payload is AppClientRevoked || @event.Payload is AppClientRenamed || - @event.Payload is AppLanguagesConfigured) + @event.Payload is AppLanguageAdded) { var appName = Cache.Get(BuildNamesCacheKey(@event.Headers.AggregateId())); diff --git a/src/Squidex.Store.MongoDb/Apps/MongoAppClientEntity.cs b/src/Squidex.Store.MongoDb/Apps/MongoAppClientEntity.cs index de05a3f12..ec0fd4e30 100644 --- a/src/Squidex.Store.MongoDb/Apps/MongoAppClientEntity.cs +++ b/src/Squidex.Store.MongoDb/Apps/MongoAppClientEntity.cs @@ -16,11 +16,11 @@ namespace Squidex.Store.MongoDb.Apps { [BsonRequired] [BsonElement] - public string ClientId { get; set; } + public string Id { get; set; } [BsonRequired] [BsonElement] - public string ClientSecret { get; set; } + public string Secret { get; set; } [BsonRequired] [BsonElement] diff --git a/src/Squidex.Store.MongoDb/Apps/MongoAppRepository.cs b/src/Squidex.Store.MongoDb/Apps/MongoAppRepository.cs index 45594a869..2c59f7f63 100644 --- a/src/Squidex.Store.MongoDb/Apps/MongoAppRepository.cs +++ b/src/Squidex.Store.MongoDb/Apps/MongoAppRepository.cs @@ -71,19 +71,11 @@ namespace Squidex.Store.MongoDb.Apps }); } - protected Task On(AppLanguagesConfigured @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(headers, a => - { - a.Languages = @event.Languages.Select(x => x.Iso2Code).ToList(); - }); - } - protected Task On(AppClientAttached @event, EnvelopeHeaders headers) { return Collection.UpdateAsync(headers, a => { - a.Clients.Add(@event.ClientId, SimpleMapper.Map(@event, new MongoAppClientEntity())); + a.Clients.Add(@event.Id, SimpleMapper.Map(@event, new MongoAppClientEntity())); }); } @@ -91,7 +83,7 @@ namespace Squidex.Store.MongoDb.Apps { return Collection.UpdateAsync(headers, a => { - a.Clients.Remove(@event.ClientId); + a.Clients.Remove(@event.Id); }); } @@ -99,7 +91,7 @@ namespace Squidex.Store.MongoDb.Apps { return Collection.UpdateAsync(headers, a => { - a.Clients[@event.ClientId].Name = @event.Name; + a.Clients[@event.Id].Name = @event.Name; }); } diff --git a/src/Squidex.Write/Apps/AppClient.cs b/src/Squidex.Write/Apps/AppClient.cs index 5a9d504c3..15c6f1761 100644 --- a/src/Squidex.Write/Apps/AppClient.cs +++ b/src/Squidex.Write/Apps/AppClient.cs @@ -13,33 +13,45 @@ namespace Squidex.Write.Apps { public sealed class AppClient { - private string name; + private readonly string name; + private readonly string id; + private readonly string secret; + private readonly DateTime expiresUtc; - public string ClientId { get; } + public string Id + { + get { return id; } + } - public string ClientSecret { get; } + public string Name + { + get { return name ?? Id; } + } - public DateTime ExpiresUtc { get; } + public string Secret + { + get { return secret; } + } - public string Name + public DateTime ExpiresUtc { - get { return name ?? ClientId; } + get { return expiresUtc; } } - public AppClient(string id, string secret, DateTime expiresUtc) + public AppClient(string id, string secret, DateTime expiresUtc, string name = null) { Guard.NotNullOrEmpty(id, nameof(id)); Guard.NotNullOrEmpty(secret, nameof(secret)); - ClientId = id; - ClientSecret = secret; - - ExpiresUtc = expiresUtc; + this.id = id; + this.name = name; + this.secret = secret; + this.expiresUtc = expiresUtc; } - public void Rename(string newName) + public AppClient Rename(string newName) { - name = newName; + return new AppClient(Id, Secret, ExpiresUtc, newName); } } } diff --git a/src/Squidex.Write/Apps/AppClients.cs b/src/Squidex.Write/Apps/AppClients.cs new file mode 100644 index 000000000..849ca315e --- /dev/null +++ b/src/Squidex.Write/Apps/AppClients.cs @@ -0,0 +1,79 @@ +// ========================================================================== +// AppClients.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Infrastructure; +// ReSharper disable InvertIf + +namespace Squidex.Write.Apps +{ + public class AppClients + { + private readonly Dictionary clients = new Dictionary(); + + public IReadOnlyDictionary Clients + { + get { return clients; } + } + + public void Add(string id, string secret, DateTime expires) + { + Func message = () => "Cannot add client"; + + ThrowIfFound(id, message); + + clients[id] = new AppClient(id, secret, expires); + } + + public void Rename(string clientId, string name) + { + Func message = () => "Cannot rename client"; + + ThrowIfNotFound(clientId); + ThrowIfSameName(clientId, name, message); + + clients[clientId] = clients[clientId].Rename(name); + } + + public void Revoke(string clientId) + { + ThrowIfNotFound(clientId); + + clients.Remove(clientId); + } + + private void ThrowIfNotFound(string clientId) + { + if (!clients.ContainsKey(clientId)) + { + throw new DomainObjectNotFoundException(clientId, "Contributors", typeof(AppDomainObject)); + } + } + + private void ThrowIfFound(string clientId, Func message) + { + if (clients.ContainsKey(clientId)) + { + var error = new ValidationError("Client id is alreay part of the app", "Id"); + + throw new ValidationException(message(), error); + } + } + + private void ThrowIfSameName(string clientId, string name, Func message) + { + if (string.Equals(clients[clientId].Name, name)) + { + var error = new ValidationError("Client already has the name", "Id"); + + throw new ValidationException(message(), error); + } + } + } +} diff --git a/src/Squidex.Write/Apps/AppCommandHandler.cs b/src/Squidex.Write/Apps/AppCommandHandler.cs index 58c6445bf..aeebed016 100644 --- a/src/Squidex.Write/Apps/AppCommandHandler.cs +++ b/src/Squidex.Write/Apps/AppCommandHandler.cs @@ -80,9 +80,9 @@ namespace Squidex.Write.Apps { return handler.UpdateAsync(command, x => { - x.AttachClient(command, keyGenerator.GenerateKey()); + x.AttachClient(command, keyGenerator.GenerateKey(), command.Timestamp.AddYears(1)); - context.Succeed(x.Clients[command.ClientId]); + context.Succeed(x.Clients[command.Id]); }); } @@ -100,12 +100,7 @@ namespace Squidex.Write.Apps { return handler.UpdateAsync(command, x => x.RevokeClient(command)); } - - protected Task On(ConfigureLanguages command, CommandContext context) - { - return handler.UpdateAsync(command, x => x.ConfigureLanguages(command)); - } - + public Task HandleAsync(CommandContext context) { return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context); diff --git a/src/Squidex.Write/Apps/AppContributors.cs b/src/Squidex.Write/Apps/AppContributors.cs new file mode 100644 index 000000000..d8eae44a3 --- /dev/null +++ b/src/Squidex.Write/Apps/AppContributors.cs @@ -0,0 +1,77 @@ +// ========================================================================== +// AppContributors.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Squidex.Core.Apps; +using Squidex.Infrastructure; + +// ReSharper disable InvertIf + +namespace Squidex.Write.Apps +{ + public class AppContributors + { + private readonly Dictionary contributors = new Dictionary(); + + public void Assign(string contributorId, PermissionLevel permission) + { + Func message = () => "Cannot assign contributor"; + + ThrowIfFound(contributorId, message); + ThrowIfNoOwner(c => c[contributorId] = permission, message); + + contributors[contributorId] = permission; + } + + public void Remove(string contributorId) + { + Func message = () => "Cannot remove contributor"; + + ThrowIfNotFound(contributorId); + ThrowIfNoOwner(c => c.Remove(contributorId), message); + + contributors.Remove(contributorId); + } + + private void ThrowIfNotFound(string contributorId) + { + if (!contributors.ContainsKey(contributorId)) + { + throw new DomainObjectNotFoundException(contributorId, "Contributors", typeof(AppDomainObject)); + } + } + + private void ThrowIfFound(string contributorId, Func message) + { + PermissionLevel currentPermission; + + if (contributors.TryGetValue(contributorId, out currentPermission)) + { + var error = new ValidationError("Contributor is already part of the app with same permissions", "ContributorId"); + + throw new ValidationException(message(), error); + } + } + + private void ThrowIfNoOwner(Action> change, Func message) + { + var contributorsCopy = new Dictionary(contributors); + + change(contributorsCopy); + + if (contributorsCopy.All(x => x.Value != PermissionLevel.Owner)) + { + var error = new ValidationError("Contributor is the last owner", "ContributorId"); + + throw new ValidationException(message(), error); + } + } + } +} diff --git a/src/Squidex.Write/Apps/AppDomainObject.cs b/src/Squidex.Write/Apps/AppDomainObject.cs index 257d81b4d..92d4a1778 100644 --- a/src/Squidex.Write/Apps/AppDomainObject.cs +++ b/src/Squidex.Write/Apps/AppDomainObject.cs @@ -15,7 +15,6 @@ using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Dispatching; using Squidex.Write.Apps.Commands; using System.Collections.Generic; -using System.Linq; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Reflection; // ReSharper disable InvertIf @@ -24,9 +23,10 @@ namespace Squidex.Write.Apps { public sealed class AppDomainObject : DomainObject { - private static readonly List DefaultLanguages = new List { Language.GetLanguage("en") }; - private readonly Dictionary clients = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary contributors = new Dictionary(); + private static readonly Language DefaultLanguage = Language.GetLanguage("en"); + private readonly AppContributors contributors = new AppContributors(); + private readonly AppLanguages languages = new AppLanguages(); + private readonly AppClients clients = new AppClients(); private string name; public string Name @@ -34,14 +34,9 @@ namespace Squidex.Write.Apps get { return name; } } - public IReadOnlyDictionary Contributors - { - get { return contributors; } - } - public IReadOnlyDictionary Clients { - get { return clients; } + get { return clients.Clients; } } public AppDomainObject(Guid id, int version) @@ -56,7 +51,7 @@ namespace Squidex.Write.Apps public void On(AppContributorAssigned @event) { - contributors[@event.ContributorId] = @event.Permission; + contributors.Assign(@event.ContributorId, @event.Permission); } public void On(AppContributorRemoved @event) @@ -66,17 +61,32 @@ namespace Squidex.Write.Apps public void On(AppClientAttached @event) { - clients.Add(@event.ClientId, new AppClient(@event.ClientId, @event.ClientSecret, @event.ExpiresUtc)); + clients.Add(@event.Id, @event.Secret, @event.ExpiresUtc); + } + + public void On(AppClientRenamed @event) + { + clients.Rename(@event.Id, @event.Name); } public void On(AppClientRevoked @event) { - clients.Remove(@event.ClientId); + clients.Revoke(@event.Id); } - public void On(AppClientRenamed @event) + public void On(AppLanguageAdded @event) { - clients[@event.ClientId].Rename(@event.Name); + languages.Add(@event.Language); + } + + public void On(AppLanguageRemoved @event) + { + languages.Remove(@event.Language); + } + + public void On(AppMasterLanguageSet @event) + { + languages.SetMasterLanguage(@event.Language); } protected override void DispatchEvent(Envelope @event) @@ -86,9 +96,7 @@ namespace Squidex.Write.Apps public AppDomainObject Create(CreateApp command) { - Func message = () => "Cannot create app"; - - Guard.Valid(command, nameof(command), message); + Guard.Valid(command, nameof(command), () => "Cannot create app"); ThrowIfCreated(); @@ -96,32 +104,49 @@ namespace Squidex.Write.Apps RaiseEvent(CreateInitialOwner(command)); RaiseEvent(CreateInitialLanguage()); + RaiseEvent(CreateInitialMasterLanguage()); return this; } public AppDomainObject AssignContributor(AssignContributor command) { - Func message = () => "Cannot assign contributor"; - - Guard.Valid(command, nameof(command), message); + Guard.Valid(command, nameof(command), () => "Cannot assign contributor"); ThrowIfNotCreated(); - ThrowIfNoOwner(c => c[command.ContributorId] = command.Permission, message); RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned())); return this; } - public AppDomainObject RenameClient(RenameClient command) + public AppDomainObject RemoveContributor(RemoveContributor command) + { + Guard.Valid(command, nameof(command), () => "Cannot remove contributor"); + + ThrowIfNotCreated(); + + RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved())); + + return this; + } + + public AppDomainObject AttachClient(AttachClient command, string secret, DateTime expiresUtc) { - Func message = () => "Cannot rename client"; + Guard.Valid(command, nameof(command), () => "Cannot attach client"); - Guard.Valid(command, nameof(command), message); + ThrowIfNotCreated(); + + RaiseEvent(SimpleMapper.Map(command, new AppClientAttached { Secret = secret, ExpiresUtc = expiresUtc })); + + return this; + } + + public AppDomainObject RenameClient(RenameClient command) + { + Guard.Valid(command, nameof(command), () => "Cannot rename client"); ThrowIfNotCreated(); - ThrowIfClientNotFound(command.ClientId, message); RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed())); @@ -130,66 +155,56 @@ namespace Squidex.Write.Apps public AppDomainObject RevokeClient(RevokeClient command) { - Func message = () => "Cannot revoke client"; - - Guard.Valid(command, nameof(command), message); + Guard.Valid(command, nameof(command), () => "Cannot revoke client"); ThrowIfNotCreated(); - ThrowIfClientNotFound(command.ClientId, message); RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked())); return this; } - public AppDomainObject AttachClient(AttachClient command, string secret) + public AppDomainObject AddLanguage(AddLanguage command) { - Func message = () => "Cannot attach client"; - - Guard.Valid(command, nameof(command), () => "Cannot attach client"); + Guard.Valid(command, nameof(command), () => "Cannot add language"); ThrowIfNotCreated(); - ThrowIfClientFound(command.ClientId, message); - - var expire = command.Timestamp.AddYears(1); - RaiseEvent(SimpleMapper.Map(command, new AppClientAttached { ClientSecret = secret, ExpiresUtc = expire })); + RaiseEvent(SimpleMapper.Map(command, new AppLanguageAdded())); return this; } - public AppDomainObject RemoveContributor(RemoveContributor command) + public AppDomainObject RemoveLanguage(RemoveLanguage command) { - Func message = () => "Cannot remove contributor"; - - Guard.Valid(command, nameof(command), () => "Cannot remove contributor"); + Guard.Valid(command, nameof(command), () => "Cannot remove language"); ThrowIfNotCreated(); - ThrowIfContributorNotFound(command.ContributorId, message); - - ThrowIfNoOwner(c => c.Remove(command.ContributorId), message); - RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved())); + RaiseEvent(SimpleMapper.Map(command, new AppLanguageRemoved())); return this; } - public AppDomainObject ConfigureLanguages(ConfigureLanguages command) + public AppDomainObject SetMasterLanguage(SetMasterLanguage command) { - Func message = () => "Cannot configure languages"; - - Guard.Valid(command, nameof(command), message); + Guard.Valid(command, nameof(command), () => "Cannot set master language"); ThrowIfNotCreated(); - RaiseEvent(SimpleMapper.Map(command, new AppLanguagesConfigured())); + RaiseEvent(SimpleMapper.Map(command, new AppMasterLanguageSet())); return this; } - private static AppLanguagesConfigured CreateInitialLanguage() + private static AppLanguageAdded CreateInitialLanguage() { - return new AppLanguagesConfigured { Languages = DefaultLanguages }; + return new AppLanguageAdded { Language = DefaultLanguage }; + } + + private static AppMasterLanguageSet CreateInitialMasterLanguage() + { + return new AppMasterLanguageSet { Language = DefaultLanguage }; } private static AppContributorAssigned CreateInitialOwner(IUserCommand command) @@ -212,49 +227,5 @@ namespace Squidex.Write.Apps throw new DomainException("App has already been created."); } } - - private void ThrowIfClientFound(string clientId, Func message) - { - if (clients.ContainsKey(clientId)) - { - var error = new ValidationError("Client id is alreay part of the app", "ClientName"); - - throw new ValidationException(message(), error); - } - } - - private void ThrowIfClientNotFound(string clientId, Func message) - { - if (!clients.ContainsKey(clientId)) - { - var error = new ValidationError("Client is not part of the app", "ClientName"); - - throw new ValidationException(message(), error); - } - } - - private void ThrowIfContributorNotFound(string contributorId, Func message) - { - if (!contributors.ContainsKey(contributorId)) - { - var error = new ValidationError("Contributor is not part of the app", "ContributorId"); - - throw new ValidationException(message(), error); - } - } - - private void ThrowIfNoOwner(Action> change, Func message) - { - var contributorsCopy = new Dictionary(contributors); - - change(contributorsCopy); - - if (contributorsCopy.All(x => x.Value != PermissionLevel.Owner)) - { - var error = new ValidationError("Contributor is the last owner", "ContributorId"); - - throw new ValidationException(message(), error); - } - } } } diff --git a/src/Squidex.Write/Apps/AppLanguages.cs b/src/Squidex.Write/Apps/AppLanguages.cs new file mode 100644 index 000000000..446d2e7ef --- /dev/null +++ b/src/Squidex.Write/Apps/AppLanguages.cs @@ -0,0 +1,83 @@ +// ========================================================================== +// AppLanguages.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Infrastructure; +// ReSharper disable InvertIf + +namespace Squidex.Write.Apps +{ + public class AppLanguages + { + private readonly HashSet languages = new HashSet(); + private Language masterLanguage; + + public IReadOnlyCollection Languages + { + get { return languages; } + } + + public void Add(Language language) + { + Func message = () => "Cannot add language"; + + ThrowIfFound(language, message); + + languages.Add(language); + } + + public void Remove(Language language) + { + Func message = () => "Cannot remove language"; + + ThrowIfNotFound(language); + ThrowIfMasterLanguage(language, message); + + languages.Remove(language); + } + + public void SetMasterLanguage(Language language) + { + Func message = () => "Cannot set master language"; + + ThrowIfNotFound(language); + ThrowIfMasterLanguage(language, message); + + masterLanguage = language; + } + + private void ThrowIfNotFound(Language language) + { + if (!languages.Contains(language)) + { + throw new DomainObjectNotFoundException(language.Iso2Code, "Languages", typeof(AppDomainObject)); + } + } + + private void ThrowIfFound(Language language, Func message) + { + if (languages.Contains(language)) + { + var error = new ValidationError("Language id is alreay part of the app", "Language"); + + throw new ValidationException(message(), error); + } + } + + private void ThrowIfMasterLanguage(Language language, Func message) + { + if (masterLanguage != null && masterLanguage.Equals(language)) + { + var error = new ValidationError("Language is the master language", "Language"); + + throw new ValidationException(message(), error); + } + } + } +} diff --git a/src/Squidex.Write/Apps/Commands/AddLanguage.cs b/src/Squidex.Write/Apps/Commands/AddLanguage.cs new file mode 100644 index 000000000..33b58bcdd --- /dev/null +++ b/src/Squidex.Write/Apps/Commands/AddLanguage.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// AddLanguage.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 AddLanguage : 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/AttachClient.cs b/src/Squidex.Write/Apps/Commands/AttachClient.cs index bf3ef274b..dd1ed8b36 100644 --- a/src/Squidex.Write/Apps/Commands/AttachClient.cs +++ b/src/Squidex.Write/Apps/Commands/AttachClient.cs @@ -15,15 +15,15 @@ namespace Squidex.Write.Apps.Commands { public sealed class AttachClient : AppAggregateCommand, ITimestampCommand, IValidatable { - public string ClientId { get; set; } + public string Id { get; set; } public DateTime Timestamp { get; set; } public void Validate(IList errors) { - if (!ClientId.IsSlug()) + if (!Id.IsSlug()) { - errors.Add(new ValidationError("Client id must be a valid slug", nameof(ClientId))); + errors.Add(new ValidationError("Client id must be a valid slug", nameof(Id))); } } } diff --git a/src/Squidex.Write/Apps/Commands/RemoveLanguage.cs b/src/Squidex.Write/Apps/Commands/RemoveLanguage.cs new file mode 100644 index 000000000..54a53e8f8 --- /dev/null +++ b/src/Squidex.Write/Apps/Commands/RemoveLanguage.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// RemoveLanguage.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 RemoveLanguage : 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/RenameClient.cs b/src/Squidex.Write/Apps/Commands/RenameClient.cs index ae3f171e8..c1a291a05 100644 --- a/src/Squidex.Write/Apps/Commands/RenameClient.cs +++ b/src/Squidex.Write/Apps/Commands/RenameClient.cs @@ -13,7 +13,7 @@ namespace Squidex.Write.Apps.Commands { public class RenameClient : AppAggregateCommand, IValidatable { - public string ClientId { get; set; } + public string Id { get; set; } public string Name { get; set; } @@ -24,9 +24,9 @@ namespace Squidex.Write.Apps.Commands errors.Add(new ValidationError("Name cannot be null or empty", nameof(Name))); } - if (!ClientId.IsSlug()) + if (!Id.IsSlug()) { - errors.Add(new ValidationError("Client id must be a valid slug", nameof(ClientId))); + errors.Add(new ValidationError("Client id must be a valid slug", nameof(Id))); } } } diff --git a/src/Squidex.Write/Apps/Commands/RevokeClient.cs b/src/Squidex.Write/Apps/Commands/RevokeClient.cs index 60033c00a..7221f34d8 100644 --- a/src/Squidex.Write/Apps/Commands/RevokeClient.cs +++ b/src/Squidex.Write/Apps/Commands/RevokeClient.cs @@ -13,13 +13,13 @@ namespace Squidex.Write.Apps.Commands { public class RevokeClient : AppAggregateCommand, IValidatable { - public string ClientId { get; set; } + public string Id { get; set; } public void Validate(IList errors) { - if (!ClientId.IsSlug()) + if (!Id.IsSlug()) { - errors.Add(new ValidationError("Client id must be a valid slug", nameof(ClientId))); + errors.Add(new ValidationError("Client id must be a valid slug", nameof(Id))); } } } diff --git a/src/Squidex.Write/Apps/Commands/SetMasterLanguage.cs b/src/Squidex.Write/Apps/Commands/SetMasterLanguage.cs new file mode 100644 index 000000000..0b5c06409 --- /dev/null +++ b/src/Squidex.Write/Apps/Commands/SetMasterLanguage.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// 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/Config/Identity/LazyClientStore.cs b/src/Squidex/Config/Identity/LazyClientStore.cs index f352b58cf..afa4cf4f7 100644 --- a/src/Squidex/Config/Identity/LazyClientStore.cs +++ b/src/Squidex/Config/Identity/LazyClientStore.cs @@ -52,7 +52,7 @@ namespace Squidex.Config.Identity var app = await appProvider.FindAppByNameAsync(token[0]); - var appClient = app?.Clients.FirstOrDefault(x => x.ClientId == token[1]); + var appClient = app?.Clients.FirstOrDefault(x => x.Id == token[1]); if (appClient == null) { @@ -71,7 +71,7 @@ namespace Squidex.Config.Identity { ClientId = id, ClientName = id, - ClientSecrets = new List { new Secret(appClient.ClientSecret.Sha512(), appClient.ExpiresUtc) }, + ClientSecrets = new List { new Secret(appClient.Secret.Sha512(), appClient.ExpiresUtc) }, AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = new List diff --git a/src/Squidex/Controllers/Api/Apps/AppClientsController.cs b/src/Squidex/Controllers/Api/Apps/AppClientsController.cs index 1dfc92f98..ccc2cfd8b 100644 --- a/src/Squidex/Controllers/Api/Apps/AppClientsController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppClientsController.cs @@ -96,7 +96,7 @@ namespace Squidex.Controllers.Api.Apps /// Updates an app client. /// /// The name of the app. - /// The id of the client that must be updated. + /// The id of the client that must be updated. /// Client object that needs to be added to the app. /// /// 201 => Client key generated. @@ -105,7 +105,7 @@ namespace Squidex.Controllers.Api.Apps [HttpPut] [Route("apps/{app}/clients/{client}/")] [ProducesResponseType(typeof(ClientDto[]), 201)] - public async Task PutClient(string app, string client, [FromBody] RenameClientDto request) + public async Task PutClient(string app, string clientId, [FromBody] RenameClientDto request) { await CommandBus.PublishAsync(SimpleMapper.Map(request, new RenameClient())); @@ -116,16 +116,16 @@ namespace Squidex.Controllers.Api.Apps /// Revoke an app client /// /// The name of the app. - /// The id of the client that must be deleted. + /// The id of the client that must be deleted. /// /// 404 => App not found or client not found. /// 204 => Client revoked. /// [HttpDelete] [Route("apps/{app}/clients/{client}/")] - public async Task DeleteClient(string app, string client) + public async Task DeleteClient(string app, string clientId) { - await CommandBus.PublishAsync(new RevokeClient { ClientId = client }); + await CommandBus.PublishAsync(new RevokeClient { Id = clientId }); return NoContent(); } diff --git a/src/Squidex/Controllers/Api/Apps/Models/AttachClientDto.cs b/src/Squidex/Controllers/Api/Apps/Models/AttachClientDto.cs index 99a98dc65..9c2fde7ff 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/AttachClientDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/AttachClientDto.cs @@ -17,6 +17,6 @@ namespace Squidex.Controllers.Api.Apps.Models /// [Required] [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] - public string ClientId { get; set; } + public string Id { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs b/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs index 01888648b..32ec5b5be 100644 --- a/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs +++ b/src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs @@ -17,24 +17,24 @@ namespace Squidex.Controllers.Api.Apps.Models /// The client id. /// [Required] - public string ClientId { get; set; } + public string Id { get; set; } /// /// The client secret. /// [Required] - public string ClientSecret { get; set; } + public string Secret { get; set; } /// - /// The date and time when the client key expires. + /// The client name. /// [Required] - public DateTime ExpiresUtc { get; set; } + public string Name { get; set; } /// - /// The client name. + /// The date and time when the client key expires. /// [Required] - public string Name { get; set; } + public DateTime ExpiresUtc { get; set; } } } diff --git a/src/Squidex/Pipeline/AppFilterAttribute.cs b/src/Squidex/Pipeline/AppFilterAttribute.cs index 4dbe0aa84..9572a6973 100644 --- a/src/Squidex/Pipeline/AppFilterAttribute.cs +++ b/src/Squidex/Pipeline/AppFilterAttribute.cs @@ -77,7 +77,7 @@ namespace Squidex.Pipeline clientId = clientId.Split(':')[0]; - var contributor = app.Clients.FirstOrDefault(x => string.Equals(x.ClientId, clientId, StringComparison.OrdinalIgnoreCase)); + var contributor = app.Clients.FirstOrDefault(x => string.Equals(x.Id, clientId, StringComparison.OrdinalIgnoreCase)); return contributor != null ? PermissionLevel.Owner : PermissionLevel.Editor; } diff --git a/src/Squidex/app/components/internal/app/settings/clients-page.component.html b/src/Squidex/app/components/internal/app/settings/clients-page.component.html index 4da97c151..6587659ed 100644 --- a/src/Squidex/app/components/internal/app/settings/clients-page.component.html +++ b/src/Squidex/app/components/internal/app/settings/clients-page.component.html @@ -37,7 +37,7 @@ Client Secret: - + diff --git a/src/Squidex/app/components/internal/app/settings/clients-page.component.ts b/src/Squidex/app/components/internal/app/settings/clients-page.component.ts index e011a6145..eb0aff4a3 100644 --- a/src/Squidex/app/components/internal/app/settings/clients-page.component.ts +++ b/src/Squidex/app/components/internal/app/settings/clients-page.component.ts @@ -82,11 +82,11 @@ export class ClientsPageComponent implements Ng2.OnInit { } public fullAppName(client: AppClientDto): string { - return this.appName + ':' + client.clientName; + return this.appName + ':' + client.id; } public revokeClient(client: AppClientDto) { - this.appClientsService.deleteClient(this.appName, client.clientName) + this.appClientsService.deleteClient(this.appName, client.id) .subscribe(() => { this.appClients.splice(this.appClients.indexOf(client), 1); }, error => { diff --git a/src/Squidex/app/shared/services/app-clients.service.spec.ts b/src/Squidex/app/shared/services/app-clients.service.spec.ts index 9128d6c06..b9469d1a3 100644 --- a/src/Squidex/app/shared/services/app-clients.service.spec.ts +++ b/src/Squidex/app/shared/services/app-clients.service.spec.ts @@ -34,12 +34,14 @@ describe('AppClientsService', () => { new Ng2Http.Response( new Ng2Http.ResponseOptions({ body: [{ - clientName: 'client1', - clientSecret: 'secret1', + id: 'client1', + name: 'Client1', + secret: 'secret1', expiresUtc: '2016-12-12T10:10' }, { - clientName: 'client2', - clientSecret: 'secret2', + id: 'client2', + name: 'Client2', + secret: 'secret2', expiresUtc: '2016-11-11T10:10' }] }) @@ -55,8 +57,8 @@ describe('AppClientsService', () => { expect(clients).toEqual( [ - new AppClientDto('client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10')), - new AppClientDto('client2', 'secret2', DateTime.parseISO_UTC('2016-11-11T10:10')), + new AppClientDto('client1', 'Client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10')), + new AppClientDto('client2', 'Client2', 'secret2', DateTime.parseISO_UTC('2016-11-11T10:10')), ]); authService.verifyAll(); @@ -70,8 +72,9 @@ describe('AppClientsService', () => { new Ng2Http.Response( new Ng2Http.ResponseOptions({ body: { - clientName: 'client1', - clientSecret: 'secret1', + id: 'client1', + name: 'Client1', + secret: 'secret1', expiresUtc: '2016-12-12T10:10' } }) @@ -86,7 +89,7 @@ describe('AppClientsService', () => { }); expect(client).toEqual( - new AppClientDto('client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10'))); + new AppClientDto('client1', 'Client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10'))); authService.verifyAll(); }); diff --git a/src/Squidex/app/shared/services/app-clients.service.ts b/src/Squidex/app/shared/services/app-clients.service.ts index 33070159b..3c6c5a8cc 100644 --- a/src/Squidex/app/shared/services/app-clients.service.ts +++ b/src/Squidex/app/shared/services/app-clients.service.ts @@ -15,8 +15,9 @@ import { AuthService } from './auth.service'; export class AppClientDto { constructor( - public readonly clientName: string, - public readonly clientSecret: string, + public readonly id: string, + public readonly name: string, + public readonly secret: string, public readonly expiresUtc: DateTime ) { } @@ -24,7 +25,7 @@ export class AppClientDto { export class AppClientCreateDto { constructor( - public readonly clientName: string + public readonly id: string ) { } } @@ -53,7 +54,7 @@ export class AppClientsService { const items: any[] = response; return items.map(item => { - return new AppClientDto(item.clientName, item.clientSecret, DateTime.parseISO_UTC(item.expiresUtc)); + return new AppClientDto(item.id, item.name, item.secret, DateTime.parseISO_UTC(item.expiresUtc)); }); }); } @@ -61,7 +62,7 @@ export class AppClientsService { public postClient(appName: string, client: AppClientCreateDto): Observable { return this.authService.authPost(this.apiUrl.buildUrl(`api/apps/${appName}/clients`), client) .map(response => response.json()) - .map(response => new AppClientDto(response.clientName, response.clientSecret, DateTime.parseISO_UTC(response.expiresUtc))) + .map(response => new AppClientDto(response.id, response.name, response.secret, DateTime.parseISO_UTC(response.expiresUtc))) .catch(response => { if (response.status === 400) { return Observable.throw('A client with the same name already exists.'); @@ -82,7 +83,7 @@ export class AppClientsService { }) }); - const body = `grant_type=client_credentials&scope=squidex-api&client_id=${appName}:${client.clientName}&client_secret=${client.clientSecret}`; + const body = `grant_type=client_credentials&scope=squidex-api&client_id=${appName}:${client.id}&client_secret=${client.secret}`; return this.http.post(this.apiUrl.buildUrl('identity-server/connect/token'), body, options) .map(response => response.json()) diff --git a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs index 553a40725..8f2ae246a 100644 --- a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs +++ b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs @@ -34,6 +34,7 @@ namespace Squidex.Write.Apps private readonly AppCommandHandler sut; private readonly AppDomainObject app; private readonly UserToken subjectId = new UserToken("subject", Guid.NewGuid().ToString()); + private readonly DateTime expiresUtc = DateTime.UtcNow.AddYears(1); private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientName = "client"; @@ -77,20 +78,6 @@ namespace Squidex.Write.Apps Assert.Equal(command.AggregateId, context.Result()); } - - [Fact] - public async Task ConfigureLanguages_should_update_domain_object() - { - CreateApp(); - - var command = new ConfigureLanguages { AggregateId = Id, Languages = new List { Language.GetLanguage("de") } }; - var context = new CommandContext(command); - - await TestUpdate(app, async _ => - { - await sut.HandleAsync(context); - }); - } [Fact] public async Task AssignContributor_should_throw_if_user_not_found() @@ -164,7 +151,7 @@ namespace Squidex.Write.Apps var timestamp = DateTime.Today; - var command = new AttachClient { ClientId = clientName, AggregateId = Id, Timestamp = timestamp }; + var command = new AttachClient { Id = clientName, AggregateId = Id, Timestamp = timestamp }; var context = new CommandContext(command); await TestUpdate(app, async _ => @@ -182,9 +169,9 @@ namespace Squidex.Write.Apps public async Task RenameClient_should_update_domain_object() { CreateApp() - .AttachClient(new AttachClient { ClientId = clientName }, clientSecret); + .AttachClient(new AttachClient { Id = clientName }, clientSecret, expiresUtc); - var command = new RenameClient { AggregateId = Id, ClientId = clientName, Name = "New Name" }; + var command = new RenameClient { AggregateId = Id, Id = clientName, Name = "New Name" }; var context = new CommandContext(command); await TestUpdate(app, async _ => @@ -197,9 +184,9 @@ namespace Squidex.Write.Apps public async Task RevokeClient_should_update_domain_object() { CreateApp() - .AttachClient(new AttachClient { ClientId = clientName }, clientSecret); + .AttachClient(new AttachClient { Id = clientName }, clientSecret, expiresUtc); - var command = new RevokeClient { AggregateId = Id, ClientId = clientName }; + var command = new RevokeClient { AggregateId = Id, Id = clientName }; var context = new CommandContext(command); await TestUpdate(app, async _ => diff --git a/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs index aa31dbbd9..795232050 100644 --- a/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs @@ -7,7 +7,6 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.Linq; using FluentAssertions; using Squidex.Core.Apps; @@ -27,11 +26,11 @@ namespace Squidex.Write.Apps private const string TestName = "app"; private readonly AppDomainObject sut; private readonly UserToken user = new UserToken("subject", Guid.NewGuid().ToString()); + private readonly DateTime expiresUtc = DateTime.UtcNow.AddYears(1); private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientId = "client"; private readonly string clientNewName = "My Client"; - private readonly List languages = new List { Language.GetLanguage("de") }; public AppDomainObjectTests() { @@ -58,7 +57,6 @@ namespace Squidex.Write.Apps sut.Create(new CreateApp { Name = TestName, User = user }); Assert.Equal(TestName, sut.Name); - Assert.Equal(PermissionLevel.Owner, sut.Contributors[user.Identifier]); sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() .ShouldBeEquivalentTo( @@ -66,7 +64,8 @@ namespace Squidex.Write.Apps { new AppCreated { Name = TestName }, new AppContributorAssigned { ContributorId = user.Identifier, Permission = PermissionLevel.Owner }, - new AppLanguagesConfigured { Languages= new List { Language.GetLanguage("en") } } + new AppLanguageAdded { Language = Language.GetLanguage("en") }, + new AppMasterLanguageSet { Language = Language.GetLanguage("en") } }); } @@ -97,8 +96,6 @@ namespace Squidex.Write.Apps sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor }); - Assert.Equal(PermissionLevel.Editor, sut.Contributors[contributorId]); - sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() .ShouldBeEquivalentTo( new IEvent[] @@ -132,9 +129,7 @@ namespace Squidex.Write.Apps { CreateApp(); - sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor }); - - Assert.Throws(() => sut.RemoveContributor(new RemoveContributor { ContributorId = "not-found" })); + Assert.Throws(() => sut.RemoveContributor(new RemoveContributor { ContributorId = "not-found" })); } [Fact] @@ -144,9 +139,7 @@ namespace Squidex.Write.Apps sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor }); sut.RemoveContributor(new RemoveContributor { ContributorId = contributorId }); - - Assert.False(sut.Contributors.ContainsKey(contributorId)); - + sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() .ShouldBeEquivalentTo( new IEvent[] @@ -155,42 +148,10 @@ namespace Squidex.Write.Apps }); } - [Fact] - public void ConfigureLanguages_should_throw_if_not_created() - { - Assert.Throws(() => sut.ConfigureLanguages(new ConfigureLanguages { Languages = languages })); - } - - [Fact] - public void ConfigureLanguages_should_throw_if_command_is_not_valid() - { - CreateApp(); - - Assert.Throws(() => sut.ConfigureLanguages(new ConfigureLanguages())); - Assert.Throws(() => sut.ConfigureLanguages(new ConfigureLanguages { Languages = new List() })); - } - - [Fact] - public void ConfigureLanguages_should_create_events() - { - CreateApp(); - - sut.ConfigureLanguages(new ConfigureLanguages { Languages = languages }); - - Assert.False(sut.Contributors.ContainsKey(contributorId)); - - sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() - .ShouldBeEquivalentTo( - new IEvent[] - { - new AppLanguagesConfigured { Languages = languages } - }); - } - [Fact] public void AttachClient_should_throw_if_not_created() { - Assert.Throws(() => sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret)); + Assert.Throws(() => sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc)); } [Fact] @@ -198,8 +159,8 @@ namespace Squidex.Write.Apps { CreateApp(); - Assert.Throws(() => sut.AttachClient(new AttachClient(), clientSecret)); - Assert.Throws(() => sut.AttachClient(new AttachClient { ClientId = string.Empty }, clientSecret)); + Assert.Throws(() => sut.AttachClient(new AttachClient(), clientSecret, expiresUtc)); + Assert.Throws(() => sut.AttachClient(new AttachClient { Id = string.Empty }, clientSecret, expiresUtc)); } [Fact] @@ -207,9 +168,9 @@ namespace Squidex.Write.Apps { CreateApp(); - sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret); + sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc); - Assert.Throws(() => sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret)); + Assert.Throws(() => sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc)); } [Fact] @@ -219,22 +180,20 @@ namespace Squidex.Write.Apps CreateApp(); - sut.AttachClient(new AttachClient { ClientId = clientId, Timestamp = now }, clientSecret); - - Assert.False(sut.Contributors.ContainsKey(contributorId)); + sut.AttachClient(new AttachClient { Id = clientId, Timestamp = now }, clientSecret, expiresUtc); sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() .ShouldBeEquivalentTo( new IEvent[] { - new AppClientAttached { ClientId = clientId, ClientSecret = clientSecret, ExpiresUtc = now.AddYears(1) } + new AppClientAttached { Id = clientId, Secret = clientSecret, ExpiresUtc = expiresUtc } }); } [Fact] public void RevokeKey_should_throw_if_not_created() { - Assert.Throws(() => sut.RevokeClient(new RevokeClient { ClientId = "not-found" })); + Assert.Throws(() => sut.RevokeClient(new RevokeClient { Id = "not-found" })); } [Fact] @@ -243,7 +202,7 @@ namespace Squidex.Write.Apps CreateApp(); Assert.Throws(() => sut.RevokeClient(new RevokeClient())); - Assert.Throws(() => sut.RevokeClient(new RevokeClient { ClientId = string.Empty })); + Assert.Throws(() => sut.RevokeClient(new RevokeClient { Id = string.Empty })); } [Fact] @@ -251,7 +210,7 @@ namespace Squidex.Write.Apps { CreateApp(); - Assert.Throws(() => sut.RevokeClient(new RevokeClient { ClientId = "not-found" })); + Assert.Throws(() => sut.RevokeClient(new RevokeClient { Id = "not-found" })); } [Fact] @@ -259,21 +218,21 @@ namespace Squidex.Write.Apps { CreateApp(); - sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret); - sut.RevokeClient(new RevokeClient { ClientId = clientId }); + sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc); + sut.RevokeClient(new RevokeClient { Id = clientId }); sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() .ShouldBeEquivalentTo( new IEvent[] { - new AppClientRevoked { ClientId = clientSecret } + new AppClientRevoked { Id = clientSecret } }); } [Fact] public void RenameKey_should_throw_if_not_created() { - Assert.Throws(() => sut.RenameClient(new RenameClient { ClientId = "not-found", Name = clientNewName })); + Assert.Throws(() => sut.RenameClient(new RenameClient { Id = "not-found", Name = clientNewName })); } [Fact] @@ -282,7 +241,7 @@ namespace Squidex.Write.Apps CreateApp(); Assert.Throws(() => sut.RenameClient(new RenameClient())); - Assert.Throws(() => sut.RenameClient(new RenameClient { ClientId = string.Empty })); + Assert.Throws(() => sut.RenameClient(new RenameClient { Id = string.Empty })); } [Fact] @@ -290,7 +249,7 @@ namespace Squidex.Write.Apps { CreateApp(); - Assert.Throws(() => sut.RenameClient(new RenameClient { ClientId = "not-found", Name = clientNewName })); + Assert.Throws(() => sut.RenameClient(new RenameClient { Id = "not-found", Name = clientNewName })); } [Fact] @@ -298,16 +257,14 @@ namespace Squidex.Write.Apps { CreateApp(); - sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret); - sut.RenameClient(new RenameClient { ClientId = clientId, Name = clientNewName }); - - Assert.Equal(clientNewName, sut.Clients[clientId].Name); + sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc); + sut.RenameClient(new RenameClient { Id = clientId, Name = clientNewName }); sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() .ShouldBeEquivalentTo( new IEvent[] { - new AppClientRenamed { ClientId = clientId, Name = clientNewName } + new AppClientRenamed { Id = clientId, Name = clientNewName } }); }