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