Browse Source

History V1

pull/1/head
Sebastian 10 years ago
parent
commit
721db58288
  1. 5
      src/Squidex.Events/EventExtensions.cs
  2. 6
      src/Squidex.Infrastructure/CQRS/Commands/IActorCommand.cs
  3. 2
      src/Squidex.Infrastructure/CQRS/CommonHeaders.cs
  4. 8
      src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs
  5. 8
      src/Squidex.Infrastructure/CQRS/Events/EnrichWithActorProcessor.cs
  6. 8
      src/Squidex.Infrastructure/Json/RefTokenConverter.cs
  7. 14
      src/Squidex.Infrastructure/RefToken.cs
  8. 3
      src/Squidex.Read/History/IHistoryEventEntity.cs
  9. 4
      src/Squidex.Read/IAppRefEntity.cs
  10. 13
      src/Squidex.Read/ITrackCreatedByEntity.cs
  11. 17
      src/Squidex.Read/ITrackLastModifiedByEntity.cs
  12. 2
      src/Squidex.Read/Schemas/Repositories/ISchemaEntity.cs
  13. 4
      src/Squidex.Store.MongoDb/History/MessagesEN.cs
  14. 22
      src/Squidex.Store.MongoDb/History/MongoHistoryEventEntity.cs
  15. 10
      src/Squidex.Store.MongoDb/History/MongoHistoryEventRepository.cs
  16. 6
      src/Squidex.Store.MongoDb/History/ParsedHistoryEvent.cs
  17. 12
      src/Squidex.Store.MongoDb/MongoDbModule.cs
  18. 83
      src/Squidex.Store.MongoDb/Utils/EntityMapper.cs
  19. 60
      src/Squidex.Store.MongoDb/Utils/RefTokenSerializer.cs
  20. 4
      src/Squidex.Write/Apps/AppDomainObject.cs
  21. 4
      src/Squidex.Write/Apps/Commands/CreateApp.cs
  22. 4
      src/Squidex.Write/SquidexCommand.cs
  23. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  24. 2
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  25. 2
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  26. 6
      src/Squidex/app/framework/utils/date-time.spec.ts
  27. 4
      src/Squidex/app/framework/utils/date-time.ts
  28. 4
      src/Squidex/app/shared/components/app-form.component.html
  29. 2
      src/Squidex/app/shared/components/app-form.component.ts
  30. 7
      src/Squidex/app/shared/components/history.component.html
  31. 20
      src/Squidex/app/shared/components/history.component.scss
  32. 32
      src/Squidex/app/shared/components/history.component.ts
  33. 4
      src/Squidex/app/shared/services/history.service.spec.ts
  34. 4
      src/Squidex/app/shared/services/history.service.ts
  35. 9
      src/Squidex/app/shared/services/users-provider.service.ts
  36. 6
      src/Squidex/app/theme/_history.scss
  37. 5
      src/Squidex/app/theme/_panels.scss
  38. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  39. 1
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  40. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  41. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  42. 27
      src/Squidex/app/theme/icomoon/selection.json
  43. 13
      src/Squidex/app/theme/icomoon/style.css
  44. 3
      src/Squidex/app/theme/theme.scss
  45. 10
      tests/Squidex.Infrastructure.Tests/CQRS/EnvelopeExtensionsTests.cs
  46. 18
      tests/Squidex.Infrastructure.Tests/CQRS/Events/EnrichWithActorProcessorTests.cs
  47. 46
      tests/Squidex.Infrastructure.Tests/RefTokenTests.cs
  48. 8
      tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs
  49. 6
      tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs

5
src/Squidex.Events/EventExtensions.cs

@ -14,6 +14,11 @@ namespace Squidex.Events
{ {
public static class EventExtensions public static class EventExtensions
{ {
public static bool HasAppId(this EnvelopeHeaders headers)
{
return headers.Contains("AppId");
}
public static Guid AppId(this EnvelopeHeaders headers) public static Guid AppId(this EnvelopeHeaders headers)
{ {
return headers["AppId"].ToGuid(CultureInfo.InvariantCulture); return headers["AppId"].ToGuid(CultureInfo.InvariantCulture);

6
src/Squidex.Infrastructure/CQRS/Commands/IUserCommand.cs → src/Squidex.Infrastructure/CQRS/Commands/IActorCommand.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// IUserCommand.cs // IActorCommand.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -8,8 +8,8 @@
namespace Squidex.Infrastructure.CQRS.Commands namespace Squidex.Infrastructure.CQRS.Commands
{ {
public interface IUserCommand : ICommand public interface IActorCommand : ICommand
{ {
UserToken User { get; set; } RefToken Actor { get; set; }
} }
} }

2
src/Squidex.Infrastructure/CQRS/CommonHeaders.cs

@ -20,6 +20,6 @@ namespace Squidex.Infrastructure.CQRS
public const string Timestamp = "Timestamp"; public const string Timestamp = "Timestamp";
public const string User = "User"; public const string Actor = "Actor";
} }
} }

8
src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs

@ -62,14 +62,14 @@ namespace Squidex.Infrastructure.CQRS
return envelope; return envelope;
} }
public static UserToken User(this EnvelopeHeaders headers) public static RefToken Actor(this EnvelopeHeaders headers)
{ {
return UserToken.Parse(headers[CommonHeaders.User].ToString()); return RefToken.Parse(headers[CommonHeaders.Actor].ToString());
} }
public static Envelope<T> SetUser<T>(this Envelope<T> envelope, UserToken value) where T : class public static Envelope<T> SetActor<T>(this Envelope<T> envelope, RefToken value) where T : class
{ {
envelope.Headers.Set(CommonHeaders.User, value.ToString()); envelope.Headers.Set(CommonHeaders.Actor, value.ToString());
return envelope; return envelope;
} }

8
src/Squidex.Infrastructure/CQRS/Events/EnrichWithUserProcessor.cs → src/Squidex.Infrastructure/CQRS/Events/EnrichWithActorProcessor.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// EnrichWithUserProcessor.cs // EnrichWithActorProcessor.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -12,15 +12,15 @@ using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Events namespace Squidex.Infrastructure.CQRS.Events
{ {
public sealed class EnrichWithUserProcessor : IEventProcessor public sealed class EnrichWithActorProcessor : IEventProcessor
{ {
public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command) public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command)
{ {
var userCommand = command as IUserCommand; var userCommand = command as IActorCommand;
if (userCommand != null) if (userCommand != null)
{ {
@event.SetUser(userCommand.User); @event.SetActor(userCommand.Actor);
} }
return TaskHelper.Done; return TaskHelper.Done;

8
src/Squidex.Infrastructure/Json/UserTokenConverter.cs → src/Squidex.Infrastructure/Json/RefTokenConverter.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// UserTokenConverter.cs // RefTokenConverter.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -11,7 +11,7 @@ using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json namespace Squidex.Infrastructure.Json
{ {
public sealed class UserTokenConverter : JsonConverter public sealed class RefTokenConverter : JsonConverter
{ {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
@ -20,12 +20,12 @@ namespace Squidex.Infrastructure.Json
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
return reader.TokenType == JsonToken.Null ? null : UserToken.Parse((string)reader.Value); return reader.TokenType == JsonToken.Null ? null : RefToken.Parse((string)reader.Value);
} }
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(UserToken); return objectType == typeof(RefToken);
} }
} }
} }

