mirror of https://github.com/Squidex/squidex.git
Browse Source
# Conflicts: # src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionHelper.cs # src/Squidex/Config/Domain/SerializationServices.cs # src/Squidex/Config/Domain/WriteServices.cs # tools/Migrate_01/Rebuilder.cspull/218/head
485 changed files with 10571 additions and 4028 deletions
@ -0,0 +1,78 @@ |
|||||
|
# Changelog |
||||
|
|
||||
|
## v1.1.7 - 2018-02-06 |
||||
|
|
||||
|
### Bugfixes |
||||
|
|
||||
|
* **UI**: Checkbox style fixed. |
||||
|
|
||||
|
|
||||
|
## v1.1.6 - 2018-02-06 |
||||
|
|
||||
|
### Features |
||||
|
|
||||
|
* **Rules**: Allow content triggers to catch all content events. |
||||
|
* **Rules**: Ensure that the events for an aggregate are handled sequentially. |
||||
|
* **UI**: History stream in the dashboard. |
||||
|
* **UI**: Better UI for apps overview. |
||||
|
* **Apps**: Added a ready to use blog sample. |
||||
|
|
||||
|
### Bugfixes |
||||
|
|
||||
|
* **UI**: History UI was throwing an exception when a user was referenced in the message. |
||||
|
* **UI**: A lot of style fixes. |
||||
|
|
||||
|
## v1.1.5 - 2018-02-03 |
||||
|
|
||||
|
### Features |
||||
|
|
||||
|
* **Content**: Slugify function for custom scripts. |
||||
|
|
||||
|
### Bugfixes |
||||
|
|
||||
|
* **Migration**: Assets and schemas were not removed before recreation. |
||||
|
* **Content**: OData queries only worked for data fields. |
||||
|
* **Assets**: OData queries did not work at all and included too many fields (e.g. AppId, Id). |
||||
|
|
||||
|
## v1.1.4 - 2018-02-03 |
||||
|
|
||||
|
### Features |
||||
|
|
||||
|
* **Login**: Consent screen to inform the user about privacy policies. |
||||
|
|
||||
|
## v1.1.3 - 2018-02-03 |
||||
|
|
||||
|
### Features |
||||
|
|
||||
|
* **Rules**: Trigger when asset has changed |
||||
|
* **Rules**: Action to purge cache items in fastly |
||||
|
* **Rules**: Action to push events to Azure storage queues. |
||||
|
|
||||
|
### Bugfixes |
||||
|
|
||||
|
* **Rules**: Layout fixes. |
||||
|
|
||||
|
### Refactorings |
||||
|
|
||||
|
* Freeze action, triggers and field properties using Fody. |
||||
|
* Fetch derived types automatically for Swagger generation. |
||||
|
|
||||
|
## v1.1.2 - 2018-01-31 |
||||
|
|
||||
|
### Features |
||||
|
|
||||
|
* **Assets**: OData support, except full text search (`$search`) |
||||
|
* **Rules**: Slack action |
||||
|
* **Rules**: Algolia action |
||||
|
|
||||
|
### Bugixes |
||||
|
|
||||
|
* **Rules**: Color corrections for actions. |
||||
|
|
||||
|
### Breaking Changes |
||||
|
|
||||
|
* Asset structure has changed: Migration will update the ocllection automatically. |
||||
|
* Asset endpoint: |
||||
|
* `take` query parameter renamed to `$top` for OData compatibility. |
||||
|
* `skip` query parameter renamed to `$skip` for OData compatibility. |
||||
|
* `query` query parameter replaced with OData. Use `$query=contains(fileName, 'MyQuery')` instead. |
||||
@ -0,0 +1,11 @@ |
|||||
|
# Build the image |
||||
|
docker build . -t squidex-build-image -f dockerfile.build |
||||
|
|
||||
|
# Open the image |
||||
|
docker create --name squidex-build-container squidex-build-image |
||||
|
|
||||
|
# Copy the output to the host file system |
||||
|
docker cp squidex-build-container:/out ./publish |
||||
|
|
||||
|
# Cleanup |
||||
|
docker rm squidex-build-container |
||||
Binary file not shown.
@ -1 +0,0 @@ |
|||||
5W20j9jiNog4dHUEt+cCnePb8z6jFEMnkwO4XilajM7FCnen3KTnN/G8PAUGuQieSlTI9MRe0sRYcafLJl900w== |
|
||||
@ -1,23 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> |
|
||||
<metadata> |
|
||||
<id>Microsoft.Orleans.OrleansCodeGenerator.Build</id> |
|
||||
<version>2.0.0-beta1-fix</version> |
|
||||
<title>Microsoft Orleans Build-time Code Generator</title> |
|
||||
<authors>Microsoft</authors> |
|
||||
<owners>Microsoft</owners> |
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance> |
|
||||
<developmentDependency>true</developmentDependency> |
|
||||
<licenseUrl>https://github.com/dotnet/Orleans#license</licenseUrl> |
|
||||
<projectUrl>https://github.com/dotnet/Orleans</projectUrl> |
|
||||
<iconUrl>https://raw.githubusercontent.com/dotnet/orleans/gh-pages/assets/logo_128.png</iconUrl> |
|
||||
<description>Microsoft Orleans build-time code generator to install in all grain interface & implementation projects.</description> |
|
||||
<copyright>© Microsoft Corporation. All rights reserved.</copyright> |
|
||||
<tags>Orleans Cloud-Computing Actor-Model Actors Distributed-Systems C# .NET</tags> |
|
||||
<repository type="git" url="https://github.com/dotnet/Orleans" /> |
|
||||
<dependencies> |
|
||||
<group targetFramework=".NETFramework4.6.1" /> |
|
||||
<group targetFramework=".NETCoreApp2.0" /> |
|
||||
</dependencies> |
|
||||
</metadata> |
|
||||
</package> |
|
||||
Binary file not shown.
@ -1 +0,0 @@ |
|||||
mBHlGWl+bNTPP463JBEB/dftmdZKQRD8X72F7lsTFqYWddW5Ytp1gbzChCxW0d/Pt71KLF6XrVmyecbFlNdFBA== |
|
||||
@ -1,25 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> |
|
||||
<metadata> |
|
||||
<id>OrleansDashboard</id> |
|
||||
<version>2.0.0-beta3</version> |
|
||||
<authors>OrleansContrib</authors> |
|
||||
<owners>OrleansContrib</owners> |
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance> |
|
||||
<licenseUrl>https://opensource.org/licenses/MIT</licenseUrl> |
|
||||
<projectUrl>https://github.com/OrleansContrib/OrleansDashboard</projectUrl> |
|
||||
<iconUrl>http://dotnet.github.io/orleans/assets/logo.png</iconUrl> |
|
||||
<description>An admin dashboard for Microsoft Orleans</description> |
|
||||
<copyright>Copyright © 2017</copyright> |
|
||||
<tags>orleans dashboard metrics monitor</tags> |
|
||||
<repository url="https://github.com/OrleansContrib/OrleansDashboard" /> |
|
||||
<dependencies> |
|
||||
<group targetFramework=".NETStandard2.0"> |
|
||||
<dependency id="Microsoft.AspNetCore" version="2.0.1" exclude="Build,Analyzers" /> |
|
||||
<dependency id="Microsoft.Orleans.Core" version="2.0.0-beta1" exclude="Build,Analyzers" /> |
|
||||
<dependency id="Microsoft.Orleans.OrleansCodeGenerator.Build" version="2.0.0-beta1" exclude="Build,Analyzers" /> |
|
||||
<dependency id="Microsoft.Orleans.OrleansRuntime" version="2.0.0-beta1" exclude="Build,Analyzers" /> |
|
||||
</group> |
|
||||
</dependencies> |
|
||||
</metadata> |
|
||||
</package> |
|
||||
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8" ?> |
||||
|
<Weavers> |
||||
|
<Freezable/> |
||||
|
</Weavers> |
||||
@ -0,0 +1,26 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Actions |
||||
|
{ |
||||
|
[TypeName(nameof(AlgoliaAction))] |
||||
|
public sealed class AlgoliaAction : RuleAction |
||||
|
{ |
||||
|
public string AppId { get; set; } |
||||
|
|
||||
|
public string ApiKey { get; set; } |
||||
|
|
||||
|
public string IndexName { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IRuleActionVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Actions |
||||
|
{ |
||||
|
[TypeName(nameof(AzureQueueAction))] |
||||
|
public sealed class AzureQueueAction : RuleAction |
||||
|
{ |
||||
|
public string ConnectionString { get; set; } |
||||
|
|
||||
|
public string Queue { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IRuleActionVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Actions |
||||
|
{ |
||||
|
[TypeName(nameof(FastlyAction))] |
||||
|
public sealed class FastlyAction : RuleAction |
||||
|
{ |
||||
|
public string ApiKey { get; set; } |
||||
|
|
||||
|
public string ServiceId { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IRuleActionVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Actions |
||||
|
{ |
||||
|
[TypeName(nameof(SlackAction))] |
||||
|
public sealed class SlackAction : RuleAction |
||||
|
{ |
||||
|
public Uri WebhookUrl { get; set; } |
||||
|
|
||||
|
public string Text { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IRuleActionVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Triggers |
||||
|
{ |
||||
|
[TypeName(nameof(AssetChangedTrigger))] |
||||
|
public sealed class AssetChangedTrigger : RuleTrigger |
||||
|
{ |
||||
|
public bool SendCreate { get; set; } |
||||
|
|
||||
|
public bool SendUpdate { get; set; } |
||||
|
|
||||
|
public bool SendRename { get; set; } |
||||
|
|
||||
|
public bool SendDelete { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IRuleTriggerVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,157 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Algolia.Search; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Domain.Apps.Events.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.Actions |
||||
|
{ |
||||
|
public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction> |
||||
|
{ |
||||
|
private const string SchemaNamePlaceholder = "$SCHEMA_NAME"; |
||||
|
private readonly ClientPool<(string AppId, string ApiKey, string IndexName), Index> clients; |
||||
|
private readonly RuleEventFormatter formatter; |
||||
|
|
||||
|
public AlgoliaActionHandler(RuleEventFormatter formatter) |
||||
|
{ |
||||
|
Guard.NotNull(formatter, nameof(formatter)); |
||||
|
|
||||
|
this.formatter = formatter; |
||||
|
|
||||
|
clients = new ClientPool<(string AppId, string ApiKey, string IndexName), Index>(key => |
||||
|
{ |
||||
|
var client = new AlgoliaClient(key.AppId, key.ApiKey); |
||||
|
|
||||
|
return client.InitIndex(key.IndexName); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, AlgoliaAction action) |
||||
|
{ |
||||
|
var ruleDescription = string.Empty; |
||||
|
var ruleData = new RuleJobData |
||||
|
{ |
||||
|
["AppId"] = action.AppId, |
||||
|
["ApiKey"] = action.ApiKey |
||||
|
}; |
||||
|
|
||||
|
if (@event.Payload is ContentEvent contentEvent) |
||||
|
{ |
||||
|
ruleData["ContentId"] = contentEvent.ContentId.ToString(); |
||||
|
ruleData["Operation"] = "Upsert"; |
||||
|
ruleData["IndexName"] = formatter.FormatString(action.IndexName, @event); |
||||
|
|
||||
|
var timestamp = @event.Headers.Timestamp().ToString(); |
||||
|
|
||||
|
switch (@event.Payload) |
||||
|
{ |
||||
|
case ContentCreated created: |
||||
|
{ |
||||
|
ruleDescription = $"Add entry to Algolia index: {action.IndexName}"; |
||||
|
ruleData["Content"] = new JObject( |
||||
|
new JProperty("id", contentEvent.ContentId), |
||||
|
new JProperty("created", timestamp), |
||||
|
new JProperty("createdBy", created.Actor.ToString()), |
||||
|
new JProperty("lastModified", timestamp), |
||||
|
new JProperty("lastModifiedBy", created.Actor.ToString()), |
||||
|
new JProperty("status", Status.Draft.ToString()), |
||||
|
new JProperty("data", formatter.ToRouteData(created.Data))); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case ContentUpdated updated: |
||||
|
{ |
||||
|
ruleDescription = $"Update entry in Algolia index: {action.IndexName}"; |
||||
|
ruleData["Content"] = new JObject( |
||||
|
new JProperty("lastModified", timestamp), |
||||
|
new JProperty("lastModifiedBy", updated.Actor.ToString()), |
||||
|
new JProperty("data", formatter.ToRouteData(updated.Data))); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case ContentStatusChanged statusChanged: |
||||
|
{ |
||||
|
ruleDescription = $"Update entry in Algolia index: {action.IndexName}"; |
||||
|
ruleData["Content"] = new JObject( |
||||
|
new JProperty("status", statusChanged.Status.ToString())); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case ContentDeleted deleted: |
||||
|
{ |
||||
|
ruleDescription = $"Delete entry from Index: {action.IndexName}"; |
||||
|
ruleData["Content"] = new JObject(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return (ruleDescription, ruleData); |
||||
|
} |
||||
|
|
||||
|
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) |
||||
|
{ |
||||
|
if (!job.TryGetValue("Operation", out var operationToken)) |
||||
|
{ |
||||
|
return (null, new InvalidOperationException("The action cannot handle this event.")); |
||||
|
} |
||||
|
|
||||
|
var appId = job["AppId"].Value<string>(); |
||||
|
var apiKey = job["ApiKey"].Value<string>(); |
||||
|
var indexName = job["IndexName"].Value<string>(); |
||||
|
|
||||
|
var index = clients.GetClient((appId, apiKey, indexName)); |
||||
|
|
||||
|
var operation = operationToken.Value<string>(); |
||||
|
var content = job["Content"].Value<JObject>(); |
||||
|
var contentId = job["ContentId"].Value<string>(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
switch (operation) |
||||
|
{ |
||||
|
case "Upsert": |
||||
|
{ |
||||
|
content["objectID"] = contentId; |
||||
|
|
||||
|
var resonse = await index.PartialUpdateObjectAsync(content); |
||||
|
|
||||
|
return (resonse.ToString(Formatting.Indented), null); |
||||
|
} |
||||
|
|
||||
|
case "Delete": |
||||
|
{ |
||||
|
var resonse = await index.DeleteObjectAsync(contentId); |
||||
|
|
||||
|
return (resonse.ToString(Formatting.Indented), null); |
||||
|
} |
||||
|
|
||||
|
default: |
||||
|
return (null, null); |
||||
|
} |
||||
|
} |
||||
|
catch (AlgoliaException ex) |
||||
|
{ |
||||
|
return (ex.Message, ex); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
return (null, ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.WindowsAzure.Storage; |
||||
|
using Microsoft.WindowsAzure.Storage.Queue; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.Actions |
||||
|
{ |
||||
|
public sealed class AzureQueueActionHandler : RuleActionHandler<AzureQueueAction> |
||||
|
{ |
||||
|
private readonly ClientPool<(string ConnectionString, string QueueName), CloudQueue> clients; |
||||
|
private readonly RuleEventFormatter formatter; |
||||
|
|
||||
|
public AzureQueueActionHandler(RuleEventFormatter formatter) |
||||
|
{ |
||||
|
Guard.NotNull(formatter, nameof(formatter)); |
||||
|
|
||||
|
this.formatter = formatter; |
||||
|
|
||||
|
clients = new ClientPool<(string ConnectionString, string QueueName), CloudQueue>(key => |
||||
|
{ |
||||
|
var storageAccount = CloudStorageAccount.Parse(key.ConnectionString); |
||||
|
|
||||
|
var queueClient = storageAccount.CreateCloudQueueClient(); |
||||
|
var queueRef = queueClient.GetQueueReference(key.QueueName); |
||||
|
|
||||
|
return queueRef; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, AzureQueueAction action) |
||||
|
{ |
||||
|
var body = formatter.ToRouteData(@event, eventName); |
||||
|
|
||||
|
var ruleDescription = $"Send event to azure queue '{action.Queue}'"; |
||||
|
var ruleData = new RuleJobData |
||||
|
{ |
||||
|
["QueueConnectionString"] = action.ConnectionString, |
||||
|
["QueueName"] = action.Queue, |
||||
|
["MessageBody"] = body |
||||
|
}; |
||||
|
|
||||
|
return (ruleDescription, ruleData); |
||||
|
} |
||||
|
|
||||
|
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) |
||||
|
{ |
||||
|
var queueConnectionString = job["QueueConnectionString"].Value<string>(); |
||||
|
var queueName = job["QueueName"].Value<string>(); |
||||
|
|
||||
|
var queue = clients.GetClient((queueConnectionString, queueName)); |
||||
|
|
||||
|
var messageBody = job["MessageBody"].ToString(Formatting.Indented); |
||||
|
|
||||
|
await queue.AddMessageAsync(new CloudQueueMessage(messageBody)); |
||||
|
|
||||
|
return ("Completed", null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Squidex.Infrastructure.Http; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.Actions |
||||
|
{ |
||||
|
public sealed class FastlyActionHandler : RuleActionHandler<FastlyAction> |
||||
|
{ |
||||
|
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, FastlyAction action) |
||||
|
{ |
||||
|
var ruleDescription = "Purge key in fastly"; |
||||
|
var ruleData = new RuleJobData |
||||
|
{ |
||||
|
["FastlyApiKey"] = action.ApiKey, |
||||
|
["FastlyServiceID"] = action.ServiceId |
||||
|
}; |
||||
|
|
||||
|
if (@event.Headers.Contains(CommonHeaders.AggregateId)) |
||||
|
{ |
||||
|
ruleData["Key"] = @event.Headers.AggregateId().ToString(); |
||||
|
} |
||||
|
|
||||
|
return (ruleDescription, ruleData); |
||||
|
} |
||||
|
|
||||
|
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) |
||||
|
{ |
||||
|
if (!job.TryGetValue("Key", out var keyToken)) |
||||
|
{ |
||||
|
return (null, new InvalidOperationException("The action cannot handle this event.")); |
||||
|
} |
||||
|
|
||||
|
var requestMsg = BuildRequest(job, keyToken.Value<string>()); |
||||
|
|
||||
|
HttpResponseMessage response = null; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
response = await HttpClientPool.GetHttpClient().SendAsync(requestMsg); |
||||
|
|
||||
|
var responseString = await response.Content.ReadAsStringAsync(); |
||||
|
var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, responseString, TimeSpan.Zero, false); |
||||
|
|
||||
|
return (requestDump, null); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
if (requestMsg != null) |
||||
|
{ |
||||
|
var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, ex.ToString(), TimeSpan.Zero, false); |
||||
|
|
||||
|
return (requestDump, ex); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var requestDump = ex.ToString(); |
||||
|
|
||||
|
return (requestDump, ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static HttpRequestMessage BuildRequest(Dictionary<string, JToken> job, string key) |
||||
|
{ |
||||
|
var serviceId = job["FastlyServiceID"].Value<string>(); |
||||
|
|
||||
|
var requestUrl = $"https://api.fastly.com/service/{serviceId}/purge/{key}"; |
||||
|
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl); |
||||
|
|
||||
|
request.Headers.Add("Fastly-Key", job["FastlyApiKey"].Value<string>()); |
||||
|
|
||||
|
return request; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Squidex.Infrastructure.Http; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.Actions |
||||
|
{ |
||||
|
public sealed class SlackActionHandler : RuleActionHandler<SlackAction> |
||||
|
{ |
||||
|
private readonly RuleEventFormatter formatter; |
||||
|
|
||||
|
public SlackActionHandler(RuleEventFormatter formatter) |
||||
|
{ |
||||
|
Guard.NotNull(formatter, nameof(formatter)); |
||||
|
|
||||
|
this.formatter = formatter; |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, SlackAction action) |
||||
|
{ |
||||
|
var body = CreatePayload(@event, action.Text); |
||||
|
|
||||
|
var ruleDescription = "Send message to slack"; |
||||
|
var ruleData = new RuleJobData |
||||
|
{ |
||||
|
["RequestUrl"] = action.WebhookUrl, |
||||
|
["RequestBody"] = body |
||||
|
}; |
||||
|
|
||||
|
return (ruleDescription, ruleData); |
||||
|
} |
||||
|
|
||||
|
private JObject CreatePayload(Envelope<AppEvent> @event, string text) |
||||
|
{ |
||||
|
return new JObject(new JProperty("text", formatter.FormatString(text, @event))); |
||||
|
} |
||||
|
|
||||
|
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) |
||||
|
{ |
||||
|
var requestBody = job["RequestBody"].ToString(Formatting.Indented); |
||||
|
var requestMsg = BuildRequest(job, requestBody); |
||||
|
|
||||
|
HttpResponseMessage response = null; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
response = await HttpClientPool.GetHttpClient().SendAsync(requestMsg); |
||||
|
|
||||
|
var responseString = await response.Content.ReadAsStringAsync(); |
||||
|
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, responseString, TimeSpan.Zero, false); |
||||
|
|
||||
|
return (requestDump, null); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
if (requestMsg != null) |
||||
|
{ |
||||
|
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, ex.ToString(), TimeSpan.Zero, false); |
||||
|
|
||||
|
return (requestDump, ex); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var requestDump = ex.ToString(); |
||||
|
|
||||
|
return (requestDump, ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static HttpRequestMessage BuildRequest(Dictionary<string, JToken> job, string requestBody) |
||||
|
{ |
||||
|
var requestUrl = job["RequestUrl"].Value<string>(); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl) |
||||
|
{ |
||||
|
Content = new StringContent(requestBody, Encoding.UTF8, "application/json") |
||||
|
}; |
||||
|
|
||||
|
return request; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
|
||||
|
#pragma warning disable RECS0108 // Warns about static fields in generic types
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
internal sealed class ClientPool<TKey, TClient> |
||||
|
{ |
||||
|
private static readonly TimeSpan TTL = TimeSpan.FromMinutes(30); |
||||
|
private readonly MemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); |
||||
|
private readonly Func<TKey, TClient> factory; |
||||
|
|
||||
|
public ClientPool(Func<TKey, TClient> factory) |
||||
|
{ |
||||
|
this.factory = factory; |
||||
|
} |
||||
|
|
||||
|
public TClient GetClient(TKey key) |
||||
|
{ |
||||
|
if (!memoryCache.TryGetValue<TClient>(key, out var client)) |
||||
|
{ |
||||
|
client = factory(key); |
||||
|
|
||||
|
memoryCache.Set(key, client, TTL); |
||||
|
} |
||||
|
|
||||
|
return client; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public static class HttpClientPool |
||||
|
{ |
||||
|
private static readonly ClientPool<string, HttpClient> Pool = new ClientPool<string, HttpClient>(key => |
||||
|
{ |
||||
|
return new HttpClient { Timeout = TimeSpan.FromSeconds(2) }; |
||||
|
}); |
||||
|
|
||||
|
public static HttpClient GetHttpClient() |
||||
|
{ |
||||
|
return Pool.GetClient(string.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,170 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// =========================================-=================================
|
||||
|
|
||||
|
using System.Globalization; |
||||
|
using System.Text; |
||||
|
using System.Text.RegularExpressions; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Domain.Apps.Events.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public class RuleEventFormatter |
||||
|
{ |
||||
|
private const string Undefined = "UNDEFINED"; |
||||
|
private const string AppIdPlaceholder = "$APP_ID"; |
||||
|
private const string AppNamePlaceholder = "$APP_NAME"; |
||||
|
private const string SchemaIdPlaceholder = "$SCHEMA_ID"; |
||||
|
private const string SchemaNamePlaceholder = "$SCHEMA_NAME"; |
||||
|
private const string TimestampDatePlaceholder = "$TIMESTAMP_DATE"; |
||||
|
private const string TimestampDateTimePlaceholder = "$TIMESTAMP_DATETIME"; |
||||
|
private const string ContentActionPlaceholder = "$CONTENT_ACTION"; |
||||
|
private static readonly Regex ContentDataPlaceholder = new Regex(@"\$CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); |
||||
|
private readonly JsonSerializer serializer; |
||||
|
|
||||
|
public RuleEventFormatter(JsonSerializer serializer) |
||||
|
{ |
||||
|
Guard.NotNull(serializer, nameof(serializer)); |
||||
|
|
||||
|
this.serializer = serializer; |
||||
|
} |
||||
|
|
||||
|
public virtual JToken ToRouteData(object value) |
||||
|
{ |
||||
|
return JToken.FromObject(value, serializer); |
||||
|
} |
||||
|
|
||||
|
public virtual JToken ToRouteData(Envelope<AppEvent> @event, string eventName) |
||||
|
{ |
||||
|
return new JObject( |
||||
|
new JProperty("type", eventName), |
||||
|
new JProperty("payload", JToken.FromObject(@event.Payload, serializer)), |
||||
|
new JProperty("timestamp", @event.Headers.Timestamp().ToString())); |
||||
|
} |
||||
|
|
||||
|
public virtual string FormatString(string text, Envelope<AppEvent> @event) |
||||
|
{ |
||||
|
var sb = new StringBuilder(text); |
||||
|
|
||||
|
if (@event.Headers.Contains(CommonHeaders.Timestamp)) |
||||
|
{ |
||||
|
var timestamp = @event.Headers.Timestamp().ToDateTimeUtc(); |
||||
|
|
||||
|
sb.Replace(TimestampDateTimePlaceholder, timestamp.ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture)); |
||||
|
sb.Replace(TimestampDatePlaceholder, timestamp.ToString("yyy-MM-dd", CultureInfo.InvariantCulture)); |
||||
|
} |
||||
|
|
||||
|
if (@event.Payload.AppId != null) |
||||
|
{ |
||||
|
sb.Replace(AppIdPlaceholder, @event.Payload.AppId.Id.ToString()); |
||||
|
sb.Replace(AppNamePlaceholder, @event.Payload.AppId.Name); |
||||
|
} |
||||
|
|
||||
|
if (@event.Payload is SchemaEvent schemaEvent && schemaEvent.SchemaId != null) |
||||
|
{ |
||||
|
sb.Replace(SchemaIdPlaceholder, schemaEvent.SchemaId.Id.ToString()); |
||||
|
sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name); |
||||
|
} |
||||
|
|
||||
|
FormatContentAction(@event, sb); |
||||
|
|
||||
|
var result = sb.ToString(); |
||||
|
|
||||
|
if (@event.Payload is ContentCreated contentCreated && contentCreated.Data != null) |
||||
|
{ |
||||
|
result = ReplaceData(contentCreated.Data, result); |
||||
|
} |
||||
|
|
||||
|
if (@event.Payload is ContentUpdated contentUpdated && contentUpdated.Data != null) |
||||
|
{ |
||||
|
result = ReplaceData(contentUpdated.Data, result); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private static void FormatContentAction(Envelope<AppEvent> @event, StringBuilder sb) |
||||
|
{ |
||||
|
switch (@event.Payload) |
||||
|
{ |
||||
|
case ContentCreated contentCreated: |
||||
|
sb.Replace(ContentActionPlaceholder, "created"); |
||||
|
break; |
||||
|
|
||||
|
case ContentUpdated contentUpdated: |
||||
|
sb.Replace(ContentActionPlaceholder, "updated"); |
||||
|
break; |
||||
|
|
||||
|
case ContentStatusChanged contentStatusChanged: |
||||
|
sb.Replace(ContentActionPlaceholder, $"set to {contentStatusChanged.Status.ToString().ToLowerInvariant()}"); |
||||
|
break; |
||||
|
|
||||
|
case ContentDeleted contentDeleted: |
||||
|
sb.Replace(ContentActionPlaceholder, "deleted"); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string ReplaceData(NamedContentData data, string text) |
||||
|
{ |
||||
|
return ContentDataPlaceholder.Replace(text, match => |
||||
|
{ |
||||
|
var captures = match.Groups[2].Captures; |
||||
|
|
||||
|
var path = new string[captures.Count]; |
||||
|
|
||||
|
for (var i = 0; i < path.Length; i++) |
||||
|
{ |
||||
|
path[i] = captures[i].Value; |
||||
|
} |
||||
|
|
||||
|
if (!data.TryGetValue(path[0], out var field)) |
||||
|
{ |
||||
|
return Undefined; |
||||
|
} |
||||
|
|
||||
|
if (!field.TryGetValue(path[1], out var value)) |
||||
|
{ |
||||
|
return Undefined; |
||||
|
} |
||||
|
|
||||
|
for (var j = 2; j < path.Length; j++) |
||||
|
{ |
||||
|
if (value is JObject obj && obj.TryGetValue(path[j], out value)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
if (value is JArray arr && int.TryParse(path[j], out var idx) && idx >= 0 && idx < arr.Count) |
||||
|
{ |
||||
|
value = arr[idx]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return Undefined; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined) |
||||
|
{ |
||||
|
return Undefined; |
||||
|
} |
||||
|
|
||||
|
if (value is JValue jValue && jValue != null) |
||||
|
{ |
||||
|
return jValue.Value.ToString(); |
||||
|
} |
||||
|
|
||||
|
return value?.ToString(Formatting.Indented) ?? Undefined; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Domain.Apps.Events.Assets; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.Triggers |
||||
|
{ |
||||
|
public sealed class AssetChangedTriggerHandler : RuleTriggerHandler<AssetChangedTrigger> |
||||
|
{ |
||||
|
protected override bool Triggers(Envelope<AppEvent> @event, AssetChangedTrigger trigger) |
||||
|
{ |
||||
|
return @event.Payload is AssetEvent assetEvent && MatchsType(trigger, assetEvent); |
||||
|
} |
||||
|
|
||||
|
private static bool MatchsType(AssetChangedTrigger trigger, AssetEvent @event) |
||||
|
{ |
||||
|
return |
||||
|
(trigger.SendCreate && @event is AssetCreated) || |
||||
|
(trigger.SendUpdate && @event is AssetUpdated) || |
||||
|
(trigger.SendDelete && @event is AssetDeleted) || |
||||
|
(trigger.SendRename && @event is AssetRenamed); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.OData.UriParser; |
||||
|
using MongoDB.Bson; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.MongoDb.OData; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors |
||||
|
{ |
||||
|
public static class FindExtensions |
||||
|
{ |
||||
|
private static readonly FilterDefinitionBuilder<MongoAssetEntity> Filter = Builders<MongoAssetEntity>.Filter; |
||||
|
private static readonly PropertyCalculator PropertyCalculator = propertyNames => |
||||
|
{ |
||||
|
if (propertyNames.Length > 0) |
||||
|
{ |
||||
|
propertyNames[0] = propertyNames[0].ToPascalCase(); |
||||
|
} |
||||
|
|
||||
|
var propertyName = string.Join(".", propertyNames); |
||||
|
|
||||
|
return propertyName; |
||||
|
}; |
||||
|
|
||||
|
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSort(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query) |
||||
|
{ |
||||
|
var sort = query.BuildSort<MongoAssetEntity>(PropertyCalculator); |
||||
|
|
||||
|
return sort != null ? cursor.Sort(sort) : cursor.SortByDescending(x => x.LastModified); |
||||
|
} |
||||
|
|
||||
|
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetTake(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query) |
||||
|
{ |
||||
|
return cursor.Take(query, 200, 20); |
||||
|
} |
||||
|
|
||||
|
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSkip(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query) |
||||
|
{ |
||||
|
return cursor.Skip(query); |
||||
|
} |
||||
|
|
||||
|
public static FilterDefinition<MongoAssetEntity> BuildQuery(ODataUriParser query, Guid appId) |
||||
|
{ |
||||
|
var filters = new List<FilterDefinition<MongoAssetEntity>> |
||||
|
{ |
||||
|
Filter.Eq(x => x.AppIdId, appId), |
||||
|
Filter.Eq(x => x.IsDeleted, false) |
||||
|
}; |
||||
|
|
||||
|
var filter = query.BuildFilter<MongoAssetEntity>(PropertyCalculator, false); |
||||
|
|
||||
|
if (filter.Filter != null) |
||||
|
{ |
||||
|
if (filter.Last) |
||||
|
{ |
||||
|
filters.Add(filter.Filter); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
filters.Insert(0, filter.Filter); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (filters.Count > 1) |
||||
|
{ |
||||
|
return Filter.And(filters); |
||||
|
} |
||||
|
else if (filters.Count == 1) |
||||
|
{ |
||||
|
return filters[0]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return new BsonDocument(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,237 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
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 CreateBlogCommandMiddleware : ICommandMiddleware |
||||
|
{ |
||||
|
private const string TemplateName = "Blog"; |
||||
|
private const string SlugScript = @"
|
||||
|
var data = ctx.data; |
||||
|
|
||||
|
data.slug = { iv: slugify(data.title.iv) }; |
||||
|
|
||||
|
replace(data);";
|
||||
|
|
||||
|
public Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
if (context.IsCompleted && context.Command is CreateApp createApp && IsRightTemplate(createApp)) |
||||
|
{ |
||||
|
var appId = new NamedId<Guid>(createApp.AppId, createApp.Name); |
||||
|
|
||||
|
return Task.WhenAll( |
||||
|
CreatePagesAsync(context.CommandBus, appId), |
||||
|
CreatePostsAsync(context.CommandBus, appId), |
||||
|
CreateClientAsync(context.CommandBus, appId)); |
||||
|
} |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
|
||||
|
private static bool IsRightTemplate(CreateApp createApp) |
||||
|
{ |
||||
|
return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase); |
||||
|
} |
||||
|
|
||||
|
private static async Task CreateClientAsync(ICommandBus bus, NamedId<Guid> appId) |
||||
|
{ |
||||
|
await bus.PublishAsync(new AttachClient { Id = "sample-client" }); |
||||
|
} |
||||
|
|
||||
|
private async Task CreatePostsAsync(ICommandBus bus, NamedId<Guid> appId) |
||||
|
{ |
||||
|
var postsId = await CreatePostsSchema(bus, appId); |
||||
|
|
||||
|
await bus.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(ICommandBus bus, NamedId<Guid> appId) |
||||
|
{ |
||||
|
var pagesId = await CreatePagesSchema(bus, appId); |
||||
|
|
||||
|
await bus.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<NamedId<Guid>> CreatePostsSchema(ICommandBus bus, NamedId<Guid> appId) |
||||
|
{ |
||||
|
var command = new CreateSchema |
||||
|
{ |
||||
|
Name = "posts", |
||||
|
Publish = true, |
||||
|
Properties = new SchemaProperties |
||||
|
{ |
||||
|
Label = "Posts" |
||||
|
}, |
||||
|
Fields = new List<CreateSchemaField> |
||||
|
{ |
||||
|
new CreateSchemaField |
||||
|
{ |
||||
|
Name = "title", |
||||
|
Partitioning = Partitioning.Invariant.Key, |
||||
|
Properties = new StringFieldProperties |
||||
|
{ |
||||
|
Editor = StringFieldEditor.Input, |
||||
|
IsRequired = true, |
||||
|
IsListField = true, |
||||
|
MaxLength = 100, |
||||
|
MinLength = 0, |
||||
|
Label = "Title" |
||||
|
} |
||||
|
}, |
||||
|
new CreateSchemaField |
||||
|
{ |
||||
|
Name = "slug", |
||||
|
Partitioning = Partitioning.Invariant.Key, |
||||
|
Properties = new StringFieldProperties |
||||
|
{ |
||||
|
Editor = StringFieldEditor.Slug, |
||||
|
IsRequired = false, |
||||
|
IsListField = true, |
||||
|
MaxLength = 100, |
||||
|
MinLength = 0, |
||||
|
Label = "Slug (Autogenerated)" |
||||
|
}, |
||||
|
IsDisabled = true |
||||
|
}, |
||||
|
new CreateSchemaField |
||||
|
{ |
||||
|
Name = "text", |
||||
|
Partitioning = Partitioning.Invariant.Key, |
||||
|
Properties = new StringFieldProperties |
||||
|
{ |
||||
|
Editor = StringFieldEditor.RichText, |
||||
|
IsRequired = true, |
||||
|
IsListField = false, |
||||
|
Label = "Text" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
AppId = appId |
||||
|
}; |
||||
|
|
||||
|
await bus.PublishAsync(command); |
||||
|
|
||||
|
var schemaId = new NamedId<Guid>(command.SchemaId, command.Name); |
||||
|
|
||||
|
await bus.PublishAsync(new ConfigureScripts |
||||
|
{ |
||||
|
SchemaId = schemaId.Id, |
||||
|
ScriptCreate = SlugScript, |
||||
|
ScriptUpdate = SlugScript |
||||
|
}); |
||||
|
|
||||
|
return schemaId; |
||||
|
} |
||||
|
|
||||
|
private async Task<NamedId<Guid>> CreatePagesSchema(ICommandBus bus, NamedId<Guid> appId) |
||||
|
{ |
||||
|
var command = new CreateSchema |
||||
|
{ |
||||
|
Name = "pages", |
||||
|
Properties = new SchemaProperties |
||||
|
{ |
||||
|
Label = "Pages" |
||||
|
}, |
||||
|
Fields = new List<CreateSchemaField> |
||||
|
{ |
||||
|
new CreateSchemaField |
||||
|
{ |
||||
|
Name = "title", |
||||
|
Partitioning = Partitioning.Invariant.Key, |
||||
|
Properties = new StringFieldProperties |
||||
|
{ |
||||
|
Editor = StringFieldEditor.Input, |
||||
|
IsRequired = true, |
||||
|
IsListField = true, |
||||
|
MaxLength = 100, |
||||
|
MinLength = 0, |
||||
|
Label = "Title" |
||||
|
} |
||||
|
}, |
||||
|
new CreateSchemaField |
||||
|
{ |
||||
|
Name = "slug", |
||||
|
Partitioning = Partitioning.Invariant.Key, |
||||
|
Properties = new StringFieldProperties |
||||
|
{ |
||||
|
Editor = StringFieldEditor.Slug, |
||||
|
IsRequired = false, |
||||
|
IsListField = true, |
||||
|
MaxLength = 100, |
||||
|
MinLength = 0, |
||||
|
Label = "Slug (Autogenerated)" |
||||
|
}, |
||||
|
IsDisabled = true |
||||
|
}, |
||||
|
new CreateSchemaField |
||||
|
{ |
||||
|
Name = "text", |
||||
|
Partitioning = Partitioning.Invariant.Key, |
||||
|
Properties = new StringFieldProperties |
||||
|
{ |
||||
|
Editor = StringFieldEditor.RichText, |
||||
|
IsRequired = true, |
||||
|
IsListField = false, |
||||
|
Label = "Text" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
AppId = appId |
||||
|
}; |
||||
|
|
||||
|
await bus.PublishAsync(command); |
||||
|
|
||||
|
var schemaId = new NamedId<Guid>(command.SchemaId, command.Name); |
||||
|
|
||||
|
await bus.PublishAsync(new ConfigureScripts |
||||
|
{ |
||||
|
SchemaId = schemaId.Id, |
||||
|
ScriptCreate = SlugScript, |
||||
|
ScriptUpdate = SlugScript |
||||
|
}); |
||||
|
|
||||
|
return schemaId; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.OData.Edm; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Edm |
||||
|
{ |
||||
|
public static class EdmAssetModel |
||||
|
{ |
||||
|
public static readonly IEdmModel Edm; |
||||
|
|
||||
|
static EdmAssetModel() |
||||
|
{ |
||||
|
var entityType = new EdmEntityType("Squidex", "Asset"); |
||||
|
|
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.Created).ToCamelCase(), EdmPrimitiveTypeKind.DateTimeOffset); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.CreatedBy).ToCamelCase(), EdmPrimitiveTypeKind.String); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.LastModified).ToCamelCase(), EdmPrimitiveTypeKind.DateTimeOffset); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.LastModifiedBy).ToCamelCase(), EdmPrimitiveTypeKind.String); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.Version).ToCamelCase(), EdmPrimitiveTypeKind.Int64); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.FileName).ToCamelCase(), EdmPrimitiveTypeKind.String); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.FileSize).ToCamelCase(), EdmPrimitiveTypeKind.Int64); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.FileVersion).ToCamelCase(), EdmPrimitiveTypeKind.Int64); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.IsImage).ToCamelCase(), EdmPrimitiveTypeKind.Boolean); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.MimeType).ToCamelCase(), EdmPrimitiveTypeKind.String); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.PixelHeight).ToCamelCase(), EdmPrimitiveTypeKind.Int32); |
||||
|
entityType.AddStructuralProperty(nameof(IAssetEntity.PixelWidth).ToCamelCase(), EdmPrimitiveTypeKind.Int32); |
||||
|
|
||||
|
var container = new EdmEntityContainer("Squidex", "Container"); |
||||
|
|
||||
|
container.AddEntitySet("AssetSet", entityType); |
||||
|
|
||||
|
var model = new EdmModel(); |
||||
|
|
||||
|
model.AddElement(container); |
||||
|
model.AddElement(entityType); |
||||
|
|
||||
|
Edm = model; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue