From 3d5c5b61358820d441b7f2fab96eeb05a5421572 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 3 Jul 2018 17:41:26 +0200 Subject: [PATCH] Medium task improved. --- .../Rules/Actions/MediumAction.cs | 4 - .../Actions/FastlyActionHandler.cs | 11 ++- .../Actions/MediumActionHandler.cs | 58 +++++++++----- .../HandleRules/Actions/SlackActionHandler.cs | 8 +- .../Actions/WebhookActionHandler.cs | 8 +- .../Apps/AppGrain.cs | 6 +- .../Assets/AssetGrain.cs | 18 ++--- .../Assets/IAssetGrain.cs | 3 +- .../Contents/ContentGrain.cs | 14 ++-- .../Contents/ContentVersionLoader.cs | 35 +++------ .../Contents/IContentGrain.cs | 3 +- .../Rules/EventEnricher.cs | 7 +- .../Rules/Guards/RuleActionValidator.cs | 5 -- .../Schemas/SchemaGrain.cs | 6 +- .../SquidexDomainObjectGrainLogSnapshots.cs | 34 +++++++++ .../Commands/DomainObjectGrain.cs | 6 +- .../Commands/DomainObjectGrainBase.cs | 5 -- .../Commands/LogSnapshotDomainObjectGrain.cs | 6 +- .../Http/DumpFormatter.cs | 12 ++- ...{MediumactionDto.cs => MediumActionDto.cs} | 10 --- .../actions/medium-action.component.html | 28 ------- .../rules/actions/medium-action.component.ts | 6 -- .../Contents/ContentVersionLoaderTests.cs | 75 +++++++++++++++++++ .../Rules/Guards/Actions/MediumActionTests.cs | 22 +----- .../TestHelpers/HandlerTestBase.cs | 16 +++- .../LogSnapshotDomainObjectGrainTests.cs | 6 +- 26 files changed, 239 insertions(+), 173 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/SquidexDomainObjectGrainLogSnapshots.cs rename src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/{MediumactionDto.cs => MediumActionDto.cs} (88%) create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/MediumAction.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/MediumAction.cs index 003bcfd51..406bff8a9 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/MediumAction.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/MediumAction.cs @@ -14,10 +14,6 @@ namespace Squidex.Domain.Apps.Core.Rules.Actions { public string AccessToken { get; set; } - public string Author { get; set; } - - public string Publication { get; set; } - public string Tags { get; set; } public string Title { get; set; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs index 937674741..55bc26a8d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs @@ -10,6 +10,7 @@ using System.Net.Http; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Infrastructure; using Squidex.Infrastructure.Http; #pragma warning disable SA1649 // File name must match first type name @@ -47,22 +48,24 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions return (null, new InvalidOperationException("The action cannot handle this event.")); } - var requestMsg = BuildRequest(job); + var request = BuildRequest(job); HttpResponseMessage response = null; try { - response = await HttpClientPool.GetHttpClient().SendAsync(requestMsg); + var valueWatch = ValueStopwatch.StartNew(); + + response = await HttpClientPool.GetHttpClient().SendAsync(request); var responseString = await response.Content.ReadAsStringAsync(); - var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, responseString, TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(request, response, null, responseString, TimeSpan.Zero, false); return (requestDump, null); } catch (Exception ex) { - var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, ex.ToString(), TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(request, response, null, ex.ToString(), TimeSpan.Zero, false); return (requestDump, ex); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs index ba089042f..899736df2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs @@ -22,8 +22,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions { public sealed class MediumJob { - public string RequestUrl { get; set; } - public string RequestBody { get; set; } public string AccessToken { get; set; } @@ -44,11 +42,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action) { - var requestUrl = - !string.IsNullOrWhiteSpace(action.Author) ? - $"https://api.medium.com/v1/users/{action.Author}/posts" : - $"https://api.medium.com/v1/publication/{action.Publication}/posts"; - var requestBody = new JObject( new JProperty("title", formatter.Format(action.Title, @event)), @@ -57,12 +50,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions new JProperty("canonicalUrl", formatter.Format(action.CanonicalUrl, @event)), new JProperty("tags", ParseTags(@event, action))); - var ruleJob = new MediumJob - { - AccessToken = action.AccessToken, - RequestUrl = requestUrl, - RequestBody = requestBody.ToString(Formatting.Indented) - }; + var ruleJob = new MediumJob { AccessToken = action.AccessToken, RequestBody = requestBody.ToString(Formatting.Indented) }; return (Description, ruleJob); } @@ -91,17 +79,36 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job) { - var requestBody = job.RequestBody; - var requestMessage = BuildRequest(job, requestBody); + string id; HttpResponseMessage response = null; + var meRequest = BuildMeRequest(job); + try + { + response = await HttpClientPool.GetHttpClient().SendAsync(meRequest); + + var responseString = await response.Content.ReadAsStringAsync(); + var responseJson = JToken.Parse(responseString); + + id = responseJson["data"]["id"].ToString(); + } + catch (Exception ex) + { + var requestDump = DumpFormatter.BuildDump(meRequest, response, ex.ToString()); + + return (requestDump, ex); + } + + var postRequestBody = job.RequestBody; + var postRequest = BuildPostRequest(job, postRequestBody, id); + try { - response = await HttpClientPool.GetHttpClient().SendAsync(requestMessage); + response = await HttpClientPool.GetHttpClient().SendAsync(postRequest); var responseString = await response.Content.ReadAsStringAsync(); - var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, responseString, TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(postRequest, response, postRequestBody, responseString); Exception ex = null; @@ -114,15 +121,15 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions } catch (Exception ex) { - var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, ex.ToString(), TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(postRequest, response, postRequestBody, ex.ToString()); return (requestDump, ex); } } - private static HttpRequestMessage BuildRequest(MediumJob job, string requestBody) + private static HttpRequestMessage BuildPostRequest(MediumJob job, string requestBody, string id) { - var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) + var request = new HttpRequestMessage(HttpMethod.Post, $"https://api.medium.com/v1/users/{id}/posts") { Content = new StringContent(requestBody, Encoding.UTF8, "application/json") }; @@ -133,5 +140,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions return request; } + + private static HttpRequestMessage BuildMeRequest(MediumJob job) + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://api.medium.com/v1/me"); + + request.Headers.Add("Accept", "application/json"); + request.Headers.Add("Accept-Charset", "utf-8"); + request.Headers.Add("Authorization", $"Bearer {job.AccessToken}"); + + return request; + } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs index c7f64fbe4..4682e2fb7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs @@ -67,22 +67,22 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(SlackJob job) { var requestBody = job.Body; - var requestMessage = BuildRequest(job, requestBody); + var request = BuildRequest(job, requestBody); HttpResponseMessage response = null; try { - response = await HttpClientPool.GetHttpClient().SendAsync(requestMessage); + response = await HttpClientPool.GetHttpClient().SendAsync(request); var responseString = await response.Content.ReadAsStringAsync(); - var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, responseString, TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(request, response, requestBody, responseString); return (requestDump, null); } catch (Exception ex) { - var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, ex.ToString(), TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(request, response, requestBody, ex.ToString()); return (requestDump, ex); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs index 385d47530..47cd65ee7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs @@ -69,16 +69,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(WebhookJob job) { var requestBody = job.Body; - var requestMessage = BuildRequest(job, requestBody); + var request = BuildRequest(job, requestBody); HttpResponseMessage response = null; try { - response = await HttpClientPool.GetHttpClient().SendAsync(requestMessage); + response = await HttpClientPool.GetHttpClient().SendAsync(request); var responseString = await response.Content.ReadAsStringAsync(); - var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, responseString, TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(request, response, requestBody, responseString); Exception ex = null; @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions } catch (Exception ex) { - var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, ex.ToString(), TimeSpan.Zero, false); + var requestDump = DumpFormatter.BuildDump(request, response, requestBody, ex.ToString()); return (requestDump, ex); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs index 3ffb37205..4ebf19747 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs @@ -72,14 +72,14 @@ namespace Squidex.Domain.Apps.Entities.Apps }); case AssignContributor assigneContributor: - return UpdateReturnAsync(assigneContributor, async c => + return UpdateReturnAsync(assigneContributor, (Func>)(async c => { await GuardAppContributors.CanAssign(Snapshot.Contributors, c, userResolver, appPlansProvider.GetPlan(Snapshot.Plan?.PlanId)); AssignContributor(c); - return EntityCreatedResult.Create(c.ContributorId, NewVersion); - }); + return EntityCreatedResult.Create(c.ContributorId, (long)base.Version); + })); case RemoveContributor removeContributor: return UpdateAsync(removeContributor, c => diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs index 70955e1bc..15d059cf1 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs @@ -22,7 +22,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Assets { - public sealed class AssetGrain : SquidexDomainObjectGrain, IAssetGrain + public sealed class AssetGrain : SquidexDomainObjectGrainLogSnapshots, IAssetGrain { public AssetGrain(IStore store, ISemanticLog log) : base(store, log) @@ -34,23 +34,23 @@ namespace Squidex.Domain.Apps.Entities.Assets switch (command) { case CreateAsset createRule: - return CreateReturnAsync(createRule, c => + return CreateReturnAsync(createRule, (Func)(c => { GuardAsset.CanCreate(c); Create(c); - return new AssetSavedResult(NewVersion, Snapshot.FileVersion); - }); + return new AssetSavedResult((long)base.Version, Snapshot.FileVersion); + })); case UpdateAsset updateRule: - return UpdateReturnAsync(updateRule, c => + return UpdateReturnAsync(updateRule, (Func)(c => { GuardAsset.CanUpdate(c); Update(c); - return new AssetSavedResult(NewVersion, Snapshot.FileVersion); - }); + return new AssetSavedResult((long)base.Version, Snapshot.FileVersion); + })); case RenameAsset renameAsset: return UpdateAsync(renameAsset, c => { @@ -140,9 +140,9 @@ namespace Squidex.Domain.Apps.Entities.Assets return Snapshot.Apply(@event); } - public Task> GetStateAsync() + public Task> GetStateAsync(long version = EtagVersion.Any) { - return J.AsTask(Snapshot); + return J.AsTask(GetSnapshot(version)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs index f76f5e48f..4018d7e3d 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; @@ -13,6 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetGrain : IDomainObjectGrain { - Task> GetStateAsync(); + Task> GetStateAsync(long version = EtagVersion.Any); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index 5b16c29ec..16f9841ea 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -26,7 +26,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Contents { - public sealed class ContentGrain : SquidexDomainObjectGrain, IContentGrain + public sealed class ContentGrain : SquidexDomainObjectGrainLogSnapshots, IContentGrain { private readonly IAppProvider appProvider; private readonly IAssetRepository assetRepository; @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents switch (command) { case CreateContent createContent: - return CreateReturnAsync(createContent, async c => + return CreateReturnAsync(createContent, (Func>)(async c => { var ctx = await CreateContext(c.AppId.Id, c.SchemaId.Id, () => "Failed to create content."); @@ -77,8 +77,8 @@ namespace Squidex.Domain.Apps.Entities.Contents Create(c); - return EntityCreatedResult.Create(c.Data, NewVersion); - }); + return EntityCreatedResult.Create(c.Data, (long)base.Version); + })); case UpdateContent updateContent: return UpdateReturnAsync(updateContent, c => @@ -216,7 +216,7 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - return new ContentDataChangedResult(newData, NewVersion); + return new ContentDataChangedResult(newData, Version); } public void Create(CreateContent command) @@ -307,9 +307,9 @@ namespace Squidex.Domain.Apps.Entities.Contents return operationContext; } - public Task> GetStateAsync() + public Task> GetStateAsync(long version = EtagVersion.Any) { - return J.AsTask(Snapshot); + return J.AsTask(GetSnapshot(version)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs index 25ec318e2..7016766ef 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs @@ -7,53 +7,38 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Contents.State; +using Orleans; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentVersionLoader : IContentVersionLoader { - private readonly IStore store; - private readonly FieldRegistry registry; + private readonly IGrainFactory grainFactory; - public ContentVersionLoader(IStore store, FieldRegistry registry) + public ContentVersionLoader(IGrainFactory grainFactory) { - Guard.NotNull(store, nameof(store)); - Guard.NotNull(registry, nameof(registry)); + Guard.NotNull(grainFactory, nameof(grainFactory)); - this.store = store; - - this.registry = registry; + this.grainFactory = grainFactory; } public async Task LoadAsync(Guid id, long version) { using (Profiler.TraceMethod()) { - var content = new ContentState(); - - var persistence = store.WithEventSourcing(id, e => - { - if (content.Version < version) - { - content = content.Apply(e); - content.Version++; - } - }); + var grain = grainFactory.GetGrain(id); - await persistence.ReadAsync(); + var content = await grain.GetStateAsync(version); - if (content.Version != version) + if (content.Value == null || content.Value.Version != version) { throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); } - return content; + return content.Value; } } } -} \ No newline at end of file +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs index 24eddbcc5..429a27746 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; @@ -13,6 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentGrain : IDomainObjectGrain { - Task> GetStateAsync(); + Task> GetStateAsync(long version = EtagVersion.Any); } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs b/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs index d3072ca78..79e298881 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs @@ -76,10 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Rules var asset = (await grainFactory .GetGrain(assetEvent.AssetId) - .GetStateAsync()).Value; + .GetStateAsync(@event.Headers.EventStreamNumber())).Value; SimpleMapper.Map(asset, result); - SimpleMapper.Map(assetEvent, result); switch (assetEvent) { @@ -105,14 +104,12 @@ namespace Squidex.Domain.Apps.Entities.Rules var content = (await grainFactory .GetGrain(contentEvent.ContentId) - .GetStateAsync()).Value; + .GetStateAsync(@event.Headers.EventStreamNumber())).Value; SimpleMapper.Map(content, result); result.Data = content.Data ?? content.DataDraft; - SimpleMapper.Map(contentEvent, result); - switch (contentEvent) { case ContentCreated _: diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs index 62e3947b5..3aadcc635 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs @@ -116,11 +116,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards errors.Add(new ValidationError("Access token is required.", nameof(action.AccessToken))); } - if (string.IsNullOrWhiteSpace(action.Author) && string.IsNullOrWhiteSpace(action.Publication)) - { - errors.Add(new ValidationError("Author or publication is required.", nameof(action.Author), nameof(action.Publication))); - } - if (string.IsNullOrWhiteSpace(action.Content)) { errors.Add(new ValidationError("Content is required.", nameof(action.Content))); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 6fc848281..2d6924531 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas switch (command) { case AddField addField: - return UpdateReturnAsync(addField, c => + return UpdateReturnAsync(addField, (Func)(c => { GuardSchemaField.CanAdd(Snapshot.SchemaDef, c); @@ -64,8 +64,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas id = ((IArrayField)Snapshot.SchemaDef.FieldsById[c.ParentFieldId.Value]).FieldsByName[c.Name].Id; } - return EntityCreatedResult.Create(id, NewVersion); - }); + return EntityCreatedResult.Create(id, (long)base.Version); + })); case CreateSchema createSchema: return CreateAsync(createSchema, async c => diff --git a/src/Squidex.Domain.Apps.Entities/SquidexDomainObjectGrainLogSnapshots.cs b/src/Squidex.Domain.Apps.Entities/SquidexDomainObjectGrainLogSnapshots.cs new file mode 100644 index 000000000..425bdc4d6 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/SquidexDomainObjectGrainLogSnapshots.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities +{ + public abstract class SquidexDomainObjectGrainLogSnapshots : LogSnapshotDomainObjectGrain where T : IDomainState, new() + { + protected SquidexDomainObjectGrainLogSnapshots(IStore store, ISemanticLog log) + : base(store, log) + { + } + + public override void RaiseEvent(Envelope @event) + { + if (@event.Payload is AppEvent appEvent) + { + @event.SetAppId(appEvent.AppId.Id); + } + + base.RaiseEvent(@event); + } + } +} diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs b/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs index 9dd94274f..10f3c2d01 100644 --- a/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs +++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs @@ -34,8 +34,12 @@ namespace Squidex.Infrastructure.Commands protected sealed override void ApplyEvent(Envelope @event) { + var newVersion = Version + 1; + + var snapshotNew = OnEvent(@event); + snapshot = OnEvent(@event); - snapshot.Version = NewVersion + 1; + snapshot.Version = newVersion; } protected sealed override void RestorePreviousSnapshot(T previousSnapshot, long previousVersion) diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs b/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs index 1fc5c520f..042f6d4e5 100644 --- a/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs +++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs @@ -31,11 +31,6 @@ namespace Squidex.Infrastructure.Commands get { return Snapshot.Version; } } - public long NewVersion - { - get { return Snapshot.Version + uncomittedEvents.Count; } - } - public abstract T Snapshot { get; } protected DomainObjectGrainBase(ISemanticLog log) diff --git a/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObjectGrain.cs b/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObjectGrain.cs index 487c8e74d..417b4f1e3 100644 --- a/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObjectGrain.cs +++ b/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObjectGrain.cs @@ -15,7 +15,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Infrastructure.Commands { - public abstract class MultiSnapshotDomainObjectGrain : DomainObjectGrainBase where T : IDomainState, new() + public abstract class LogSnapshotDomainObjectGrain : DomainObjectGrainBase where T : IDomainState, new() { private readonly IStore store; private readonly List snapshots = new List { new T { Version = EtagVersion.Empty } }; @@ -26,7 +26,7 @@ namespace Squidex.Infrastructure.Commands get { return snapshots.Last(); } } - protected MultiSnapshotDomainObjectGrain(IStore store, ISemanticLog log) + protected LogSnapshotDomainObjectGrain(IStore store, ISemanticLog log) : base(log) { Guard.NotNull(log, nameof(log)); @@ -58,7 +58,7 @@ namespace Squidex.Infrastructure.Commands { var snapshot = OnEvent(@event); - snapshot.Version = NewVersion + 1; + snapshot.Version = Version + 1; snapshots.Add(snapshot); } diff --git a/src/Squidex.Infrastructure/Http/DumpFormatter.cs b/src/Squidex.Infrastructure/Http/DumpFormatter.cs index 5f0c0dff1..040e6ddc1 100644 --- a/src/Squidex.Infrastructure/Http/DumpFormatter.cs +++ b/src/Squidex.Infrastructure/Http/DumpFormatter.cs @@ -15,7 +15,17 @@ namespace Squidex.Infrastructure.Http { public static class DumpFormatter { - public static string BuildDump(HttpRequestMessage request, HttpResponseMessage response, string requestBody, string responseBody, TimeSpan elapsed, bool isTimeout) + public static string BuildDump(HttpRequestMessage request, HttpResponseMessage response, string responseBody) + { + return BuildDump(request, response, null, responseBody, TimeSpan.Zero, false); + } + + public static string BuildDump(HttpRequestMessage request, HttpResponseMessage response, string requestBody, string responseBody) + { + return BuildDump(request, response, requestBody, responseBody, TimeSpan.Zero, false); + } + + public static string BuildDump(HttpRequestMessage request, HttpResponseMessage response, string requestBody, string responseBody, TimeSpan elapsed, bool isTimeout = false) { var writer = new StringBuilder(); diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumactionDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumActionDto.cs similarity index 88% rename from src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumactionDto.cs rename to src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumActionDto.cs index 6335a67f6..3d52a238e 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumactionDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumActionDto.cs @@ -22,16 +22,6 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions [Required] public string AccessToken { get; set; } - /// - /// The author name. - /// - public string Author { get; set; } - - /// - /// The author name. - /// - public string Publication { get; set; } - /// /// The optional comma separated list of tags. /// diff --git a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html index 259dc2785..b5861bcef 100644 --- a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html +++ b/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html @@ -15,34 +15,6 @@ -
- - -
- - - - - - The name of the author. You can also define the publication. - -
-
- -
- - -
- - - - - - The name of the publication. You can also define the author. - -
-
-
diff --git a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts index afb7c26e4..a29f274df 100644 --- a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts @@ -39,12 +39,6 @@ export class MediumActionComponent implements OnInit { Validators.required ])); - this.actionForm.setControl('author', - new FormControl(this.action.author || '')); - - this.actionForm.setControl('publication', - new FormControl(this.action.publication || '')); - this.actionForm.setControl('canonicalUrl', new FormControl(this.action.canonicalUrl || '')); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs new file mode 100644 index 000000000..c35433c34 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs @@ -0,0 +1,75 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using FakeItEasy; +using Orleans; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Orleans; +using Squidex.Infrastructure.States; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Contents +{ + public class ContentVersionLoaderTests + { + private readonly IGrainFactory grainFactory = A.Fake(); + private readonly IContentGrain grain = A.Fake(); + private readonly FieldRegistry fieldRegistry = new FieldRegistry(new TypeNameRegistry()); + private readonly Guid id = Guid.NewGuid(); + private readonly ContentVersionLoader sut; + + public ContentVersionLoaderTests() + { + A.CallTo(() => grainFactory.GetGrain(id, null)) + .Returns(grain); + + sut = new ContentVersionLoader(grainFactory); + } + + [Fact] + public async Task Should_throw_exception_if_no_state_returned() + { + A.CallTo(() => grain.GetStateAsync(10)) + .Returns(new J(null)); + + await Assert.ThrowsAsync(() => sut.LoadAsync(id, 10)); + } + + [Fact] + public async Task Should_throw_exception_if_state_has_other_version() + { + var entity = A.Fake(); + + A.CallTo(() => entity.Version) + .Returns(5); + + A.CallTo(() => grain.GetStateAsync(10)) + .Returns(J.Of(entity)); + + await Assert.ThrowsAsync(() => sut.LoadAsync(id, 10)); + } + + [Fact] + public async Task Should_return_content_from_state() + { + var entity = A.Fake(); + + A.CallTo(() => entity.Version) + .Returns(10); + + A.CallTo(() => grain.GetStateAsync(10)) + .Returns(J.Of(entity)); + + var result = await sut.LoadAsync(id, 10); + + Assert.Same(entity, result); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/MediumActionTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/MediumActionTests.cs index 4500a4081..c309b52ac 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/MediumActionTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/MediumActionTests.cs @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions [Fact] public async Task Should_add_error_if_access_token_is_null() { - var action = new MediumAction { AccessToken = null, Author = "author", Title = "title", Content = "content" }; + var action = new MediumAction { AccessToken = null, Title = "title", Content = "content" }; var errors = await RuleActionValidator.ValidateAsync(action); @@ -30,24 +30,10 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions }); } - [Fact] - public async Task Should_add_error_if_author_is_null() - { - var action = new MediumAction { AccessToken = "token", Author = null, Title = "title", Content = "content" }; - - var errors = await RuleActionValidator.ValidateAsync(action); - - errors.Should().BeEquivalentTo( - new List - { - new ValidationError("Author or publication is required.", "Author", "Publication") - }); - } - [Fact] public async Task Should_add_error_if_title_null() { - var action = new MediumAction { AccessToken = "token", Author = "author", Title = null, Content = "content" }; + var action = new MediumAction { AccessToken = "token", Title = null, Content = "content" }; var errors = await RuleActionValidator.ValidateAsync(action); @@ -61,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions [Fact] public async Task Should_add_error_if_content_is_null() { - var action = new MediumAction { AccessToken = "token", Author = "author", Title = "title", Content = null }; + var action = new MediumAction { AccessToken = "token", Title = "title", Content = null }; var errors = await RuleActionValidator.ValidateAsync(action); @@ -75,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions [Fact] public async Task Should_not_add_error_if_values_are_valid() { - var action = new MediumAction { AccessToken = "token", Author = "author", Title = "title", Content = "content" }; + var action = new MediumAction { AccessToken = "token", Title = "title", Content = "content" }; var errors = await RuleActionValidator.ValidateAsync(action); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs index c8148df60..60c257bb8 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs @@ -24,7 +24,8 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers public abstract class HandlerTestBase where T : IDomainObjectGrain { private readonly IStore store = A.Fake>(); - private readonly IPersistence persistence = A.Fake>(); + private readonly IPersistence persistence1 = A.Fake>(); + private readonly IPersistence persistence2 = A.Fake(); protected RefToken User { get; } = new RefToken("subject", Guid.NewGuid().ToString()); @@ -58,9 +59,18 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers protected HandlerTestBase() { A.CallTo(() => store.WithSnapshotsAndEventSourcing(A.Ignored, Id, A>.Ignored, A, Task>>.Ignored)) - .Returns(persistence); + .Returns(persistence1); - A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) + A.CallTo(() => store.WithEventSourcing(A.Ignored, Id, A, Task>>.Ignored)) + .Returns(persistence2); + + A.CallTo(() => persistence1.WriteEventsAsync(A>>.Ignored)) + .Invokes(new Action>>(events => + { + LastEvents = events; + })); + + A.CallTo(() => persistence2.WriteEventsAsync(A>>.Ignored)) .Invokes(new Action>>(events => { LastEvents = events; diff --git a/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectGrainTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectGrainTests.cs index 8fc1270d8..b224cdb82 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectGrainTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectGrainTests.cs @@ -20,7 +20,7 @@ using Xunit; namespace Squidex.Infrastructure.Commands { - public class MultiSnapshotDomainObjectGrainTests + public class LogSnapshotDomainObjectGrainTests { private readonly IStore store = A.Fake>(); private readonly ISnapshotStore snapshotStore = A.Fake>(); @@ -53,7 +53,7 @@ namespace Squidex.Infrastructure.Commands public int Value { get; set; } } - public sealed class MyDomainObject : MultiSnapshotDomainObjectGrain + public sealed class MyDomainObject : LogSnapshotDomainObjectGrain { public MyDomainObject(IStore store) : base(store, A.Dummy()) @@ -102,7 +102,7 @@ namespace Squidex.Infrastructure.Commands } } - public MultiSnapshotDomainObjectGrainTests() + public LogSnapshotDomainObjectGrainTests() { A.CallTo(() => store.WithEventSourcing(typeof(MyDomainObject), id, A, Task>>.Ignored)) .Returns(persistence);