Browse Source

Some refactoring

pull/1/head
Sebastian 10 years ago
parent
commit
43e6b097a1
  1. 4
      src/Squidex.Events/Apps/AppClientAttached.cs
  2. 2
      src/Squidex.Events/Apps/AppClientRenamed.cs
  3. 2
      src/Squidex.Events/Apps/AppClientRevoked.cs
  4. 9
      src/Squidex.Events/Apps/AppLanguageAdded.cs
  5. 19
      src/Squidex.Events/Apps/AppLanguageRemoved.cs
  6. 19
      src/Squidex.Events/Apps/AppMasterLanguageSet.cs
  7. 2
      src/Squidex.Infrastructure/DomainObjectDeletedException.cs
  8. 15
      src/Squidex.Infrastructure/DomainObjectException.cs
  9. 10
      src/Squidex.Infrastructure/DomainObjectNotFoundException.cs
  10. 10
      src/Squidex.Infrastructure/DomainObjectVersionException.cs
  11. 6
      src/Squidex.Read/Apps/IAppClientEntity.cs
  12. 2
      src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs
  13. 4
      src/Squidex.Store.MongoDb/Apps/MongoAppClientEntity.cs
  14. 14
      src/Squidex.Store.MongoDb/Apps/MongoAppRepository.cs
  15. 38
      src/Squidex.Write/Apps/AppClient.cs
  16. 79
      src/Squidex.Write/Apps/AppClients.cs
  17. 11
      src/Squidex.Write/Apps/AppCommandHandler.cs
  18. 77
      src/Squidex.Write/Apps/AppContributors.cs
  19. 165
      src/Squidex.Write/Apps/AppDomainObject.cs
  20. 83
      src/Squidex.Write/Apps/AppLanguages.cs
  21. 26
      src/Squidex.Write/Apps/Commands/AddLanguage.cs
  22. 6
      src/Squidex.Write/Apps/Commands/AttachClient.cs
  23. 26
      src/Squidex.Write/Apps/Commands/RemoveLanguage.cs
  24. 6
      src/Squidex.Write/Apps/Commands/RenameClient.cs
  25. 6
      src/Squidex.Write/Apps/Commands/RevokeClient.cs
  26. 26
      src/Squidex.Write/Apps/Commands/SetMasterLanguage.cs
  27. 4
      src/Squidex/Config/Identity/LazyClientStore.cs
  28. 10
      src/Squidex/Controllers/Api/Apps/AppClientsController.cs
  29. 2
      src/Squidex/Controllers/Api/Apps/Models/AttachClientDto.cs
  30. 12
      src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs
  31. 2
      src/Squidex/Pipeline/AppFilterAttribute.cs
  32. 2
      src/Squidex/app/components/internal/app/settings/clients-page.component.html
  33. 4
      src/Squidex/app/components/internal/app/settings/clients-page.component.ts
  34. 21
      src/Squidex/app/shared/services/app-clients.service.spec.ts
  35. 13
      src/Squidex/app/shared/services/app-clients.service.ts
  36. 25
      tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs
  37. 91
      tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs

4
src/Squidex.Events/Apps/AppClientAttached.cs

@ -15,9 +15,9 @@ namespace Squidex.Events.Apps
[TypeName("AppClientAttachedEvent")] [TypeName("AppClientAttachedEvent")]
public sealed class AppClientAttached : IEvent 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; } public DateTime ExpiresUtc { get; set; }
} }

2
src/Squidex.Events/Apps/AppClientRenamed.cs

@ -14,7 +14,7 @@ namespace Squidex.Events.Apps
[TypeName("AppClientRenamedEvent")] [TypeName("AppClientRenamedEvent")]
public sealed class AppClientRenamed : IEvent public sealed class AppClientRenamed : IEvent
{ {
public string ClientId { get; set; } public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
} }

2
src/Squidex.Events/Apps/AppClientRevoked.cs

@ -14,6 +14,6 @@ namespace Squidex.Events.Apps
[TypeName("AppClientRevokedEvent")] [TypeName("AppClientRevokedEvent")]
public sealed class AppClientRevoked : IEvent public sealed class AppClientRevoked : IEvent
{ {
public string ClientId { get; set; } public string Id { get; set; }
} }
} }

9
src/Squidex.Events/Apps/AppLanguagesConfigured.cs → src/Squidex.Events/Apps/AppLanguageAdded.cs

@ -1,20 +1,19 @@
// ========================================================================== // ==========================================================================
// AppLanguagesConfigured.cs // AppLanguageAdded.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Apps namespace Squidex.Events.Apps
{ {
[TypeName("AppLanguagesConfiguredEvent")] [TypeName("AppLanguageAddedEvent")]
public sealed class AppLanguagesConfigured : IEvent public sealed class AppLanguageAdded : IEvent
{ {
public List<Language> Languages { get; set; } public Language Language { get; set; }
} }
} }