14
src/Squidex.Infrastructure/UserToken.cs → src/Squidex.Infrastructure/RefToken.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// UserToken.cs // RefToken.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -11,13 +11,13 @@ using System.Linq;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
public sealed class UserToken : IEquatable<UserToken> public sealed class RefToken : IEquatable<RefToken>
{ {
public string Type { get; } public string Type { get; }
public string Identifier { get; } public string Identifier { get; }
public UserToken(string type, string identifier) public RefToken(string type, string identifier)
{ {
Guard.NotNullOrEmpty(type, nameof(type)); Guard.NotNullOrEmpty(type, nameof(type));
Guard.NotNullOrEmpty(identifier, nameof(identifier)); Guard.NotNullOrEmpty(identifier, nameof(identifier));
@ -27,7 +27,7 @@ namespace Squidex.Infrastructure
Identifier = identifier; Identifier = identifier;
} }
public static UserToken Parse(string input) public static RefToken Parse(string input)
{ {
Guard.NotNullOrEmpty(input, nameof(input)); Guard.NotNullOrEmpty(input, nameof(input));
@ -38,7 +38,7 @@ namespace Squidex.Infrastructure
throw new ArgumentException("Input must have more than 2 parts divided by colon", nameof(input)); throw new ArgumentException("Input must have more than 2 parts divided by colon", nameof(input));
} }
return new UserToken(parts[0], string.Join(":", parts.Skip(1))); return new RefToken(parts[0], string.Join(":", parts.Skip(1)));
} }
public override string ToString() public override string ToString()
@ -48,10 +48,10 @@ namespace Squidex.Infrastructure
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return Equals(obj as UserToken); return Equals(obj as RefToken);
} }
public bool Equals(UserToken other) public bool Equals(RefToken other)
{ {
return other != null && (ReferenceEquals(this, other) || (Type.Equals(other.Type) && Identifier.Equals(other.Identifier))); return other != null && (ReferenceEquals(this, other) || (Type.Equals(other.Type) && Identifier.Equals(other.Identifier)));
} }

3
src/Squidex.Read/History/IHistoryEventEntity.cs

@ -7,6 +7,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure;
namespace Squidex.Read.History namespace Squidex.Read.History
{ {
@ -15,5 +16,7 @@ namespace Squidex.Read.History
Guid EventId { get; } Guid EventId { get; }
string Message { get; } string Message { get; }
RefToken Actor { get; }
} }
} }

