diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs index c4f934b48..02bb242bd 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs @@ -25,29 +25,25 @@ namespace Squidex.Domain.Apps.Entities.Apps private readonly IAppPlansProvider appPlansProvider; private readonly IAppPlanBillingManager appPlansBillingManager; private readonly IUserResolver userResolver; - private readonly IEnumerable templateBuilders; public AppCommandMiddleware( IAggregateHandler handler, IAppProvider appProvider, IAppPlansProvider appPlansProvider, IAppPlanBillingManager appPlansBillingManager, - IUserResolver userResolver, - IEnumerable templateBuilders) + IUserResolver userResolver) { Guard.NotNull(handler, nameof(handler)); Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(userResolver, nameof(userResolver)); Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager)); - Guard.NotNull(templateBuilders, nameof(templateBuilders)); this.handler = handler; this.userResolver = userResolver; this.appProvider = appProvider; this.appPlansProvider = appPlansProvider; this.appPlansBillingManager = appPlansBillingManager; - this.templateBuilders = templateBuilders; } protected async Task On(CreateApp command, CommandContext context) @@ -60,14 +56,6 @@ namespace Squidex.Domain.Apps.Entities.Apps context.Complete(EntityCreatedResult.Create(command.AppId, a.Version)); }); - - if (!string.IsNullOrWhiteSpace(command.Template)) - { - foreach (var templateBuilder in templateBuilders) - { - await templateBuilder.PopulateTemplate(app.Snapshot, command.Template, context.CommandBus); - } - } } protected Task On(AssignContributor command, CommandContext context) @@ -206,10 +194,8 @@ namespace Squidex.Domain.Apps.Entities.Apps public async Task HandleAsync(CommandContext context, Func next) { - if (!await this.DispatchActionAsync(context.Command, context)) - { - await next(); - } + await this.DispatchActionAsync(context.Command, context); + await next(); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/IAppTemplateBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/IAppTemplateBuilder.cs deleted file mode 100644 index 42b4b3439..000000000 --- a/src/Squidex.Domain.Apps.Entities/Apps/IAppTemplateBuilder.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using Squidex.Infrastructure.Commands; - -namespace Squidex.Domain.Apps.Entities.Apps -{ - public interface IAppTemplateBuilder - { - Task PopulateTemplate(IAppEntity app, string name, ICommandBus bus); - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Blog.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs similarity index 72% rename from src/Squidex.Domain.Apps.Entities/Apps/Templates/Blog.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs index 79d9df9f3..46a59f82e 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Blog.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs @@ -16,11 +16,13 @@ using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.Apps.Templates { - public sealed class Blog : IAppTemplateBuilder + public sealed class CreateBlogCommandMiddleware : ICommandMiddleware { + private const string TemplateName = "Blog"; private const string SlugScript = @" var data = ctx.data; @@ -28,52 +30,71 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates replace(data);"; - public async Task PopulateTemplate(IAppEntity app, string name, ICommandBus bus) + public Task HandleAsync(CommandContext context, Func next) { - if (string.Equals("Blog", name, StringComparison.OrdinalIgnoreCase)) + if (context.IsCompleted && + context.Command is CreateApp createApp && + string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase)) { - var appId = new NamedId(app.Id, app.Name); + var appId = new NamedId(createApp.AppId, createApp.Name); Task publishAsync(AppCommand command) { command.AppId = appId; - return bus.PublishAsync(command); + return context.CommandBus.PublishAsync(command); } - var pagesId = await CreatePagesSchema(publishAsync); - var postsId = await CreatePostsSchema(publishAsync); - - await publishAsync(new CreateContent - { - SchemaId = pagesId, - Data = - new NamedContentData() - .AddField("title", - new ContentFieldData() - .AddValue("iv", "About Me")) - .AddField("text", - new ContentFieldData() - .AddValue("iv", "I love Squidex and SciFi!")), - Publish = true - }); - - await publishAsync(new CreateContent - { - SchemaId = postsId, - Data = - new NamedContentData() - .AddField("title", - new ContentFieldData() - .AddValue("iv", "My first post with Squidex")) - .AddField("text", - new ContentFieldData() - .AddValue("iv", "Just created a blog with Squidex. I love it!")), - Publish = true, - }); - - await publishAsync(new AttachClient { Id = "sample-client" }); + return Task.WhenAll( + CreatePagesAsync(publishAsync, appId), + CreatePostsAsync(publishAsync, appId), + CreateClientAsync(publishAsync, appId)); } + + return TaskHelper.Done; + } + + private static async Task CreateClientAsync(Func publishAsync, NamedId appId) + { + await publishAsync(new AttachClient { Id = "sample-client" }); + } + + private async Task CreatePostsAsync(Func publishAsync, NamedId appId) + { + var postsId = await CreatePostsSchema(publishAsync); + + await publishAsync(new CreateContent + { + SchemaId = postsId, + Data = + new NamedContentData() + .AddField("title", + new ContentFieldData() + .AddValue("iv", "My first post with Squidex")) + .AddField("text", + new ContentFieldData() + .AddValue("iv", "Just created a blog with Squidex. I love it!")), + Publish = true, + }); + } + + private async Task CreatePagesAsync(Func publishAsync, NamedId appId) + { + var pagesId = await CreatePagesSchema(publishAsync); + + await publishAsync(new CreateContent + { + SchemaId = pagesId, + Data = + new NamedContentData() + .AddField("title", + new ContentFieldData() + .AddValue("iv", "About Me")) + .AddField("text", + new ContentFieldData() + .AddValue("iv", "I love Squidex and SciFi!")), + Publish = true + }); } private async Task> CreatePostsSchema(Func publishAsync) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs index 6923e5005..6b10ba6df 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs @@ -133,10 +133,8 @@ namespace Squidex.Domain.Apps.Entities.Contents public async Task HandleAsync(CommandContext context, Func next) { - if (!await this.DispatchActionAsync(context.Command, context)) - { - await next(); - } + await this.DispatchActionAsync(context.Command, context); + await next(); } private async Task CreateContext(ContentCommand command, ContentDomainObject content, Func message) diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs index e252b5632..c0ee1cf5b 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs @@ -82,10 +82,8 @@ namespace Squidex.Domain.Apps.Entities.Rules public async Task HandleAsync(CommandContext context, Func next) { - if (!await this.DispatchActionAsync(context.Command, context)) - { - await next(); - } + await this.DispatchActionAsync(context.Command, context); + await next(); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs index 18c468c0f..b58f2c815 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs @@ -187,10 +187,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas public async Task HandleAsync(CommandContext context, Func next) { - if (!await this.DispatchActionAsync(context.Command, context)) - { - await next(); - } + await this.DispatchActionAsync(context.Command, context); + await next(); } } } diff --git a/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs b/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs index 13892b92e..7c72e8fba 100644 --- a/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs +++ b/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs @@ -15,13 +15,13 @@ namespace Squidex.Infrastructure.Commands { public sealed class InMemoryCommandBus : ICommandBus { - private readonly List handlers; + private readonly List middlewares; - public InMemoryCommandBus(IEnumerable handlers) + public InMemoryCommandBus(IEnumerable middlewares) { - Guard.NotNull(handlers, nameof(handlers)); + Guard.NotNull(middlewares, nameof(middlewares)); - this.handlers = handlers.Reverse().ToList(); + this.middlewares = middlewares.Reverse().ToList(); } public async Task PublishAsync(ICommand command) @@ -32,7 +32,7 @@ namespace Squidex.Infrastructure.Commands var next = new Func(() => TaskHelper.Done); - foreach (var handler in handlers) + foreach (var handler in middlewares) { next = Join(handler, context, next); } diff --git a/src/Squidex/Config/Domain/WriteServices.cs b/src/Squidex/Config/Domain/WriteServices.cs index 957d1c1da..7c890b235 100644 --- a/src/Squidex/Config/Domain/WriteServices.cs +++ b/src/Squidex/Config/Domain/WriteServices.cs @@ -64,6 +64,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddTransientAs() .As(); @@ -76,9 +79,6 @@ namespace Squidex.Config.Domain services.AddTransientAs() .As(); - services.AddTransientAs() - .As(); - services.AddTransientAs() .AsSelf(); diff --git a/src/Squidex/app/features/apps/pages/apps-page.component.html b/src/Squidex/app/features/apps/pages/apps-page.component.html index cd36226d0..68f4e00ec 100644 --- a/src/Squidex/app/features/apps/pages/apps-page.component.html +++ b/src/Squidex/app/features/apps/pages/apps-page.component.html @@ -42,10 +42,10 @@
- +
-

Blog Sample

+

New Blog Sample

Start with our ready to use blog.
diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html index b09792026..160232581 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html @@ -32,7 +32,7 @@ - {{ruleTriggers[trigger]}} + {{ruleTriggers[trigger].name}}
@@ -61,7 +61,7 @@ - {{ruleActions[action]}} + {{ruleActions[action].name}}
diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html index de4833ece..a6b7f98f6 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html @@ -43,7 +43,7 @@ - {{ruleTriggers[rule.triggerType]}} + {{ruleTriggers[rule.triggerType].name}} @@ -56,7 +56,7 @@ - {{ruleActions[rule.actionType]}} + {{ruleActions[rule.actionType].name}} diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index 150b4ac66..88cf2b71b 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -21,16 +21,30 @@ import { } from 'framework'; export const ruleTriggers: any = { - 'AssetChanged': 'Asset changed', - 'ContentChanged': 'Content changed' + 'AssetChanged': { + name: 'Asset changed' + }, + 'ContentChanged': { + name: 'Content changed' + } }; export const ruleActions: any = { - 'Algolia': 'Populate Algolia Index', - 'AzureQueue': 'Send to Azure Queue', - 'Fastly': 'Purge fastly Cache', - 'Slack': 'Send to Slack', - 'Webhook': 'Send Webhook' + 'Algolia': { + name: 'Populate Algolia Index' + }, + 'AzureQueue': { + name: 'Send to Azure Queue' + }, + 'Fastly': { + name: 'Purge fastly Cache' + }, + 'Slack': { + name: 'Send to Slack' + }, + 'Webhook': { + name: 'Send Webhook' + } }; export class RuleDto { @@ -154,125 +168,125 @@ export class RulesService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`); return HTTP.getVersioned(this.http, url) - .map(response => { - const items: any[] = response.payload.body; - - return items.map(item => { - return new RuleDto( - item.id, - item.createdBy, - item.lastModifiedBy, - DateTime.parseISO_UTC(item.created), - DateTime.parseISO_UTC(item.lastModified), - new Version(item.version.toString()), - item.isEnabled, - item.trigger, - item.trigger.triggerType, - item.action, - item.action.actionType); - }); - }) - .pretifyError('Failed to load Rules. Please reload.'); + .map(response => { + const items: any[] = response.payload.body; + + return items.map(item => { + return new RuleDto( + item.id, + item.createdBy, + item.lastModifiedBy, + DateTime.parseISO_UTC(item.created), + DateTime.parseISO_UTC(item.lastModified), + new Version(item.version.toString()), + item.isEnabled, + item.trigger, + item.trigger.triggerType, + item.action, + item.action.actionType); + }); + }) + .pretifyError('Failed to load Rules. Please reload.'); } public postRule(appName: string, dto: CreateRuleDto, user: string, now: DateTime): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`); return HTTP.postVersioned(this.http, url, dto) - .map(response => { - const body = response.payload.body; - - return new RuleDto( - body.id, - user, - user, - now, - now, - response.version, - true, - dto.trigger, - dto.trigger.triggerType, - dto.action, - dto.action.actionType); - }) - .do(() => { - this.analytics.trackEvent('Rule', 'Created', appName); - }) - .pretifyError('Failed to create rule. Please reload.'); + .map(response => { + const body = response.payload.body; + + return new RuleDto( + body.id, + user, + user, + now, + now, + response.version, + true, + dto.trigger, + dto.trigger.triggerType, + dto.action, + dto.action.actionType); + }) + .do(() => { + this.analytics.trackEvent('Rule', 'Created', appName); + }) + .pretifyError('Failed to create rule. Please reload.'); } public putRule(appName: string, id: string, dto: UpdateRuleDto, version: Version): Observable> { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}`); return HTTP.putVersioned(this.http, url, dto, version) - .do(() => { - this.analytics.trackEvent('Rule', 'Updated', appName); - }) - .pretifyError('Failed to update rule. Please reload.'); + .do(() => { + this.analytics.trackEvent('Rule', 'Updated', appName); + }) + .pretifyError('Failed to update rule. Please reload.'); } public enableRule(appName: string, id: string, version: Version): Observable> { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}/enable`); return HTTP.putVersioned(this.http, url, {}, version) - .do(() => { - this.analytics.trackEvent('Rule', 'Updated', appName); - }) - .pretifyError('Failed to enable rule. Please reload.'); + .do(() => { + this.analytics.trackEvent('Rule', 'Updated', appName); + }) + .pretifyError('Failed to enable rule. Please reload.'); } public disableRule(appName: string, id: string, version: Version): Observable> { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}/disable`); return HTTP.putVersioned(this.http, url, {}, version) - .do(() => { - this.analytics.trackEvent('Rule', 'Updated', appName); - }) - .pretifyError('Failed to disable rule. Please reload.'); + .do(() => { + this.analytics.trackEvent('Rule', 'Updated', appName); + }) + .pretifyError('Failed to disable rule. Please reload.'); } public deleteRule(appName: string, id: string, version: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/${id}`); return HTTP.deleteVersioned(this.http, url, version) - .do(() => { - this.analytics.trackEvent('Rule', 'Deleted', appName); - }) - .pretifyError('Failed to delete rule. Please reload.'); + .do(() => { + this.analytics.trackEvent('Rule', 'Deleted', appName); + }) + .pretifyError('Failed to delete rule. Please reload.'); } public getEvents(appName: string, take: number, skip: number): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/events?take=${take}&skip=${skip}`); return HTTP.getVersioned(this.http, url) - .map(response => { - const body = response.payload.body; - - const items: any[] = body.items; - - return new RuleEventsDto(body.total, items.map(item => { - return new RuleEventDto( - item.id, - DateTime.parseISO_UTC(item.created), - item.nextAttempt ? DateTime.parseISO_UTC(item.nextAttempt) : null, - item.eventName, - item.description, - item.lastDump, - item.result, - item.jobResult, - item.numCalls); - })); - }) - .pretifyError('Failed to load events. Please reload.'); + .map(response => { + const body = response.payload.body; + + const items: any[] = body.items; + + return new RuleEventsDto(body.total, items.map(item => { + return new RuleEventDto( + item.id, + DateTime.parseISO_UTC(item.created), + item.nextAttempt ? DateTime.parseISO_UTC(item.nextAttempt) : null, + item.eventName, + item.description, + item.lastDump, + item.result, + item.jobResult, + item.numCalls); + })); + }) + .pretifyError('Failed to load events. Please reload.'); } public enqueueEvent(appName: string, id: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/events/${id}`); return HTTP.putVersioned(this.http, url, {}) - .do(() => { - this.analytics.trackEvent('Rule', 'EventEnqueued', appName); - }) - .pretifyError('Failed to enqueue rule event. Please reload.'); + .do(() => { + this.analytics.trackEvent('Rule', 'EventEnqueued', appName); + }) + .pretifyError('Failed to enqueue rule event. Please reload.'); } } \ No newline at end of file diff --git a/src/Squidex/wwwroot/images/add-blog.png b/src/Squidex/wwwroot/images/add-blog.png new file mode 100644 index 000000000..764e65cf1 Binary files /dev/null and b/src/Squidex/wwwroot/images/add-blog.png differ diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs index 4b82b2a2e..42223eaaa 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs @@ -25,8 +25,6 @@ namespace Squidex.Domain.Apps.Entities.Apps private readonly IAppPlansProvider appPlansProvider = A.Fake(); private readonly IAppPlanBillingManager appPlansBillingManager = A.Fake(); private readonly IUserResolver userResolver = A.Fake(); - private readonly IAppTemplateBuilder templateBuilder1 = A.Fake(); - private readonly IAppTemplateBuilder templateBuilder2 = A.Fake(); private readonly Language language = Language.DE; private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string clientName = "client"; @@ -47,13 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Apps A.CallTo(() => userResolver.FindByIdAsync(contributorId)) .Returns(A.Fake()); - var templateBuilders = new[] - { - templateBuilder1, - templateBuilder2 - }; - - sut = new AppCommandMiddleware(Handler, appProvider, appPlansProvider, appPlansBillingManager, userResolver, templateBuilders); + sut = new AppCommandMiddleware(Handler, appProvider, appPlansProvider, appPlansBillingManager, userResolver); } [Fact] @@ -67,29 +59,6 @@ namespace Squidex.Domain.Apps.Entities.Apps }); Assert.Equal(AppId, context.Result>().IdOrValue); - - A.CallTo(() => templateBuilder1.PopulateTemplate(A.Ignored, A.Ignored, context.CommandBus)) - .MustNotHaveHappened(); - A.CallTo(() => templateBuilder2.PopulateTemplate(A.Ignored, A.Ignored, context.CommandBus)) - .MustNotHaveHappened(); - } - - [Fact] - public async Task Create_should_call_template_builders_with_template_name() - { - var context = CreateContextForCommand(new CreateApp { Name = AppName, AppId = AppId, Template = "Blog" }); - - await TestCreate(app, async _ => - { - await sut.HandleAsync(context); - }); - - Assert.Equal(AppId, context.Result>().IdOrValue); - - A.CallTo(() => templateBuilder1.PopulateTemplate(A.Ignored, "Blog", context.CommandBus)) - .MustHaveHappened(); - A.CallTo(() => templateBuilder2.PopulateTemplate(A.Ignored, "Blog", context.CommandBus)) - .MustHaveHappened(); } [Fact]