19
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; }
}
}

19
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; }
}
}

2
src/Squidex.Infrastructure/DomainObjectDeletedException.cs

@ -19,7 +19,7 @@ namespace Squidex.Infrastructure
private static string FormatMessage(string id, Type type) private static string FormatMessage(string id, Type type)
{ {
return $"Domain object \'{id}\' (type {type}) not deleted."; return $"Domain object \'{id}\' (type {type}) already deleted.";
} }
} }
} }

15
src/Squidex.Infrastructure/DomainObjectException.cs

@ -17,18 +17,12 @@ namespace Squidex.Infrastructure
public string TypeName public string TypeName
{ {
get get { return typeName; }
{
return typeName;
}
} }
public string Id public string Id
{ {
get get { return id; }
{
return id;
}
} }
protected DomainObjectException(string message, string id, Type type) protected DomainObjectException(string message, string id, Type type)
@ -41,10 +35,7 @@ namespace Squidex.Infrastructure
{ {
this.id = id; this.id = id;
if (type != null) typeName = type?.Name;
{
typeName = type.Name;
}
} }
} }
} }

10
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) private static string FormatMessage(string id, Type type)
{ {
return $"Domain object \'{id}\' (type {type}) not found."; 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}";
}
} }
} }

10
src/Squidex.Infrastructure/DomainObjectVersionException.cs

@ -17,18 +17,12 @@ namespace Squidex.Infrastructure
public int CurrentVersion public int CurrentVersion
{ {
get get { return currentVersion; }
{
return currentVersion;
}
} }
public int ExpectedVersion public int ExpectedVersion
{ {
get get { return expectedVersion; }
{
return expectedVersion;
}
} }
public DomainObjectVersionException(string id, Type type, int currentVersion, int expectedVersion) public DomainObjectVersionException(string id, Type type, int currentVersion, int expectedVersion)

6
src/Squidex.Read/Apps/IAppClientEntity.cs

@ -12,11 +12,11 @@ namespace Squidex.Read.Apps
{ {
public interface IAppClientEntity public interface IAppClientEntity
{ {
string Name { get; } string Id { get; }
string ClientId { get; } string Name { get; }
string ClientSecret { get; } string Secret { get; }
DateTime ExpiresUtc { get; } DateTime ExpiresUtc { get; }
} }

2
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 AppClientAttached ||
@event.Payload is AppClientRevoked || @event.Payload is AppClientRevoked ||
@event.Payload is AppClientRenamed || @event.Payload is AppClientRenamed ||
@event.Payload is AppLanguagesConfigured) @event.Payload is AppLanguageAdded)
{ {
var appName = Cache.Get<string>(BuildNamesCacheKey(@event.Headers.AggregateId())); var appName = Cache.Get<string>(BuildNamesCacheKey(@event.Headers.AggregateId()));

4
src/Squidex.Store.MongoDb/Apps/MongoAppClientEntity.cs

@ -16,11 +16,11 @@ namespace Squidex.Store.MongoDb.Apps
{ {
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public string ClientId { get; set; } public string Id { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public string ClientSecret { get; set; } public string Secret { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]

14
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) protected Task On(AppClientAttached @event, EnvelopeHeaders headers)
{ {
return Collection.UpdateAsync(headers, a => 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 => 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 => return Collection.UpdateAsync(headers, a =>
{ {
a.Clients[@event.ClientId].Name = @event.Name; a.Clients[@event.Id].Name = @event.Name;
}); });
} }

38
src/Squidex.Write/Apps/AppClient.cs

@ -13,33 +13,45 @@ namespace Squidex.Write.Apps
{ {
public sealed class AppClient 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(id, nameof(id));
Guard.NotNullOrEmpty(secret, nameof(secret)); Guard.NotNullOrEmpty(secret, nameof(secret));
ClientId = id; this.id = id;
ClientSecret = secret; this.name = name;
this.secret = secret;
ExpiresUtc = expiresUtc; this.expiresUtc = expiresUtc;
} }
public void Rename(string newName) public AppClient Rename(string newName)
{ {
name = newName; return new AppClient(Id, Secret, ExpiresUtc, newName);
} }
} }
} }

79
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<string, AppClient> clients = new Dictionary<string, AppClient>();
public IReadOnlyDictionary<string, AppClient> Clients
{
get { return clients; }
}
public void Add(string id, string secret, DateTime expires)
{
Func<string> message = () => "Cannot add client";
ThrowIfFound(id, message);
clients[id] = new AppClient(id, secret, expires);
}
public void Rename(string clientId, string name)
{
Func<string> 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<string> 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<string> message)
{
if (string.Equals(clients[clientId].Name, name))
{
var error = new ValidationError("Client already has the name", "Id");
throw new ValidationException(message(), error);
}
}
}
}