4
src/Squidex.Read/IAppEntity.cs → src/Squidex.Read/IAppRefEntity.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// IAppEntity.cs // IAppRefEntity.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -10,7 +10,7 @@ using System;
namespace Squidex.Read namespace Squidex.Read
{ {
public interface IAppEntity : IEntity public interface IAppRefEntity : IEntity
{ {
Guid AppId { get; set; } Guid AppId { get; set; }
} }

13
src/Squidex.Read/ITrackCreatedByEntity.cs

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Read
{
public interface ITrackCreatedByEntity
{
RefToken CreatedBy { get; set; }
}
}

17
src/Squidex.Read/ITrackLastModifiedByEntity.cs

@ -0,0 +1,17 @@
// ==========================================================================
// ITrackLastModifiedByEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Read
{
public interface ITrackLastModifiedByEntity
{
RefToken LastModifiedBy { get; set; }
}
}

2
src/Squidex.Read/Schemas/Repositories/ISchemaEntity.cs

@ -7,7 +7,7 @@
// ========================================================================== // ==========================================================================
namespace Squidex.Read.Schemas.Repositories namespace Squidex.Read.Schemas.Repositories
{ {
public interface ISchemaEntity : IAppEntity public interface ISchemaEntity : IAppRefEntity
{ {
string Name { get; } string Name { get; }
} }

4
src/Squidex.Store.MongoDb/History/MessagesEN.cs

@ -19,11 +19,11 @@ namespace Squidex.Store.MongoDb.History
{ {
{ {
TypeNameRegistry.GetName<AppContributorAssigned>(), TypeNameRegistry.GetName<AppContributorAssigned>(),
"[User] assigned [Contributor] to app with permission [Permission]" "assigned {user:[Contributor]} to app with permission [Permission]"
}, },
{ {
TypeNameRegistry.GetName<AppContributorRemoved>(), TypeNameRegistry.GetName<AppContributorRemoved>(),
"[User] removed [Contributor] from app" "removed {user:[Contributor]} from app"
} }
}; };
} }

22
src/Squidex.Store.MongoDb/History/MongoHistoryEventEntity.cs

@ -11,11 +11,12 @@ using System.Collections.Generic;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Read;
using Squidex.Store.MongoDb.Utils; using Squidex.Store.MongoDb.Utils;
namespace Squidex.Store.MongoDb.History namespace Squidex.Store.MongoDb.History
{ {
public sealed class MongoHistoryEventEntity : MongoEntity public sealed class MongoHistoryEventEntity : MongoEntity, IAppRefEntity, ITrackCreatedByEntity
{ {
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
@ -31,12 +32,24 @@ namespace Squidex.Store.MongoDb.History
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public UserToken User { get; set; } public RefToken Actor { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public Dictionary<string, string> Parameters { get; set; } public Dictionary<string, string> Parameters { get; set; }
RefToken ITrackCreatedByEntity.CreatedBy
{
get
{
return Actor;
}
set
{
Actor = value;
}
}
public MongoHistoryEventEntity() public MongoHistoryEventEntity()
{ {
Parameters = new Dictionary<string, string>(); Parameters = new Dictionary<string, string>();
@ -46,11 +59,6 @@ namespace Squidex.Store.MongoDb.History
{ {
Channel = channel; Channel = channel;
if (headers.Contains(CommonHeaders.User))
{
AddParameter("User", headers[CommonHeaders.User].ToString());
}
Message = TypeNameRegistry.GetName<T>(); Message = TypeNameRegistry.GetName<T>();
return this; return this;

10
src/Squidex.Store.MongoDb/History/MongoHistoryEventRepository.cs

@ -44,7 +44,7 @@ namespace Squidex.Store.MongoDb.History
public async Task<List<IHistoryEventEntity>> QueryEventsByChannel(Guid appId, string channelPrefix, int count) public async Task<List<IHistoryEventEntity>> QueryEventsByChannel(Guid appId, string channelPrefix, int count)
{ {
var entities = var entities =
await Collection.Find(x => x.AppId == appId && x.Channel.StartsWith(channelPrefix)).Limit(count).ToListAsync(); await Collection.Find(x => x.AppId == appId && x.Channel.StartsWith(channelPrefix)).SortByDescending(x => x.Created).Limit(count).ToListAsync();
return entities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, MessagesEN.Texts)).ToList(); return entities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, MessagesEN.Texts)).ToList();
} }
@ -53,23 +53,23 @@ namespace Squidex.Store.MongoDb.History
{ {
return Collection.CreateAsync(headers, x => return Collection.CreateAsync(headers, x =>
{ {
var channel = $"Apps.{headers.AggregateId()}.Contributors"; const string channel = "settings.contributors";
x.Setup<AppContributorAssigned>(headers, channel) x.Setup<AppContributorAssigned>(headers, channel)
.AddParameter("Contributor", @event.ContributorId) .AddParameter("Contributor", @event.ContributorId)
.AddParameter("Permission", @event.Permission.ToString()); .AddParameter("Permission", @event.Permission.ToString());
}); }, false);
} }
protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers) protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers)
{ {
return Collection.CreateAsync(headers, x => return Collection.CreateAsync(headers, x =>
{ {
var channel = $"Apps.{headers.AggregateId()}.Contributors"; const string channel = "settings.contributors";
x.Setup<AppContributorRemoved>(headers, channel) x.Setup<AppContributorRemoved>(headers, channel)
.AddParameter("Contributor", @event.ContributorId); .AddParameter("Contributor", @event.ContributorId);
}); }, false);
} }
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)

6
src/Squidex.Store.MongoDb/History/ParsedHistoryEvent.cs

@ -8,6 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Infrastructure;
using Squidex.Read.History; using Squidex.Read.History;
namespace Squidex.Store.MongoDb.History namespace Squidex.Store.MongoDb.History
@ -27,6 +28,11 @@ namespace Squidex.Store.MongoDb.History
get { return inner.Id; } get { return inner.Id; }
} }
public RefToken Actor
{
get { return inner.Actor; }
}
public DateTime Created public DateTime Created
{ {
get { return inner.Created; } get { return inner.Created; }

12
src/Squidex.Store.MongoDb/MongoDbModule.cs

@ -23,6 +23,7 @@ using Squidex.Store.MongoDb.History;
using Squidex.Store.MongoDb.Infrastructure; using Squidex.Store.MongoDb.Infrastructure;
using Squidex.Store.MongoDb.Schemas; using Squidex.Store.MongoDb.Schemas;
using Squidex.Store.MongoDb.Users; using Squidex.Store.MongoDb.Users;
using Squidex.Store.MongoDb.Utils;
namespace Squidex.Store.MongoDb namespace Squidex.Store.MongoDb
{ {
@ -30,6 +31,8 @@ namespace Squidex.Store.MongoDb
{ {
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
RefTokenSerializer.Register();
builder.Register(context => builder.Register(context =>
{ {
var options = context.Resolve<IOptions<MyMongoDbOptions>>().Value; var options = context.Resolve<IOptions<MyMongoDbOptions>>().Value;
@ -67,12 +70,13 @@ namespace Squidex.Store.MongoDb
.As<IStreamPositionStorage>() .As<IStreamPositionStorage>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoHistoryEventRepository>()
.As<IHistoryEventRepository>()
.SingleInstance();
builder.RegisterType<MongoUserRepository>() builder.RegisterType<MongoUserRepository>()
.As<IUserRepository>() .As<IUserRepository>()
.InstancePerLifetimeScope();
builder.RegisterType<MongoHistoryEventRepository>()
.As<IHistoryEventRepository>()
.As<ICatchEventConsumer>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoSchemaRepository>() builder.RegisterType<MongoSchemaRepository>()

83
src/Squidex.Store.MongoDb/Utils/EntityMapper.cs

@ -14,25 +14,83 @@ using Newtonsoft.Json.Linq;
using Squidex.Events; using Squidex.Events;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Read; using Squidex.Read;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable SuspiciousTypeConversion.Global
namespace Squidex.Store.MongoDb.Utils namespace Squidex.Store.MongoDb.Utils
{ {
public static class EntityMapper public static class EntityMapper
{ {
public static T Create<T>(EnvelopeHeaders headers) where T : MongoEntity, new() public static T Create<T>(EnvelopeHeaders headers, bool useAggregateId = true) where T : MongoEntity, new()
{ {
var timestamp = headers.Timestamp().ToDateTimeUtc(); var entity = new T();
var entity = new T { Id = headers.AggregateId(), Created = timestamp }; AssignId(headers, entity, useAggregateId);
AssignAppId(headers, entity);
AssignCreated(headers, entity);
AssignCreatedBy(headers, entity);
var appEntity = entity as IAppEntity; return Update(entity, headers);
}
public static T Update<T>(T entity, EnvelopeHeaders headers) where T : MongoEntity
{
AssignLastModified(headers, entity);
AssignLastModifiedBy(headers, entity);
return entity;
}
private static void AssignCreated(EnvelopeHeaders headers, MongoEntity entity)
{
entity.Created = headers.Timestamp().ToDateTimeUtc();
}
private static void AssignLastModified(EnvelopeHeaders headers, MongoEntity entity)
{
entity.LastModified = headers.Timestamp().ToDateTimeUtc();
}
private static void AssignCreatedBy(EnvelopeHeaders headers, MongoEntity entity)
{
var createdBy = entity as ITrackCreatedByEntity;
if (createdBy != null)
{
createdBy.CreatedBy = headers.Actor();
}
}
private static void AssignLastModifiedBy(EnvelopeHeaders headers, MongoEntity entity)
{
var modifiedBy = entity as ITrackLastModifiedByEntity;
if (modifiedBy != null)
{
modifiedBy.LastModifiedBy = headers.Actor();
}
}
private static void AssignAppId(EnvelopeHeaders headers, MongoEntity entity)
{
var appEntity = entity as IAppRefEntity;
if (appEntity != null) if (appEntity != null)
{ {
appEntity.AppId = headers.AppId(); appEntity.AppId = headers.AppId();
} }
}
return Update(entity, headers);
private static void AssignId(EnvelopeHeaders headers, MongoEntity entity, bool useAggregateId)
{
if (useAggregateId)
{
entity.Id = headers.AggregateId();
}
else
{
entity.Id = Guid.NewGuid();
}
} }
public static BsonDocument ToBsonDocument(this JToken value) public static BsonDocument ToBsonDocument(this JToken value)
@ -49,18 +107,9 @@ namespace Squidex.Store.MongoDb.Utils
return JToken.Parse(json); return JToken.Parse(json);
} }
public static T Update<T>(T entity, EnvelopeHeaders headers) where T : MongoEntity public static Task CreateAsync<T>(this IMongoCollection<T> collection, EnvelopeHeaders headers, Action<T> updater, bool useAggregateId = true) where T : MongoEntity, new()
{
var timestamp = headers.Timestamp().ToDateTimeUtc();
entity.LastModified = timestamp;
return entity;
}
public static Task CreateAsync<T>(this IMongoCollection<T> collection, EnvelopeHeaders headers, Action<T> updater) where T : MongoEntity, new()
{ {
var entity = Create<T>(headers); var entity = Create<T>(headers, useAggregateId);
updater(entity); updater(entity);

60
src/Squidex.Store.MongoDb/Utils/RefTokenSerializer.cs

@ -0,0 +1,60 @@
// ==========================================================================
// RefTokenSerializer.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
namespace Squidex.Store.MongoDb.Utils
{
public class RefTokenSerializer : SerializerBase<RefToken>
{
private static bool isRegistered;
private static readonly object LockObject = new object();
public static bool Register()
{
if (!isRegistered)
{
lock (LockObject)
{
if (!isRegistered)
{
BsonSerializer.RegisterSerializer(new RefTokenSerializer());
isRegistered = true;
return true;
}
}
}
return false;
}
public override RefToken Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var value = context.Reader.ReadString();
return value != null ? RefToken.Parse(value) : null;
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, RefToken value)
{
if (value != null)
{
context.Writer.WriteString(value.ToString());
}
else
{
context.Writer.WriteNull();
}
}
}
}

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

@ -207,9 +207,9 @@ namespace Squidex.Write.Apps
return new AppMasterLanguageSet { Language = DefaultLanguage }; return new AppMasterLanguageSet { Language = DefaultLanguage };
} }
private static AppContributorAssigned CreateInitialOwner(IUserCommand command) private static AppContributorAssigned CreateInitialOwner(IActorCommand command)
{ {
return new AppContributorAssigned { ContributorId = command.User.Identifier, Permission = PermissionLevel.Owner }; return new AppContributorAssigned { ContributorId = command.Actor.Identifier, Permission = PermissionLevel.Owner };
} }
private void ThrowIfNotCreated() private void ThrowIfNotCreated()

4
src/Squidex.Write/Apps/Commands/CreateApp.cs

@ -13,11 +13,11 @@ using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Write.Apps.Commands namespace Squidex.Write.Apps.Commands
{ {
public sealed class CreateApp : AggregateCommand, IUserCommand, IValidatable public sealed class CreateApp : AggregateCommand, IActorCommand, IValidatable
{ {
public string Name { get; set; } public string Name { get; set; }
public UserToken User { get; set; } public RefToken Actor { get; set; }
public CreateApp() public CreateApp()
{ {

4
src/Squidex.Write/SquidexCommand.cs

@ -11,8 +11,8 @@ using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Write namespace Squidex.Write
{ {
public abstract class SquidexCommand : AggregateCommand, IUserCommand public abstract class SquidexCommand : AggregateCommand, IActorCommand
{ {
public UserToken User { get; set; } public RefToken Actor { get; set; }
} }
} }

2
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -47,7 +47,7 @@
<div class="nav nav-pills nav-stacked nav-light"> <div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="history" routerLinkActive="active"> <a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-filter"></i> <i class="icon-time"></i>
</a> </a>
</li> </li>
</div> </div>

2
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -67,7 +67,7 @@
<div class="nav nav-pills nav-stacked nav-light"> <div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="history" routerLinkActive="active"> <a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-filter"></i> <i class="icon-time"></i>
</a> </a>
</li> </li>
</div> </div>

2
src/Squidex/app/features/settings/pages/languages/languages-page.component.html

@ -78,7 +78,7 @@
<div class="nav nav-pills nav-stacked nav-light"> <div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="history" routerLinkActive="active"> <a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-filter"></i> <i class="icon-time"></i>
</a> </a>
</li> </li>
</div> </div>

6
src/Squidex/app/framework/utils/date-time.spec.ts

@ -81,6 +81,12 @@ describe('DateTime', () => {
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
it('should convert to local time', () => {
const value = DateTime.parseISO_UTC('2013-10-16T12:13:14');
expect(value.toLocal().ne(value)).toBeTruthy();
});
it('should print to formatted string', () => { it('should print to formatted string', () => {
const value = DateTime.parseISO_UTC('2013-10-16T12:13:14'); const value = DateTime.parseISO_UTC('2013-10-16T12:13:14');
const actual = value.toStringFormat('hh:mm'); const actual = value.toStringFormat('hh:mm');

4
src/Squidex/app/framework/utils/date-time.ts

@ -221,6 +221,10 @@ export class DateTime {
return new DateTime(clone); return new DateTime(clone);
} }
public toLocal(): DateTime {
return new DateTime(new Date(this.value.getTime() - this.value.getTimezoneOffset() * 60 * 1000));
}
public toUTCString(): string { public toUTCString(): string {
return this.value.toUTCString(); return this.value.toUTCString();
} }

4
src/Squidex/app/shared/components/app-form.component.html

@ -8,7 +8,7 @@
<div class="form-group"> <div class="form-group">
<label for="app-name">Name</label> <label for="app-name">Name</label>
<div class="errors-box" *ngIf="createForm.get('name').invalid && createForm.get('name').dirty" [@fade]> <div class="errors-box" *ngIf="createForm.get('name').invalid && createForm.get('name').touched" [@fade]>
<div class="errors"> <div class="errors">
<span *ngIf="createForm.get('name').hasError('required')"> <span *ngIf="createForm.get('name').hasError('required')">
Name is required. Name is required.
@ -33,6 +33,6 @@
<div class="form-group clearfix"> <div class="form-group clearfix">
<button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button> <button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="submit" class="float-xs-right btn btn-primary" [disabled]="createForm.invalid">Create</button> <button type="submit" class="float-xs-right btn btn-primary" >Create</button>
</div> </div>
</form> </form>

2
src/Squidex/app/shared/components/app-form.component.ts

@ -59,7 +59,7 @@ export class AppFormComponent implements OnInit {
} }
public createApp() { public createApp() {
this.createForm.markAsDirty(); this.createForm.markAsTouched();
if (this.createForm.valid) { if (this.createForm.valid) {
this.createForm.disable(); this.createForm.disable();

7
src/Squidex/app/shared/components/history.component.html

@ -11,8 +11,11 @@
<div class="panel-main"> <div class="panel-main">
<div class="panel-content panel-content-blank"> <div class="panel-content panel-content-blank">
<div *ngFor="let event of events"> <div *ngFor="let event of events" class="event">
{{event.message}} <div class="event-created">{{event.created.toLocal() | fromNow}}</div>
<div class="event-message">
<span class="event-actor">{{actorName(event.actor) | async}}</span> <span [innerHTML]="format(event.message) | async"></span>
</div>
</div> </div>
</div> </div>
</div> </div>

20
src/Squidex/app/shared/components/history.component.scss

@ -2,6 +2,22 @@
@import '_mixins'; @import '_mixins';
.panel { .panel {
min-width: 220px; min-width: 250px;
max-width: 1220px80px; max-width: 250px;
}
.event {
& {
margin-bottom: .8rem;
}
&-message {
font-size: .9rem;
}
&-created {
font-size: .65rem;
font-weight: normal;
color: $color-empty;
}
} }

32
src/Squidex/app/shared/components/history.component.ts

@ -7,6 +7,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { ImmutableArray, NotificationService } from 'framework'; import { ImmutableArray, NotificationService } from 'framework';
@ -16,6 +17,8 @@ import { HistoryEventDto, HistoryService } from './../services/history.service';
import { UsersProviderService } from './../services/users-provider.service'; import { UsersProviderService } from './../services/users-provider.service';
const FALLBACK_NAME = 'my-app'; const FALLBACK_NAME = 'my-app';
const REPLACEMENT_REGEXP = new RegExp('{([^\s:]*):([^}]*)}');
const REPLACEMENT_TEMP = '$TEMP$';
@Component({ @Component({
selector: 'sqx-history', selector: 'sqx-history',
@ -56,4 +59,33 @@ export class HistoryComponent extends AppComponentBase implements OnDestroy, OnI
this.events = ImmutableArray.of(dtos); this.events = ImmutableArray.of(dtos);
}); });
} }
public actorName(actor: string): Observable<string> {
const parts = actor.split(':');
if (parts[0] === 'subject') {
return this.userName(parts[1]).map(n => n === 'Me' ? 'I' : n);
}
return Observable.of(parts[1]);
}
public format(message: string): Observable<string> {
let foundUserId: string;
message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => {
if (type === 'user') {
foundUserId = id;
return REPLACEMENT_TEMP;
} else {
return id;
}
});
if (foundUserId) {
return this.userName(foundUserId).map(t => message.replace(REPLACEMENT_TEMP, `<span class="user-ref">${t}</span>`));
}
return Observable.of(message);
}
} }

4
src/Squidex/app/shared/services/history.service.spec.ts

@ -33,12 +33,12 @@ describe('HistoryService', () => {
new Response( new Response(
new ResponseOptions({ new ResponseOptions({
body: [{ body: [{
user: 'User1', actor: 'User1',
eventId: '1', eventId: '1',
message: 'Message 1', message: 'Message 1',
created: '2016-12-12T10:10' created: '2016-12-12T10:10'
}, { }, {
user: 'User2', actor: 'User2',
eventId: '2', eventId: '2',
message: 'Message 2', message: 'Message 2',
created: '2016-12-13T10:10' created: '2016-12-13T10:10'

4
src/Squidex/app/shared/services/history.service.ts

@ -19,7 +19,7 @@ import { AuthService } from './auth.service';
export class HistoryEventDto { export class HistoryEventDto {
constructor( constructor(
public readonly eventId: string, public readonly eventId: string,
public readonly user: string, public readonly actor: string,
public readonly message: string, public readonly message: string,
public readonly created: DateTime public readonly created: DateTime
) { ) {
@ -45,7 +45,7 @@ export class HistoryService {
return items.map(item => { return items.map(item => {
return new HistoryEventDto( return new HistoryEventDto(
item.eventId, item.eventId,
item.user, item.actor,
item.message, item.message,
DateTime.parseISO_UTC(item.created)); DateTime.parseISO_UTC(item.created));
}); });

9
src/Squidex/app/shared/services/users-provider.service.ts

@ -31,12 +31,11 @@ export class UsersProviderService {
.catch(err => { .catch(err => {
return Observable.of(new UserDto('NOT FOUND', 'NOT FOUND', 'NOT FOUND', '')); return Observable.of(new UserDto('NOT FOUND', 'NOT FOUND', 'NOT FOUND', ''));
}) })
.map(u => { .map(dto => {
if (this.authService.user && u.id === this.authService.user.id) { if (this.authService.user && dto.id === this.authService.user.id) {
return new UserDto(u.id, u.email, 'Me', u.pictureUrl); dto = new UserDto(dto.id, dto.email, 'Me', dto.pictureUrl);
} else {
return u;
} }
return dto;
}) })
.publishLast(); .publishLast();

6
src/Squidex/app/theme/_history.scss

@ -0,0 +1,6 @@
@import '_mixins';
@import '_vars';
.user-ref {
color: $color-theme-blue-dark;
}

5
src/Squidex/app/theme/_panels.scss

@ -3,7 +3,7 @@
$panel-padding: 20px; $panel-padding: 20px;
$panel-header: 70px; $panel-header: 70px;
$panel-sidebar: 60px; $panel-sidebar: 61px;
.panel-container { .panel-container {
@include absolute($size-navbar-height, 0, 0, $size-sidebar-width); @include absolute($size-navbar-height, 0, 0, $size-sidebar-width);
@ -68,12 +68,13 @@ $panel-sidebar: 60px;
& .nav-link { & .nav-link {
text-align: center; text-align: center;
color: $color-text;
} }
} }
&-close { &-close {
& { & {
@include absolute($panel-padding, $panel-padding, auto, auto); @include absolute($panel-padding, $panel-padding - 2px, auto, auto);
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 400; font-weight: 400;
cursor: pointer; cursor: pointer;

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

1
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -8,6 +8,7 @@
<missing-glyph horiz-adv-x="1024" /> <missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" /> <glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe5cd;" glyph-name="close" d="M810 664.667l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" /> <glyph unicode="&#xe5cd;" glyph-name="close" d="M810 664.667l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" />
<glyph unicode="&#xe8b5;" glyph-name="time" d="M534 640.667v-224l192-114-32-54-224 136v256h64zM512 84.667c188 0 342 154 342 342s-154 342-342 342-342-154-342-342 154-342 342-342zM512 852.667c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
<glyph unicode="&#xe900;" glyph-name="logo" d="M512.34 939.52c-0.174-0.065-226.41-96.5-283.902-294.74-5.545-19.035 40.453-10.673 38.399-31.6-2.517-25.409-55.264-35.821-48.385-108.78 7.001-74.289 74.617-149.342 84.791-194.72v-31.78c-0.615-9.802-5.639-36.405-22.285-49.4-9.13-7.105-21.442-9.661-37.671-7.78-22.528 2.612-31.493 16.604-35.078 27.9-5.881 18.616-0.409 40.331 12.793 50.52 13.271 10.243 15.084 28.513 4.029 40.82-11.055 12.296-30.785 13.965-44.056 3.7-32.168-24.839-45.65-70.615-32.785-111.34 12.146-38.328 44.789-64.147 87.363-69.080 6.067-0.699 11.848-1.040 17.335-1.040 32.945 0 55.27 11.669 68.785 22.32 40.671 32.105 43.867 85.623 44.099 91.62 0.011 0.355 0.022 0.705 0.022 1.060v24.36h0.129v1.64c0 14.177 12.394 25.66 27.707 25.66 14.869 0 26.889-10.843 27.578-24.46v-232.2c-0.255-3.343-3.155-34.297-22.157-49.28-9.118-7.201-21.512-9.802-37.799-7.9-22.54 2.612-31.526 16.605-35.099 27.88-5.893 18.627-0.387 40.341 12.814 50.52 13.271 10.254 15.062 28.523 4.007 40.84-11.044 12.274-30.764 13.945-44.035 3.68-32.191-24.828-45.65-70.615-32.785-111.34 12.122-38.328 44.789-64.136 87.363-69.080 6.067-0.699 11.848-1.040 17.335-1.040 32.945 0 55.262 11.669 68.742 22.32 40.683 32.105 43.879 85.623 44.099 91.62 0.024 0.376 0.042 0.696 0.042 1.040v259l0.129 0.060v1.14c0 14.456 12.65 26.18 28.264 26.18 15.288 0 27.657-11.292 28.135-25.36v-261.020c0-0.355-0.002-0.675 0.022-1.040 0.232-5.987 3.438-59.515 44.121-91.62 13.504-10.652 35.819-22.32 68.764-22.32 5.499 0 11.258 0.341 17.314 1.040 42.562 4.944 75.24 30.763 87.363 69.080 12.876 40.725-0.584 86.501-32.764 111.34-13.294 10.265-33.013 8.584-44.056-3.68-11.055-12.328-9.264-30.586 4.007-40.84 13.201-10.179 18.697-31.893 12.793-50.52-3.561-11.275-12.55-25.268-35.078-27.88-16.217-1.892-28.531 0.675-37.649 7.78-16.716 13.038-21.715 39.783-22.307 49.36v231.8c0.445 13.816 12.612 24.9 27.642 24.9 15.313 0 27.707-11.472 27.707-25.66v-1.64h0.085v-24.36c0-0.365-0.002-0.716 0.022-1.060 0.22-5.987 3.438-59.515 44.121-91.62 13.503-10.651 35.818-22.32 68.763-22.32 5.487 0 11.259 0.332 17.314 1.020 42.562 4.933 75.24 30.783 87.363 69.1 12.876 40.725-0.606 86.49-32.785 111.34-13.294 10.254-33.003 8.576-44.035-3.72-11.067-12.307-9.285-30.557 3.986-40.8 13.201-10.189 18.719-31.904 12.814-50.52-3.561-11.296-12.571-25.299-35.099-27.9-16.194-1.892-28.51 0.686-37.628 7.78-16.716 13.048-21.727 39.785-22.307 49.34v24.24c6.634 62.066 78.084 123.637 85.499 202.32 6.844 72.959-45.943 83.371-48.449 108.78-2.065 20.927 43.943 12.565 38.421 31.6-57.503 198.24-283.718 294.675-283.88 294.74z" /> <glyph unicode="&#xe900;" glyph-name="logo" d="M512.34 939.52c-0.174-0.065-226.41-96.5-283.902-294.74-5.545-19.035 40.453-10.673 38.399-31.6-2.517-25.409-55.264-35.821-48.385-108.78 7.001-74.289 74.617-149.342 84.791-194.72v-31.78c-0.615-9.802-5.639-36.405-22.285-49.4-9.13-7.105-21.442-9.661-37.671-7.78-22.528 2.612-31.493 16.604-35.078 27.9-5.881 18.616-0.409 40.331 12.793 50.52 13.271 10.243 15.084 28.513 4.029 40.82-11.055 12.296-30.785 13.965-44.056 3.7-32.168-24.839-45.65-70.615-32.785-111.34 12.146-38.328 44.789-64.147 87.363-69.080 6.067-0.699 11.848-1.040 17.335-1.040 32.945 0 55.27 11.669 68.785 22.32 40.671 32.105 43.867 85.623 44.099 91.62 0.011 0.355 0.022 0.705 0.022 1.060v24.36h0.129v1.64c0 14.177 12.394 25.66 27.707 25.66 14.869 0 26.889-10.843 27.578-24.46v-232.2c-0.255-3.343-3.155-34.297-22.157-49.28-9.118-7.201-21.512-9.802-37.799-7.9-22.54 2.612-31.526 16.605-35.099 27.88-5.893 18.627-0.387 40.341 12.814 50.52 13.271 10.254 15.062 28.523 4.007 40.84-11.044 12.274-30.764 13.945-44.035 3.68-32.191-24.828-45.65-70.615-32.785-111.34 12.122-38.328 44.789-64.136 87.363-69.080 6.067-0.699 11.848-1.040 17.335-1.040 32.945 0 55.262 11.669 68.742 22.32 40.683 32.105 43.879 85.623 44.099 91.62 0.024 0.376 0.042 0.696 0.042 1.040v259l0.129 0.060v1.14c0 14.456 12.65 26.18 28.264 26.18 15.288 0 27.657-11.292 28.135-25.36v-261.020c0-0.355-0.002-0.675 0.022-1.040 0.232-5.987 3.438-59.515 44.121-91.62 13.504-10.652 35.819-22.32 68.764-22.32 5.499 0 11.258 0.341 17.314 1.040 42.562 4.944 75.24 30.763 87.363 69.080 12.876 40.725-0.584 86.501-32.764 111.34-13.294 10.265-33.013 8.584-44.056-3.68-11.055-12.328-9.264-30.586 4.007-40.84 13.201-10.179 18.697-31.893 12.793-50.52-3.561-11.275-12.55-25.268-35.078-27.88-16.217-1.892-28.531 0.675-37.649 7.78-16.716 13.038-21.715 39.783-22.307 49.36v231.8c0.445 13.816 12.612 24.9 27.642 24.9 15.313 0 27.707-11.472 27.707-25.66v-1.64h0.085v-24.36c0-0.365-0.002-0.716 0.022-1.060 0.22-5.987 3.438-59.515 44.121-91.62 13.503-10.651 35.818-22.32 68.763-22.32 5.487 0 11.259 0.332 17.314 1.020 42.562 4.933 75.24 30.783 87.363 69.1 12.876 40.725-0.606 86.49-32.785 111.34-13.294 10.254-33.003 8.576-44.035-3.72-11.067-12.307-9.285-30.557 3.986-40.8 13.201-10.189 18.719-31.904 12.814-50.52-3.561-11.296-12.571-25.299-35.099-27.9-16.194-1.892-28.51 0.686-37.628 7.78-16.716 13.048-21.727 39.785-22.307 49.34v24.24c6.634 62.066 78.084 123.637 85.499 202.32 6.844 72.959-45.943 83.371-48.449 108.78-2.065 20.927 43.943 12.565 38.421 31.6-57.503 198.24-283.718 294.675-283.88 294.74z" />
<glyph unicode="&#xe901;" glyph-name="plus" d="M810 384.667h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" /> <glyph unicode="&#xe901;" glyph-name="plus" d="M810 384.667h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
<glyph unicode="&#xe905;" glyph-name="pencil" d="M864 960c88.364 0 160-71.634 160-160 0-36.020-11.91-69.258-32-96l-64-64-224 224 64 64c26.742 20.090 59.978 32 96 32zM64 224l-64-288 288 64 592 592-224 224-592-592zM715.578 596.422l-448-448-55.156 55.156 448 448 55.156-55.156z" /> <glyph unicode="&#xe905;" glyph-name="pencil" d="M864 960c88.364 0 160-71.634 160-160 0-36.020-11.91-69.258-32-96l-64-64-224 224 64 64c26.742 20.090 59.978 32 96 32zM64 224l-64-288 288 64 592 592-224 224-592-592zM715.578 596.422l-448-448-55.156 55.156 448 448 55.156-55.156z" />

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

27
src/Squidex/app/theme/icomoon/selection.json

@ -1,6 +1,33 @@
{ {
"IcoMoonType": "selection", "IcoMoonType": "selection",
"icons": [ "icons": [
{
"icon": {
"paths": [
"M534 298v224l192 114-32 54-224-136v-256h64zM512 854c188 0 342-154 342-342s-154-342-342-342-342 154-342 342 154 342 342 342zM512 86c236 0 426 190 426 426s-190 426-426 426-426-190-426-426 190-426 426-426z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"schedule"
],
"defaultCode": 59573,
"grid": 24
},
"attrs": [],
"properties": {
"order": 17,
"ligatures": "access_time, query_builder, schedule",
"id": 4,
"prevSize": 24,
"code": 59573,
"name": "time"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 4
},
{ {
"icon": { "icon": {
"paths": [ "paths": [

13
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
src: url('fonts/icomoon.eot?sylov9'); src: url('fonts/icomoon.eot?2yz9dj');
src: url('fonts/icomoon.eot?sylov9#iefix') format('embedded-opentype'), src: url('fonts/icomoon.eot?2yz9dj#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?sylov9') format('truetype'), url('fonts/icomoon.ttf?2yz9dj') format('truetype'),
url('fonts/icomoon.woff?sylov9') format('woff'), url('fonts/icomoon.woff?2yz9dj') format('woff'),
url('fonts/icomoon.svg?sylov9#icomoon') format('svg'); url('fonts/icomoon.svg?2yz9dj#icomoon') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -24,6 +24,9 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-time:before {
content: "\e8b5";
}
.icon-close:before { .icon-close:before {
content: "\e5cd"; content: "\e5cd";
} }

3
src/Squidex/app/theme/theme.scss

@ -1,3 +1,4 @@
@import '_bootstrap.scss'; @import '_bootstrap.scss';
@import '_layout.scss'; @import '_layout.scss';
@import '_panels.scss'; @import '_panels.scss';
@import '_history.scss';

10
tests/Squidex.Infrastructure.Tests/CQRS/EnvelopeExtensionsTests.cs

@ -63,14 +63,14 @@ namespace Squidex.Infrastructure.CQRS
} }
[Fact] [Fact]
public void Should_set_and_get_user() public void Should_set_and_get_actor()
{ {
var user = new UserToken("subject", "123"); var actor = new RefToken("subject", "123");
sut.SetUser(user); sut.SetActor(actor);
Assert.Equal(user, sut.Headers.User()); Assert.Equal(actor, sut.Headers.Actor());
Assert.Equal(user, UserToken.Parse(sut.Headers["User"].ToString())); Assert.Equal(actor, RefToken.Parse(sut.Headers["User"].ToString()));
} }
[Fact] [Fact]

18
tests/Squidex.Infrastructure.Tests/CQRS/Events/EnrichWithUserProcessorTests.cs → tests/Squidex.Infrastructure.Tests/CQRS/Events/EnrichWithActorProcessorTests.cs

@ -12,11 +12,11 @@ using Xunit;
namespace Squidex.Infrastructure.CQRS.Events namespace Squidex.Infrastructure.CQRS.Events
{ {
public class EnrichWithUserProcessorTests public class EnrichWithActorProcessorTests
{ {
public sealed class MyUserCommand : IUserCommand public sealed class MyActorCommand : IActorCommand
{ {
public UserToken User { get; set; } public RefToken Actor { get; set; }
} }
public sealed class MyNormalCommand : ICommand public sealed class MyNormalCommand : ICommand
@ -27,10 +27,10 @@ namespace Squidex.Infrastructure.CQRS.Events
{ {
} }
private readonly EnrichWithUserProcessor sut = new EnrichWithUserProcessor(); private readonly EnrichWithActorProcessor sut = new EnrichWithActorProcessor();
[Fact] [Fact]
public async Task Should_not_do_anything_if_not_user_command() public async Task Should_not_do_anything_if_not_actor_command()
{ {
var envelope = new Envelope<IEvent>(new MyEvent()); var envelope = new Envelope<IEvent>(new MyEvent());
@ -42,14 +42,14 @@ namespace Squidex.Infrastructure.CQRS.Events
[Fact] [Fact]
public async Task Should_attach_user_to_event_envelope() public async Task Should_attach_user_to_event_envelope()
{ {
var user = new UserToken("subject", "123"); var actorToken = new RefToken("subject", "123");
var userCommand = new MyUserCommand { User = user }; var actorCommand = new MyActorCommand { Actor = actorToken };
var envelope = new Envelope<IEvent>(new MyEvent()); var envelope = new Envelope<IEvent>(new MyEvent());
await sut.ProcessEventAsync(envelope, null, userCommand); await sut.ProcessEventAsync(envelope, null, actorCommand);
Assert.Equal(user, envelope.Headers.User()); Assert.Equal(actorToken, envelope.Headers.Actor());
} }
} }
} }

46
tests/Squidex.Infrastructure.Tests/UserTokenTests.cs → tests/Squidex.Infrastructure.Tests/RefTokenTests.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// UserTokenTests.cs // RefTokenTests.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -14,13 +14,13 @@ using Xunit;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
public class UserTokenTests public class RefTokenTests
{ {
private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
static UserTokenTests() static RefTokenTests()
{ {
serializerSettings.Converters.Add(new UserTokenConverter()); serializerSettings.Converters.Add(new RefTokenConverter());
serializerSettings.NullValueHandling = NullValueHandling.Include; serializerSettings.NullValueHandling = NullValueHandling.Include;
} }
@ -31,13 +31,13 @@ namespace Squidex.Infrastructure
[InlineData("user")] [InlineData("user")]
public void Should_throw_if_parsing_invalid_input(string input) public void Should_throw_if_parsing_invalid_input(string input)
{ {
Assert.Throws<ArgumentException>(() => UserToken.Parse(input)); Assert.Throws<ArgumentException>(() => RefToken.Parse(input));
} }
[Fact] [Fact]
public void Should_instantiate_token() public void Should_instantiate_token()
{ {
var token = new UserToken("client", "client1"); var token = new RefToken("client", "client1");
Assert.Equal("client", token.Type); Assert.Equal("client", token.Type);
Assert.Equal("client1", token.Identifier); Assert.Equal("client1", token.Identifier);
@ -46,7 +46,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_instantiate_token_and_lower_type() public void Should_instantiate_token_and_lower_type()
{ {
var token = new UserToken("Client", "client1"); var token = new RefToken("Client", "client1");
Assert.Equal("client", token.Type); Assert.Equal("client", token.Type);
Assert.Equal("client1", token.Identifier); Assert.Equal("client1", token.Identifier);
@ -55,7 +55,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_parse_user_token_from_string() public void Should_parse_user_token_from_string()
{ {
var token = UserToken.Parse("client:client1"); var token = RefToken.Parse("client:client1");
Assert.Equal("client", token.Type); Assert.Equal("client", token.Type);
Assert.Equal("client1", token.Identifier); Assert.Equal("client1", token.Identifier);
@ -64,7 +64,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_parse_user_token_with_colon_in_identifier() public void Should_parse_user_token_with_colon_in_identifier()
{ {
var token = UserToken.Parse("client:client1:app"); var token = RefToken.Parse("client:client1:app");
Assert.Equal("client", token.Type); Assert.Equal("client", token.Type);
Assert.Equal("client1:app", token.Identifier); Assert.Equal("client1:app", token.Identifier);
@ -73,7 +73,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_convert_user_token_to_string() public void Should_convert_user_token_to_string()
{ {
var token = UserToken.Parse("client:client1"); var token = RefToken.Parse("client:client1");
Assert.Equal("client:client1", token.ToString()); Assert.Equal("client:client1", token.ToString());
} }
@ -81,9 +81,9 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_make_correct_equal_comparisons() public void Should_make_correct_equal_comparisons()
{ {
var token1a = UserToken.Parse("client:client1"); var token1a = RefToken.Parse("client:client1");
var token1b = UserToken.Parse("client:client1"); var token1b = RefToken.Parse("client:client1");
var token2 = UserToken.Parse("client:client2"); var token2 = RefToken.Parse("client:client2");
Assert.True(token1a.Equals(token1b)); Assert.True(token1a.Equals(token1b));
@ -93,10 +93,10 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_make_correct_object_equal_comparisons() public void Should_make_correct_object_equal_comparisons()
{ {
var token1a = UserToken.Parse("client:client1"); var token1a = RefToken.Parse("client:client1");
object token1b = UserToken.Parse("client:client1"); object token1b = RefToken.Parse("client:client1");
object token2 = UserToken.Parse("client:client2"); object token2 = RefToken.Parse("client:client2");
Assert.True(token1a.Equals(token1b)); Assert.True(token1a.Equals(token1b));
@ -106,9 +106,9 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_provide_correct_hash_codes() public void Should_provide_correct_hash_codes()
{ {
var token1a = UserToken.Parse("client:client1"); var token1a = RefToken.Parse("client:client1");
var token1b = UserToken.Parse("client:client1"); var token1b = RefToken.Parse("client:client1");
var token2 = UserToken.Parse("client:client2"); var token2 = RefToken.Parse("client:client2");
Assert.Equal(token1a.GetHashCode(), token1b.GetHashCode()); Assert.Equal(token1a.GetHashCode(), token1b.GetHashCode());
@ -118,9 +118,9 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_serialize_and_deserialize_null_token() public void Should_serialize_and_deserialize_null_token()
{ {
var input = Tuple.Create<UserToken>(null); var input = Tuple.Create<RefToken>(null);
var json = JsonConvert.SerializeObject(input, serializerSettings); var json = JsonConvert.SerializeObject(input, serializerSettings);
var output = JsonConvert.DeserializeObject<Tuple<UserToken>>(json, serializerSettings); var output = JsonConvert.DeserializeObject<Tuple<RefToken>>(json, serializerSettings);
Assert.Equal(output.Item1, input.Item1); Assert.Equal(output.Item1, input.Item1);
} }
@ -128,9 +128,9 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_serialize_and_deserialize_valid_token() public void Should_serialize_and_deserialize_valid_token()
{ {
var input = Tuple.Create(UserToken.Parse("client:client1")); var input = Tuple.Create(RefToken.Parse("client:client1"));
var json = JsonConvert.SerializeObject(input, serializerSettings); var json = JsonConvert.SerializeObject(input, serializerSettings);
var output = JsonConvert.DeserializeObject<Tuple<UserToken>>(json, serializerSettings); var output = JsonConvert.DeserializeObject<Tuple<RefToken>>(json, serializerSettings);
Assert.Equal(output.Item1, input.Item1); Assert.Equal(output.Item1, input.Item1);
} }

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

@ -32,7 +32,7 @@ namespace Squidex.Write.Apps
private readonly Mock<IUserRepository> userRepository = new Mock<IUserRepository>(); private readonly Mock<IUserRepository> userRepository = new Mock<IUserRepository>();
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 RefToken subjectId = new RefToken("subject", Guid.NewGuid().ToString());
private readonly DateTime expiresUtc = DateTime.UtcNow.AddYears(1); private readonly DateTime expiresUtc = DateTime.UtcNow.AddYears(1);
private readonly Language language = Language.GetLanguage("de"); private readonly Language language = Language.GetLanguage("de");
private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string contributorId = Guid.NewGuid().ToString();
@ -50,7 +50,7 @@ namespace Squidex.Write.Apps
[Fact] [Fact]
public async Task Create_should_throw_if_a_name_with_same_name_already_exists() public async Task Create_should_throw_if_a_name_with_same_name_already_exists()
{ {
var command = new CreateApp { Name = appName, AggregateId = Id, User = subjectId }; var command = new CreateApp { Name = appName, AggregateId = Id, Actor = subjectId };
var context = new CommandContext(command); var context = new CommandContext(command);
appRepository.Setup(x => x.FindAppByNameAsync(appName)).Returns(Task.FromResult(new Mock<IAppEntity>().Object)).Verifiable(); appRepository.Setup(x => x.FindAppByNameAsync(appName)).Returns(Task.FromResult(new Mock<IAppEntity>().Object)).Verifiable();
@ -66,7 +66,7 @@ namespace Squidex.Write.Apps
[Fact] [Fact]
public async Task Create_should_create_app_if_name_is_free() public async Task Create_should_create_app_if_name_is_free()
{ {
var command = new CreateApp { Name = appName, AggregateId = Id, User = subjectId }; var command = new CreateApp { Name = appName, AggregateId = Id, Actor = subjectId };
var context = new CommandContext(command); var context = new CommandContext(command);
appRepository.Setup(x => x.FindAppByNameAsync(appName)).Returns(Task.FromResult<IAppEntity>(null)).Verifiable(); appRepository.Setup(x => x.FindAppByNameAsync(appName)).Returns(Task.FromResult<IAppEntity>(null)).Verifiable();
@ -241,7 +241,7 @@ namespace Squidex.Write.Apps
private AppDomainObject CreateApp() private AppDomainObject CreateApp()
{ {
app.Create(new CreateApp { Name = appName, User = subjectId }); app.Create(new CreateApp { Name = appName, Actor = subjectId });
return app; return app;
} }

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

@ -25,7 +25,7 @@ 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 RefToken user = new RefToken("subject", Guid.NewGuid().ToString());
private readonly DateTime expiresUtc = DateTime.UtcNow.AddYears(1); 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();
@ -54,7 +54,7 @@ namespace Squidex.Write.Apps
[Fact] [Fact]
public void Create_should_specify_name_and_owner() public void Create_should_specify_name_and_owner()
{ {
sut.Create(new CreateApp { Name = TestName, User = user }); sut.Create(new CreateApp { Name = TestName, Actor = user });
Assert.Equal(TestName, sut.Name); Assert.Equal(TestName, sut.Name);
@ -410,7 +410,7 @@ namespace Squidex.Write.Apps
private void CreateApp() private void CreateApp()
{ {
sut.Create(new CreateApp { Name = TestName, User = user }); sut.Create(new CreateApp { Name = TestName, Actor = user });
((IAggregate)sut).ClearUncommittedEvents(); ((IAggregate)sut).ClearUncommittedEvents();
} }

Loading…
Cancel
Save