11
src/Squidex.Write/Apps/AppCommandHandler.cs

@ -80,9 +80,9 @@ namespace Squidex.Write.Apps
{ {
return handler.UpdateAsync<AppDomainObject>(command, x => return handler.UpdateAsync<AppDomainObject>(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<AppDomainObject>(command, x => x.RevokeClient(command)); return handler.UpdateAsync<AppDomainObject>(command, x => x.RevokeClient(command));
} }
protected Task On(ConfigureLanguages command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(command, x => x.ConfigureLanguages(command));
}
public Task<bool> HandleAsync(CommandContext context) public Task<bool> HandleAsync(CommandContext context)
{ {
return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context); return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context);

77
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<string, PermissionLevel> contributors = new Dictionary<string, PermissionLevel>();
public void Assign(string contributorId, PermissionLevel permission)
{
Func<string> message = () => "Cannot assign contributor";
ThrowIfFound(contributorId, message);
ThrowIfNoOwner(c => c[contributorId] = permission, message);
contributors[contributorId] = permission;
}
public void Remove(string contributorId)
{
Func<string> 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<string> 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<Dictionary<string, PermissionLevel>> change, Func<string> message)
{
var contributorsCopy = new Dictionary<string, PermissionLevel>(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);
}
}
}
}

165
src/Squidex.Write/Apps/AppDomainObject.cs

@ -15,7 +15,6 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Write.Apps.Commands; using Squidex.Write.Apps.Commands;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
// ReSharper disable InvertIf // ReSharper disable InvertIf
@ -24,9 +23,10 @@ namespace Squidex.Write.Apps
{ {
public sealed class AppDomainObject : DomainObject public sealed class AppDomainObject : DomainObject
{ {
private static readonly List<Language> DefaultLanguages = new List<Language> { Language.GetLanguage("en") }; private static readonly Language DefaultLanguage = Language.GetLanguage("en");
private readonly Dictionary<string, AppClient> clients = new Dictionary<string, AppClient>(StringComparer.OrdinalIgnoreCase); private readonly AppContributors contributors = new AppContributors();
private readonly Dictionary<string, PermissionLevel> contributors = new Dictionary<string, PermissionLevel>(); private readonly AppLanguages languages = new AppLanguages();
private readonly AppClients clients = new AppClients();
private string name; private string name;
public string Name public string Name
@ -34,14 +34,9 @@ namespace Squidex.Write.Apps
get { return name; } get { return name; }
} }
public IReadOnlyDictionary<string, PermissionLevel> Contributors
{
get { return contributors; }
}
public IReadOnlyDictionary<string, AppClient> Clients public IReadOnlyDictionary<string, AppClient> Clients
{ {
get { return clients; } get { return clients.Clients; }
} }
public AppDomainObject(Guid id, int version) public AppDomainObject(Guid id, int version)
@ -56,7 +51,7 @@ namespace Squidex.Write.Apps
public void On(AppContributorAssigned @event) public void On(AppContributorAssigned @event)
{ {
contributors[@event.ContributorId] = @event.Permission; contributors.Assign(@event.ContributorId, @event.Permission);
} }
public void On(AppContributorRemoved @event) public void On(AppContributorRemoved @event)
@ -66,17 +61,32 @@ namespace Squidex.Write.Apps
public void On(AppClientAttached @event) 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) 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<IEvent> @event) protected override void DispatchEvent(Envelope<IEvent> @event)
@ -86,9 +96,7 @@ namespace Squidex.Write.Apps
public AppDomainObject Create(CreateApp command) public AppDomainObject Create(CreateApp command)
{ {
Func<string> message = () => "Cannot create app"; Guard.Valid(command, nameof(command), () => "Cannot create app");
Guard.Valid(command, nameof(command), message);
ThrowIfCreated(); ThrowIfCreated();
@ -96,32 +104,49 @@ namespace Squidex.Write.Apps
RaiseEvent(CreateInitialOwner(command)); RaiseEvent(CreateInitialOwner(command));
RaiseEvent(CreateInitialLanguage()); RaiseEvent(CreateInitialLanguage());
RaiseEvent(CreateInitialMasterLanguage());
return this; return this;
} }
public AppDomainObject AssignContributor(AssignContributor command) public AppDomainObject AssignContributor(AssignContributor command)
{ {
Func<string> message = () => "Cannot assign contributor"; Guard.Valid(command, nameof(command), () => "Cannot assign contributor");
Guard.Valid(command, nameof(command), message);
ThrowIfNotCreated(); ThrowIfNotCreated();
ThrowIfNoOwner(c => c[command.ContributorId] = command.Permission, message);
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned())); RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned()));
return this; 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<string> 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(); ThrowIfNotCreated();
ThrowIfClientNotFound(command.ClientId, message);
RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed())); RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed()));
@ -130,66 +155,56 @@ namespace Squidex.Write.Apps
public AppDomainObject RevokeClient(RevokeClient command) public AppDomainObject RevokeClient(RevokeClient command)
{ {
Func<string> message = () => "Cannot revoke client"; Guard.Valid(command, nameof(command), () => "Cannot revoke client");
Guard.Valid(command, nameof(command), message);
ThrowIfNotCreated(); ThrowIfNotCreated();
ThrowIfClientNotFound(command.ClientId, message);
RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked())); RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked()));
return this; return this;
} }
public AppDomainObject AttachClient(AttachClient command, string secret) public AppDomainObject AddLanguage(AddLanguage command)
{ {
Func<string> message = () => "Cannot attach client"; Guard.Valid(command, nameof(command), () => "Cannot add language");
Guard.Valid(command, nameof(command), () => "Cannot attach client");
ThrowIfNotCreated(); 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; return this;
} }
public AppDomainObject RemoveContributor(RemoveContributor command) public AppDomainObject RemoveLanguage(RemoveLanguage command)
{ {
Func<string> message = () => "Cannot remove contributor"; Guard.Valid(command, nameof(command), () => "Cannot remove language");
Guard.Valid(command, nameof(command), () => "Cannot remove contributor");
ThrowIfNotCreated(); 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; return this;
} }
public AppDomainObject ConfigureLanguages(ConfigureLanguages command) public AppDomainObject SetMasterLanguage(SetMasterLanguage command)
{ {
Func<string> message = () => "Cannot configure languages"; Guard.Valid(command, nameof(command), () => "Cannot set master language");
Guard.Valid(command, nameof(command), message);
ThrowIfNotCreated(); ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppLanguagesConfigured())); RaiseEvent(SimpleMapper.Map(command, new AppMasterLanguageSet()));
return this; 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) private static AppContributorAssigned CreateInitialOwner(IUserCommand command)
@ -212,49 +227,5 @@ namespace Squidex.Write.Apps
throw new DomainException("App has already been created."); throw new DomainException("App has already been created.");
} }
} }
private void ThrowIfClientFound(string clientId, Func<string> 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<string> 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<string> 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<Dictionary<string, PermissionLevel>> change, Func<string> message)
{
var contributorsCopy = new Dictionary<string, PermissionLevel>(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);
}
}
} }
} }

83
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<Language> languages = new HashSet<Language>();
private Language masterLanguage;
public IReadOnlyCollection<Language> Languages
{
get { return languages; }
}
public void Add(Language language)
{
Func<string> message = () => "Cannot add language";
ThrowIfFound(language, message);
languages.Add(language);
}
public void Remove(Language language)
{
Func<string> message = () => "Cannot remove language";
ThrowIfNotFound(language);
ThrowIfMasterLanguage(language, message);
languages.Remove(language);
}
public void SetMasterLanguage(Language language)
{
Func<string> 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<string> 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<string> message)
{
if (masterLanguage != null && masterLanguage.Equals(language))
{
var error = new ValidationError("Language is the master language", "Language");
throw new ValidationException(message(), error);
}
}
}
}

26
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<ValidationError> errors)
{
if (Language == null)
{
errors.Add(new ValidationError("Language cannot be null", nameof(Language)));
}
}
}
}

6
src/Squidex.Write/Apps/Commands/AttachClient.cs

@ -15,15 +15,15 @@ namespace Squidex.Write.Apps.Commands
{ {
public sealed class AttachClient : AppAggregateCommand, ITimestampCommand, IValidatable public sealed class AttachClient : AppAggregateCommand, ITimestampCommand, IValidatable
{ {
public string ClientId { get; set; } public string Id { get; set; }
public DateTime Timestamp { get; set; } public DateTime Timestamp { get; set; }
public void Validate(IList<ValidationError> errors) public void Validate(IList<ValidationError> 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)));
} }
} }
} }

26
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<ValidationError> errors)
{
if (Language == null)
{
errors.Add(new ValidationError("Language cannot be null", nameof(Language)));
}
}
}
}

6
src/Squidex.Write/Apps/Commands/RenameClient.cs

@ -13,7 +13,7 @@ namespace Squidex.Write.Apps.Commands
{ {
public class RenameClient : AppAggregateCommand, IValidatable public class RenameClient : AppAggregateCommand, IValidatable
{ {
public string ClientId { get; set; } public string Id { get; set; }
public string Name { 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))); 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)));
} }
} }
} }

6
src/Squidex.Write/Apps/Commands/RevokeClient.cs

@ -13,13 +13,13 @@ namespace Squidex.Write.Apps.Commands
{ {
public class RevokeClient : AppAggregateCommand, IValidatable public class RevokeClient : AppAggregateCommand, IValidatable
{ {
public string ClientId { get; set; } public string Id { get; set; }
public void Validate(IList<ValidationError> errors) public void Validate(IList<ValidationError> 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)));
} }
} }
} }

26
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<ValidationError> errors)
{
if (Language == null)
{
errors.Add(new ValidationError("Language cannot be null", nameof(Language)));
}
}
}
}

4
src/Squidex/Config/Identity/LazyClientStore.cs

@ -52,7 +52,7 @@ namespace Squidex.Config.Identity
var app = await appProvider.FindAppByNameAsync(token[0]); 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) if (appClient == null)
{ {
@ -71,7 +71,7 @@ namespace Squidex.Config.Identity
{ {
ClientId = id, ClientId = id,
ClientName = id, ClientName = id,
ClientSecrets = new List<Secret> { new Secret(appClient.ClientSecret.Sha512(), appClient.ExpiresUtc) }, ClientSecrets = new List<Secret> { new Secret(appClient.Secret.Sha512(), appClient.ExpiresUtc) },
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds,
AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = new List<string> AllowedScopes = new List<string>

10
src/Squidex/Controllers/Api/Apps/AppClientsController.cs

@ -96,7 +96,7 @@ namespace Squidex.Controllers.Api.Apps
/// Updates an app client. /// Updates an app client.
/// </summary> /// </summary>
/// <param name="app">The name of the app.</param> /// <param name="app">The name of the app.</param>
/// <param name="client">The id of the client that must be updated.</param> /// <param name="clientId">The id of the client that must be updated.</param>
/// <param name="request">Client object that needs to be added to the app.</param> /// <param name="request">Client object that needs to be added to the app.</param>
/// <returns> /// <returns>
/// 201 => Client key generated. /// 201 => Client key generated.
@ -105,7 +105,7 @@ namespace Squidex.Controllers.Api.Apps
[HttpPut] [HttpPut]
[Route("apps/{app}/clients/{client}/")] [Route("apps/{app}/clients/{client}/")]
[ProducesResponseType(typeof(ClientDto[]), 201)] [ProducesResponseType(typeof(ClientDto[]), 201)]
public async Task<IActionResult> PutClient(string app, string client, [FromBody] RenameClientDto request) public async Task<IActionResult> PutClient(string app, string clientId, [FromBody] RenameClientDto request)
{ {
await CommandBus.PublishAsync(SimpleMapper.Map(request, new RenameClient())); await CommandBus.PublishAsync(SimpleMapper.Map(request, new RenameClient()));
@ -116,16 +116,16 @@ namespace Squidex.Controllers.Api.Apps
/// Revoke an app client /// Revoke an app client
/// </summary> /// </summary>
/// <param name="app">The name of the app.</param> /// <param name="app">The name of the app.</param>
/// <param name="client">The id of the client that must be deleted.</param> /// <param name="clientId">The id of the client that must be deleted.</param>
/// <returns> /// <returns>
/// 404 => App not found or client not found. /// 404 => App not found or client not found.
/// 204 => Client revoked. /// 204 => Client revoked.
/// </returns> /// </returns>
[HttpDelete] [HttpDelete]
[Route("apps/{app}/clients/{client}/")] [Route("apps/{app}/clients/{client}/")]
public async Task<IActionResult> DeleteClient(string app, string client) public async Task<IActionResult> DeleteClient(string app, string clientId)
{ {
await CommandBus.PublishAsync(new RevokeClient { ClientId = client }); await CommandBus.PublishAsync(new RevokeClient { Id = clientId });
return NoContent(); return NoContent();
} }

2
src/Squidex/Controllers/Api/Apps/Models/AttachClientDto.cs

@ -17,6 +17,6 @@ namespace Squidex.Controllers.Api.Apps.Models
/// </summary> /// </summary>
[Required] [Required]
[RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")]
public string ClientId { get; set; } public string Id { get; set; }
} }
} }

12
src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs

@ -17,24 +17,24 @@ namespace Squidex.Controllers.Api.Apps.Models
/// The client id. /// The client id.
/// </summary> /// </summary>
[Required] [Required]
public string ClientId { get; set; } public string Id { get; set; }
/// <summary> /// <summary>
/// The client secret. /// The client secret.
/// </summary> /// </summary>
[Required] [Required]
public string ClientSecret { get; set; } public string Secret { get; set; }
/// <summary> /// <summary>
/// The date and time when the client key expires. /// The client name.
/// </summary> /// </summary>
[Required] [Required]
public DateTime ExpiresUtc { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// The client name. /// The date and time when the client key expires.
/// </summary> /// </summary>
[Required] [Required]
public string Name { get; set; } public DateTime ExpiresUtc { get; set; }
} }
} }

2
src/Squidex/Pipeline/AppFilterAttribute.cs

@ -77,7 +77,7 @@ namespace Squidex.Pipeline
clientId = clientId.Split(':')[0]; 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; return contributor != null ? PermissionLevel.Owner : PermissionLevel.Editor;
} }

2
src/Squidex/app/components/internal/app/settings/clients-page.component.html

@ -37,7 +37,7 @@
<tr> <tr>
<td>Client Secret:</td> <td>Client Secret:</td>
<td> <td>
<input readonly class="form-control" [attr.value]="client.clientSecret" /> <input readonly class="form-control" [attr.value]="client.secret" />
</td> </td>
</tr> </tr>
<tr> <tr>

4
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 { public fullAppName(client: AppClientDto): string {
return this.appName + ':' + client.clientName; return this.appName + ':' + client.id;
} }
public revokeClient(client: AppClientDto) { public revokeClient(client: AppClientDto) {
this.appClientsService.deleteClient(this.appName, client.clientName) this.appClientsService.deleteClient(this.appName, client.id)
.subscribe(() => { .subscribe(() => {
this.appClients.splice(this.appClients.indexOf(client), 1); this.appClients.splice(this.appClients.indexOf(client), 1);
}, error => { }, error => {

21
src/Squidex/app/shared/services/app-clients.service.spec.ts

@ -34,12 +34,14 @@ describe('AppClientsService', () => {
new Ng2Http.Response( new Ng2Http.Response(
new Ng2Http.ResponseOptions({ new Ng2Http.ResponseOptions({
body: [{ body: [{
clientName: 'client1', id: 'client1',
clientSecret: 'secret1', name: 'Client1',
secret: 'secret1',
expiresUtc: '2016-12-12T10:10' expiresUtc: '2016-12-12T10:10'
}, { }, {
clientName: 'client2', id: 'client2',
clientSecret: 'secret2', name: 'Client2',
secret: 'secret2',
expiresUtc: '2016-11-11T10:10' expiresUtc: '2016-11-11T10:10'
}] }]
}) })
@ -55,8 +57,8 @@ describe('AppClientsService', () => {
expect(clients).toEqual( expect(clients).toEqual(
[ [
new AppClientDto('client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10')), new AppClientDto('client1', 'Client1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10')),
new AppClientDto('client2', 'secret2', DateTime.parseISO_UTC('2016-11-11T10:10')), new AppClientDto('client2', 'Client2', 'secret2', DateTime.parseISO_UTC('2016-11-11T10:10')),
]); ]);
authService.verifyAll(); authService.verifyAll();
@ -70,8 +72,9 @@ describe('AppClientsService', () => {
new Ng2Http.Response( new Ng2Http.Response(
new Ng2Http.ResponseOptions({ new Ng2Http.ResponseOptions({
body: { body: {
clientName: 'client1', id: 'client1',
clientSecret: 'secret1', name: 'Client1',
secret: 'secret1',
expiresUtc: '2016-12-12T10:10' expiresUtc: '2016-12-12T10:10'
} }
}) })
@ -86,7 +89,7 @@ describe('AppClientsService', () => {
}); });
expect(client).toEqual( 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(); authService.verifyAll();
}); });

13
src/Squidex/app/shared/services/app-clients.service.ts

@ -15,8 +15,9 @@ import { AuthService } from './auth.service';
export class AppClientDto { export class AppClientDto {
constructor( constructor(
public readonly clientName: string, public readonly id: string,
public readonly clientSecret: string, public readonly name: string,
public readonly secret: string,
public readonly expiresUtc: DateTime public readonly expiresUtc: DateTime
) { ) {
} }
@ -24,7 +25,7 @@ export class AppClientDto {
export class AppClientCreateDto { export class AppClientCreateDto {
constructor( constructor(
public readonly clientName: string public readonly id: string
) { ) {
} }
} }
@ -53,7 +54,7 @@ export class AppClientsService {
const items: any[] = response; const items: any[] = response;
return items.map(item => { 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<AppClientDto> { public postClient(appName: string, client: AppClientCreateDto): Observable<AppClientDto> {
return this.authService.authPost(this.apiUrl.buildUrl(`api/apps/${appName}/clients`), client) return this.authService.authPost(this.apiUrl.buildUrl(`api/apps/${appName}/clients`), client)
.map(response => response.json()) .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 => { .catch(response => {
if (response.status === 400) { if (response.status === 400) {
return Observable.throw('A client with the same name already exists.'); 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) return this.http.post(this.apiUrl.buildUrl('identity-server/connect/token'), body, options)
.map(response => response.json()) .map(response => response.json())

25
tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs

@ -34,6 +34,7 @@ namespace Squidex.Write.Apps
private readonly AppCommandHandler sut; private readonly AppCommandHandler sut;
private readonly AppDomainObject app; private readonly AppDomainObject app;
private readonly UserToken subjectId = new UserToken("subject", Guid.NewGuid().ToString()); 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 contributorId = Guid.NewGuid().ToString();
private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientSecret = Guid.NewGuid().ToString();
private readonly string clientName = "client"; private readonly string clientName = "client";
@ -77,20 +78,6 @@ namespace Squidex.Write.Apps
Assert.Equal(command.AggregateId, context.Result<Guid>()); Assert.Equal(command.AggregateId, context.Result<Guid>());
} }
[Fact]
public async Task ConfigureLanguages_should_update_domain_object()
{
CreateApp();
var command = new ConfigureLanguages { AggregateId = Id, Languages = new List<Language> { Language.GetLanguage("de") } };
var context = new CommandContext(command);
await TestUpdate(app, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact] [Fact]
public async Task AssignContributor_should_throw_if_user_not_found() public async Task AssignContributor_should_throw_if_user_not_found()
@ -164,7 +151,7 @@ namespace Squidex.Write.Apps
var timestamp = DateTime.Today; 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); var context = new CommandContext(command);
await TestUpdate(app, async _ => await TestUpdate(app, async _ =>
@ -182,9 +169,9 @@ namespace Squidex.Write.Apps
public async Task RenameClient_should_update_domain_object() public async Task RenameClient_should_update_domain_object()
{ {
CreateApp() 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); var context = new CommandContext(command);
await TestUpdate(app, async _ => await TestUpdate(app, async _ =>
@ -197,9 +184,9 @@ namespace Squidex.Write.Apps
public async Task RevokeClient_should_update_domain_object() public async Task RevokeClient_should_update_domain_object()
{ {
CreateApp() 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); var context = new CommandContext(command);
await TestUpdate(app, async _ => await TestUpdate(app, async _ =>

91
tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using Squidex.Core.Apps; using Squidex.Core.Apps;
@ -27,11 +26,11 @@ namespace Squidex.Write.Apps
private const string TestName = "app"; private const string TestName = "app";
private readonly AppDomainObject sut; private readonly AppDomainObject sut;
private readonly UserToken user = new UserToken("subject", Guid.NewGuid().ToString()); 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 contributorId = Guid.NewGuid().ToString();
private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientSecret = Guid.NewGuid().ToString();
private readonly string clientId = "client"; private readonly string clientId = "client";
private readonly string clientNewName = "My Client"; private readonly string clientNewName = "My Client";
private readonly List<Language> languages = new List<Language> { Language.GetLanguage("de") };
public AppDomainObjectTests() public AppDomainObjectTests()
{ {
@ -58,7 +57,6 @@ namespace Squidex.Write.Apps
sut.Create(new CreateApp { Name = TestName, User = user }); sut.Create(new CreateApp { Name = TestName, User = user });
Assert.Equal(TestName, sut.Name); Assert.Equal(TestName, sut.Name);
Assert.Equal(PermissionLevel.Owner, sut.Contributors[user.Identifier]);
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
@ -66,7 +64,8 @@ namespace Squidex.Write.Apps
{ {
new AppCreated { Name = TestName }, new AppCreated { Name = TestName },
new AppContributorAssigned { ContributorId = user.Identifier, Permission = PermissionLevel.Owner }, new AppContributorAssigned { ContributorId = user.Identifier, Permission = PermissionLevel.Owner },
new AppLanguagesConfigured { Languages= new List<Language> { 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 }); sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor });
Assert.Equal(PermissionLevel.Editor, sut.Contributors[contributorId]);
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
@ -132,9 +129,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor }); Assert.Throws<DomainObjectNotFoundException>(() => sut.RemoveContributor(new RemoveContributor { ContributorId = "not-found" }));
Assert.Throws<ValidationException>(() => sut.RemoveContributor(new RemoveContributor { ContributorId = "not-found" }));
} }
[Fact] [Fact]
@ -144,9 +139,7 @@ namespace Squidex.Write.Apps
sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor }); sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor });
sut.RemoveContributor(new RemoveContributor { ContributorId = contributorId }); sut.RemoveContributor(new RemoveContributor { ContributorId = contributorId });
Assert.False(sut.Contributors.ContainsKey(contributorId));
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
@ -155,42 +148,10 @@ namespace Squidex.Write.Apps
}); });
} }
[Fact]
public void ConfigureLanguages_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.ConfigureLanguages(new ConfigureLanguages { Languages = languages }));
}
[Fact]
public void ConfigureLanguages_should_throw_if_command_is_not_valid()
{
CreateApp();
Assert.Throws<ValidationException>(() => sut.ConfigureLanguages(new ConfigureLanguages()));
Assert.Throws<ValidationException>(() => sut.ConfigureLanguages(new ConfigureLanguages { Languages = new List<Language>() }));
}
[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] [Fact]
public void AttachClient_should_throw_if_not_created() public void AttachClient_should_throw_if_not_created()
{ {
Assert.Throws<DomainException>(() => sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret)); Assert.Throws<DomainException>(() => sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc));
} }
[Fact] [Fact]
@ -198,8 +159,8 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.AttachClient(new AttachClient(), clientSecret)); Assert.Throws<ValidationException>(() => sut.AttachClient(new AttachClient(), clientSecret, expiresUtc));
Assert.Throws<ValidationException>(() => sut.AttachClient(new AttachClient { ClientId = string.Empty }, clientSecret)); Assert.Throws<ValidationException>(() => sut.AttachClient(new AttachClient { Id = string.Empty }, clientSecret, expiresUtc));
} }
[Fact] [Fact]
@ -207,9 +168,9 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret); sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc);
Assert.Throws<ValidationException>(() => sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret)); Assert.Throws<ValidationException>(() => sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc));
} }
[Fact] [Fact]
@ -219,22 +180,20 @@ namespace Squidex.Write.Apps
CreateApp(); CreateApp();
sut.AttachClient(new AttachClient { ClientId = clientId, Timestamp = now }, clientSecret); sut.AttachClient(new AttachClient { Id = clientId, Timestamp = now }, clientSecret, expiresUtc);
Assert.False(sut.Contributors.ContainsKey(contributorId));
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new AppClientAttached { ClientId = clientId, ClientSecret = clientSecret, ExpiresUtc = now.AddYears(1) } new AppClientAttached { Id = clientId, Secret = clientSecret, ExpiresUtc = expiresUtc }
}); });
} }
[Fact] [Fact]
public void RevokeKey_should_throw_if_not_created() public void RevokeKey_should_throw_if_not_created()
{ {
Assert.Throws<DomainException>(() => sut.RevokeClient(new RevokeClient { ClientId = "not-found" })); Assert.Throws<DomainException>(() => sut.RevokeClient(new RevokeClient { Id = "not-found" }));
} }
[Fact] [Fact]
@ -243,7 +202,7 @@ namespace Squidex.Write.Apps
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.RevokeClient(new RevokeClient())); Assert.Throws<ValidationException>(() => sut.RevokeClient(new RevokeClient()));
Assert.Throws<ValidationException>(() => sut.RevokeClient(new RevokeClient { ClientId = string.Empty })); Assert.Throws<ValidationException>(() => sut.RevokeClient(new RevokeClient { Id = string.Empty }));
} }
[Fact] [Fact]
@ -251,7 +210,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.RevokeClient(new RevokeClient { ClientId = "not-found" })); Assert.Throws<DomainObjectNotFoundException>(() => sut.RevokeClient(new RevokeClient { Id = "not-found" }));
} }
[Fact] [Fact]
@ -259,21 +218,21 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret); sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc);
sut.RevokeClient(new RevokeClient { ClientId = clientId }); sut.RevokeClient(new RevokeClient { Id = clientId });
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new AppClientRevoked { ClientId = clientSecret } new AppClientRevoked { Id = clientSecret }
}); });
} }
[Fact] [Fact]
public void RenameKey_should_throw_if_not_created() public void RenameKey_should_throw_if_not_created()
{ {
Assert.Throws<DomainException>(() => sut.RenameClient(new RenameClient { ClientId = "not-found", Name = clientNewName })); Assert.Throws<DomainException>(() => sut.RenameClient(new RenameClient { Id = "not-found", Name = clientNewName }));
} }
[Fact] [Fact]
@ -282,7 +241,7 @@ namespace Squidex.Write.Apps
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.RenameClient(new RenameClient())); Assert.Throws<ValidationException>(() => sut.RenameClient(new RenameClient()));
Assert.Throws<ValidationException>(() => sut.RenameClient(new RenameClient { ClientId = string.Empty })); Assert.Throws<ValidationException>(() => sut.RenameClient(new RenameClient { Id = string.Empty }));
} }
[Fact] [Fact]
@ -290,7 +249,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.RenameClient(new RenameClient { ClientId = "not-found", Name = clientNewName })); Assert.Throws<DomainObjectNotFoundException>(() => sut.RenameClient(new RenameClient { Id = "not-found", Name = clientNewName }));
} }
[Fact] [Fact]
@ -298,16 +257,14 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
sut.AttachClient(new AttachClient { ClientId = clientId }, clientSecret); sut.AttachClient(new AttachClient { Id = clientId }, clientSecret, expiresUtc);
sut.RenameClient(new RenameClient { ClientId = clientId, Name = clientNewName }); sut.RenameClient(new RenameClient { Id = clientId, Name = clientNewName });
Assert.Equal(clientNewName, sut.Clients[clientId].Name);
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new AppClientRenamed { ClientId = clientId, Name = clientNewName } new AppClientRenamed { Id = clientId, Name = clientNewName }
}); });
} }

Loading…
Cancel
Save