mirror of https://github.com/Squidex/squidex.git
Browse Source
# Conflicts: # src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj # src/Squidex.Domain.Apps.Entities/AppProvider.cs # src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs # src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs # src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs # src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs # src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangeAsyncResult.cs # src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs # src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs # src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs # src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs # src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs # src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs # src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs # src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs # src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs # src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs # src/Squidex.Infrastructure/Orleans/IBackgroundGrain.cs # src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs # src/Squidex.Infrastructure/States/StateFactory.cs # src/Squidex/Config/Domain/WriteServices.cs # src/Squidex/Squidex.csproj # tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs # tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs # tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetGrainTests.cs # tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs # tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs # tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs # tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AssertHelper.cs # tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs # tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs # tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs # tests/Squidex.Infrastructure.Tests/States/StateFactoryTests.cs # tools/Migrate_01/Migrations/AddPatterns.cs # tools/Migrate_01/Rebuilder.cspull/249/head
159 changed files with 3991 additions and 2377 deletions
@ -0,0 +1,25 @@ |
|||
FROM microsoft/aspnetcore-build:2.0.3-jessie |
|||
|
|||
# Install runtime dependencies |
|||
RUN apt-get update \ |
|||
&& apt-get install -y --no-install-recommends ca-certificates bzip2 libfontconfig \ |
|||
&& apt-get clean \ |
|||
&& rm -rf /var/lib/apt/lists/* |
|||
|
|||
# Install official PhantomJS release |
|||
RUN set -x \ |
|||
&& apt-get update \ |
|||
&& apt-get install -y --no-install-recommends \ |
|||
&& mkdir /srv/var \ |
|||
&& mkdir /tmp/phantomjs \ |
|||
# Download Phantom JS |
|||
&& curl -L https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 | tar -xj --strip-components=1 -C /tmp/phantomjs \ |
|||
# Copy binaries only |
|||
&& mv /tmp/phantomjs/bin/phantomjs /usr/local/bin \ |
|||
# Create symbol link |
|||
# Clean up |
|||
&& apt-get autoremove -y \ |
|||
&& apt-get clean all \ |
|||
&& rm -rf /tmp/* /var/lib/apt/lists/* |
|||
|
|||
RUN phantomjs --version |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Rules.Actions |
|||
{ |
|||
[TypeName(nameof(ElasticSearchAction))] |
|||
public sealed class ElasticSearchAction : RuleAction |
|||
{ |
|||
public Uri Host { get; set; } |
|||
|
|||
public string Username { get; set; } |
|||
|
|||
public string Password { get; set; } |
|||
|
|||
public string IndexName { get; set; } |
|||
|
|||
public string IndexType { get; set; } |
|||
|
|||
public override T Accept<T>(IRuleActionVisitor<T> visitor) |
|||
{ |
|||
return visitor.Visit(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,180 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Elasticsearch.Net; |
|||
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 ElasticSearchActionHandler : RuleActionHandler<ElasticSearchAction> |
|||
{ |
|||
private readonly ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient> clients; |
|||
private readonly RuleEventFormatter formatter; |
|||
|
|||
public ElasticSearchActionHandler(RuleEventFormatter formatter) |
|||
{ |
|||
Guard.NotNull(formatter, nameof(formatter)); |
|||
|
|||
this.formatter = formatter; |
|||
|
|||
clients = new ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient>(key => |
|||
{ |
|||
var config = new ConnectionConfiguration(key.Host); |
|||
|
|||
if (!string.IsNullOrEmpty(key.Username) && !string.IsNullOrWhiteSpace(key.Password)) |
|||
{ |
|||
config = config.BasicAuthentication(key.Username, key.Password); |
|||
} |
|||
|
|||
return new ElasticLowLevelClient(config); |
|||
}); |
|||
} |
|||
|
|||
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, ElasticSearchAction action) |
|||
{ |
|||
var ruleDescription = string.Empty; |
|||
var ruleData = new RuleJobData |
|||
{ |
|||
["Host"] = action.Host, |
|||
["Username"] = action.Username, |
|||
["Password"] = action.Password |
|||
}; |
|||
|
|||
if (@event.Payload is ContentEvent contentEvent) |
|||
{ |
|||
ruleData["ContentId"] = contentEvent.ContentId.ToString(); |
|||
ruleData["IndexName"] = formatter.FormatString(action.IndexName, @event); |
|||
ruleData["IndexType"] = formatter.FormatString(action.IndexType, @event); |
|||
|
|||
var timestamp = @event.Headers.Timestamp().ToString(); |
|||
|
|||
switch (@event.Payload) |
|||
{ |
|||
case ContentCreated created: |
|||
{ |
|||
ruleDescription = $"Add entry to ES index: {action.IndexName}"; |
|||
|
|||
ruleData["Operation"] = "Create"; |
|||
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 ES index: {action.IndexName}"; |
|||
|
|||
ruleData["Operation"] = "Update"; |
|||
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 ES index: {action.IndexName}"; |
|||
|
|||
ruleData["Operation"] = "Update"; |
|||
ruleData["Content"] = new JObject( |
|||
new JProperty("lastModified", timestamp), |
|||
new JProperty("lastModifiedBy", statusChanged.Actor.ToString()), |
|||
new JProperty("status", statusChanged.Status.ToString())); |
|||
break; |
|||
} |
|||
|
|||
case ContentDeleted deleted: |
|||
{ |
|||
ruleDescription = $"Delete entry from ES index: {action.IndexName}"; |
|||
|
|||
ruleData["Operation"] = "Delete"; |
|||
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 host = new Uri(job["Host"].Value<string>(), UriKind.Absolute); |
|||
|
|||
var username = job["Username"].Value<string>(); |
|||
var password = job["Password"].Value<string>(); |
|||
|
|||
var client = clients.GetClient((host, username, password)); |
|||
|
|||
var indexName = job["IndexName"].Value<string>(); |
|||
var indexType = job["IndexType"].Value<string>(); |
|||
|
|||
var operation = operationToken.Value<string>(); |
|||
var content = job["Content"].Value<JObject>(); |
|||
var contentId = job["ContentId"].Value<string>(); |
|||
|
|||
try |
|||
{ |
|||
switch (operation) |
|||
{ |
|||
case "Create": |
|||
{ |
|||
var doc = content.ToString(); |
|||
|
|||
var response = await client.IndexAsync<StringResponse>(indexName, indexType, contentId, doc); |
|||
|
|||
return (response.Body, response.OriginalException); |
|||
} |
|||
|
|||
case "Update": |
|||
{ |
|||
var doc = new JObject(new JProperty("doc", content)).ToString(); |
|||
|
|||
var response = await client.UpdateAsync<StringResponse>(indexName, indexType, contentId, doc); |
|||
|
|||
return (response.Body, response.OriginalException); |
|||
} |
|||
|
|||
case "Delete": |
|||
{ |
|||
var response = await client.DeleteAsync<StringResponse>(indexName, indexType, contentId); |
|||
|
|||
return (response.Body, response.OriginalException); |
|||
} |
|||
|
|||
default: |
|||
return (null, null); |
|||
} |
|||
} |
|||
catch (ElasticsearchClientException ex) |
|||
{ |
|||
return (ex.Message, ex); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,603 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Threading.Tasks; |
|||
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; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Templates |
|||
{ |
|||
public sealed class CreateProfileCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private const string TemplateName = "Profile"; |
|||
|
|||
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); |
|||
|
|||
var publish = new Func<ICommand, Task>(command => |
|||
{ |
|||
if (command is IAppCommand appCommand) |
|||
{ |
|||
appCommand.AppId = appId; |
|||
} |
|||
|
|||
return context.CommandBus.PublishAsync(command); |
|||
}); |
|||
|
|||
return Task.WhenAll( |
|||
CreateBasicsAsync(publish), |
|||
CreateProjectsSchemaAsync(publish), |
|||
CreateExperienceSchemaAsync(publish), |
|||
CreateSkillsSchemaAsync(publish), |
|||
CreateEducationSchemaAsync(publish), |
|||
CreatePublicationsSchemaAsync(publish), |
|||
CreateClientAsync(publish, appId.Id)); |
|||
} |
|||
|
|||
return next(); |
|||
} |
|||
|
|||
private static bool IsRightTemplate(CreateApp createApp) |
|||
{ |
|||
return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
private static async Task CreateClientAsync(Func<ICommand, Task> publish, Guid appId) |
|||
{ |
|||
await publish(new AttachClient { Id = "sample-client", AppId = appId }); |
|||
} |
|||
|
|||
private async Task CreateBasicsAsync(Func<ICommand, Task> publish) |
|||
{ |
|||
var postsId = await CreateBasicsSchemaAsync(publish); |
|||
|
|||
await publish(new CreateContent |
|||
{ |
|||
SchemaId = postsId, |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("firstName", |
|||
new ContentFieldData() |
|||
.AddValue("iv", "John")) |
|||
.AddField("lastName", |
|||
new ContentFieldData() |
|||
.AddValue("iv", "Doe")) |
|||
.AddField("profession", |
|||
new ContentFieldData() |
|||
.AddValue("iv", "Software Developer")), |
|||
Publish = true, |
|||
}); |
|||
} |
|||
|
|||
private async Task<NamedId<Guid>> CreateBasicsSchemaAsync(Func<ICommand, Task> publish) |
|||
{ |
|||
var command = new CreateSchema |
|||
{ |
|||
Name = "basics", |
|||
Properties = new SchemaProperties |
|||
{ |
|||
Label = "Basics" |
|||
}, |
|||
Fields = new List<CreateSchemaField> |
|||
{ |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "firstName", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "First Name", |
|||
Hints = "Your first name" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "lastName", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "Last Name", |
|||
Hints = "Your last name" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "profession", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.TextArea, |
|||
IsRequired = true, |
|||
IsListField = false, |
|||
Label = "Profession", |
|||
Hints = "Define your profession" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "image", |
|||
Properties = new AssetsFieldProperties |
|||
{ |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
MustBeImage = true, |
|||
Label = "Image", |
|||
Hints = "Your image" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "summary", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.TextArea, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Summary", |
|||
Hints = "Write a short summary about yourself" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "githubLink", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Github", |
|||
Hints = "An optional link to your Github account" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "blogLink", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Blog", |
|||
Hints = "An optional link to your blog" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "twitterLink", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Twitter", |
|||
Hints = "An optional link to your twitter account" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "linkedInLink", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "LinkedIn", |
|||
Hints = "An optional link to your LinkedIn account" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "emailAddress", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Email Address", |
|||
Hints = "An optional email address to contact you" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "legalTerms", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.TextArea, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Legal terms", |
|||
Hints = "The terms to fulfill legal requirements" |
|||
} |
|||
} |
|||
}, |
|||
Publish = true |
|||
}; |
|||
|
|||
await publish(command); |
|||
|
|||
return new NamedId<Guid>(command.SchemaId, command.Name); |
|||
} |
|||
|
|||
private async Task<NamedId<Guid>> CreateProjectsSchemaAsync(Func<ICommand, Task> publish) |
|||
{ |
|||
var command = new CreateSchema |
|||
{ |
|||
Name = "projects", |
|||
Properties = new SchemaProperties |
|||
{ |
|||
Label = "Projects" |
|||
}, |
|||
Fields = new List<CreateSchemaField> |
|||
{ |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "name", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "Name", |
|||
Hints = "The name of the projection" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "description", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.TextArea, |
|||
IsRequired = true, |
|||
IsListField = false, |
|||
Label = "Description", |
|||
Hints = "Describe your project" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "image", |
|||
Properties = new AssetsFieldProperties |
|||
{ |
|||
IsRequired = true, |
|||
IsListField = false, |
|||
MustBeImage = true, |
|||
Label = "Image", |
|||
Hints = "An image or screenshot for your project" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "label", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Label", |
|||
Hints = "An optional label to categorize your project, e.g. 'Open Source'" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "link", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "link", |
|||
Hints = "The logo of the company or organization you worked for" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "year", |
|||
Properties = new NumberFieldProperties |
|||
{ |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Year", |
|||
Hints = "The year, when you realized the project, used for sorting only" |
|||
} |
|||
} |
|||
}, |
|||
Publish = true |
|||
}; |
|||
|
|||
await publish(command); |
|||
|
|||
return new NamedId<Guid>(command.SchemaId, command.Name); |
|||
} |
|||
|
|||
private async Task<NamedId<Guid>> CreateExperienceSchemaAsync(Func<ICommand, Task> publish) |
|||
{ |
|||
var command = new CreateSchema |
|||
{ |
|||
Name = "experience", |
|||
Properties = new SchemaProperties |
|||
{ |
|||
Label = "Experience" |
|||
}, |
|||
Fields = new List<CreateSchemaField> |
|||
{ |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "position", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "Position", |
|||
Hints = "Your position in this job" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "company", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "Company", |
|||
Hints = "The company or organization you worked for" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "logo", |
|||
Properties = new AssetsFieldProperties |
|||
{ |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
MustBeImage = true, |
|||
Label = "Logo", |
|||
Hints = "The logo of the company or organization you worked for" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "from", |
|||
Properties = new DateTimeFieldProperties |
|||
{ |
|||
Editor = DateTimeFieldEditor.Date, |
|||
IsRequired = true, |
|||
IsListField = false, |
|||
Label = "Start Date", |
|||
Hints = "The start date" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "to", |
|||
Properties = new DateTimeFieldProperties |
|||
{ |
|||
Editor = DateTimeFieldEditor.Date, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "End Date", |
|||
Hints = "The end date, keep empty if you still work there" |
|||
} |
|||
} |
|||
}, |
|||
Publish = true |
|||
}; |
|||
|
|||
await publish(command); |
|||
|
|||
return new NamedId<Guid>(command.SchemaId, command.Name); |
|||
} |
|||
|
|||
private async Task<NamedId<Guid>> CreateEducationSchemaAsync(Func<ICommand, Task> publish) |
|||
{ |
|||
var command = new CreateSchema |
|||
{ |
|||
Name = "education", |
|||
Properties = new SchemaProperties |
|||
{ |
|||
Label = "Education" |
|||
}, |
|||
Fields = new List<CreateSchemaField> |
|||
{ |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "degree", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "Degree", |
|||
Hints = "The degree you got or achieved" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "school", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "School", |
|||
Hints = "The school or university" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "logo", |
|||
Properties = new AssetsFieldProperties |
|||
{ |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
MustBeImage = true, |
|||
Label = "Logo", |
|||
Hints = "The logo of the school" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "from", |
|||
Properties = new DateTimeFieldProperties |
|||
{ |
|||
Editor = DateTimeFieldEditor.Date, |
|||
IsRequired = true, |
|||
IsListField = false, |
|||
Label = "Start Date", |
|||
Hints = "The start date" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "to", |
|||
Properties = new DateTimeFieldProperties |
|||
{ |
|||
Editor = DateTimeFieldEditor.Date, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "End Date", |
|||
Hints = "The end date, keep empty if you still study there" |
|||
} |
|||
} |
|||
}, |
|||
Publish = true |
|||
}; |
|||
|
|||
await publish(command); |
|||
|
|||
return new NamedId<Guid>(command.SchemaId, command.Name); |
|||
} |
|||
|
|||
private async Task<NamedId<Guid>> CreatePublicationsSchemaAsync(Func<ICommand, Task> publish) |
|||
{ |
|||
var command = new CreateSchema |
|||
{ |
|||
Name = "publications", |
|||
Properties = new SchemaProperties |
|||
{ |
|||
Label = "Publications" |
|||
}, |
|||
Fields = new List<CreateSchemaField> |
|||
{ |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "name", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "Name", |
|||
Hints = "The name or title of your publication" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "description", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.TextArea, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Description", |
|||
Hints = "Describe the content of your publication" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "cover", |
|||
Properties = new AssetsFieldProperties |
|||
{ |
|||
IsRequired = true, |
|||
IsListField = false, |
|||
MustBeImage = true, |
|||
Label = "Cover", |
|||
Hints = "The cover of your publication" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "link", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = false, |
|||
IsListField = false, |
|||
Label = "Link", |
|||
Hints = "An optional link to your publication" |
|||
} |
|||
} |
|||
}, |
|||
Publish = true |
|||
}; |
|||
|
|||
await publish(command); |
|||
|
|||
return new NamedId<Guid>(command.SchemaId, command.Name); |
|||
} |
|||
|
|||
private async Task<NamedId<Guid>> CreateSkillsSchemaAsync(Func<ICommand, Task> publish) |
|||
{ |
|||
var command = new CreateSchema |
|||
{ |
|||
Name = "skills", |
|||
Properties = new SchemaProperties |
|||
{ |
|||
Label = "Skills" |
|||
}, |
|||
Fields = new List<CreateSchemaField> |
|||
{ |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "name", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Input, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
Label = "Name", |
|||
Hints = "The name for your skill" |
|||
} |
|||
}, |
|||
new CreateSchemaField |
|||
{ |
|||
Name = "experience", |
|||
Properties = new StringFieldProperties |
|||
{ |
|||
Editor = StringFieldEditor.Dropdown, |
|||
IsRequired = true, |
|||
IsListField = true, |
|||
AllowedValues = ImmutableList.Create("Beginner", "Advanced", "Professional", "Expert"), |
|||
Label = "Experience", |
|||
Hints = "The level of experience" |
|||
} |
|||
} |
|||
}, |
|||
Publish = true |
|||
}; |
|||
|
|||
await publish(command); |
|||
|
|||
return new NamedId<Guid>(command.SchemaId, command.Name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans |
|||
{ |
|||
public abstract class GrainOfGuid : Grain |
|||
{ |
|||
public override Task OnActivateAsync() |
|||
{ |
|||
return OnActivateAsync(this.GetPrimaryKey()); |
|||
} |
|||
|
|||
public virtual Task OnActivateAsync(Guid key) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans |
|||
{ |
|||
public abstract class GrainOfString : Grain |
|||
{ |
|||
public override Task OnActivateAsync() |
|||
{ |
|||
return OnActivateAsync(this.GetPrimaryKeyString()); |
|||
} |
|||
|
|||
public virtual Task OnActivateAsync(string key) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using NJsonSchema.Annotations; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Core.Rules.Actions; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions |
|||
{ |
|||
[JsonSchema("ElasticSearch")] |
|||
public sealed class ElasticSearchActionDto : RuleActionDto |
|||
{ |
|||
/// <summary>
|
|||
/// The host to the elastic search instance.
|
|||
/// </summary>
|
|||
[Required] |
|||
public Uri Host { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The name of the index.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string IndexName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The name of the index type.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string IndexType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The optional username for authentication.
|
|||
/// </summary>
|
|||
public string Username { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The optional password for authentication.
|
|||
/// </summary>
|
|||
public string Password { get; set; } |
|||
|
|||
public override RuleAction ToAction() |
|||
{ |
|||
return SimpleMapper.Map(this, new ElasticSearchAction()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
<h3 class="rule-title">Populate index in ElasticSearch with content</h3> |
|||
|
|||
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()"> |
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="host">Host</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="host" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="host" formControlName="host" placeholder="http://localhost:9200" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The url to your elastic search instance. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="username">Username</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="username" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="username" formControlName="username" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The username for authentication. Highly recommended. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="username">Password</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="password" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="password" formControlName="password" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The password for authentication. Highly recommended. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="sharedSecret">Index Name</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="indexName" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="indexName" formControlName="indexName" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The name of the index. You can use advanced formatting (read help section). |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="sharedSecret">Type Name</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="indexType" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="indexType" formControlName="indexType" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The name of the type. You can use advanced formatting (read help section). |
|||
</small> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
@ -0,0 +1,2 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -0,0 +1,70 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; |
|||
import { FormBuilder, Validators } from '@angular/forms'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-elastic-search-action', |
|||
styleUrls: ['./elastic-search-action.component.scss'], |
|||
templateUrl: './elastic-search-action.component.html' |
|||
}) |
|||
export class ElasticSearchActionComponent implements OnInit { |
|||
@Input() |
|||
public action: any; |
|||
|
|||
@Output() |
|||
public actionChanged = new EventEmitter<object>(); |
|||
|
|||
public actionFormSubmitted = false; |
|||
public actionForm = |
|||
this.formBuilder.group({ |
|||
host: ['', |
|||
[ |
|||
Validators.required |
|||
]], |
|||
indexName: ['$APP_NAME', |
|||
[ |
|||
Validators.required |
|||
]], |
|||
indexType: ['$SCHEMA_NAME', |
|||
[ |
|||
// Validators.required
|
|||
]], |
|||
username: '', |
|||
password: '' |
|||
}); |
|||
|
|||
constructor( |
|||
private readonly formBuilder: FormBuilder |
|||
) { |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.action = Object.assign({}, { |
|||
host: '', |
|||
indexName: '$APP_NAME', |
|||
indexType: '$SCHEMA_NAME', |
|||
username: '', |
|||
password: '' |
|||
}, this.action || {}); |
|||
|
|||
this.actionFormSubmitted = false; |
|||
this.actionForm.reset(); |
|||
this.actionForm.setValue(this.action); |
|||
} |
|||
|
|||
public save() { |
|||
this.actionFormSubmitted = true; |
|||
|
|||
if (this.actionForm.valid) { |
|||
const action = this.actionForm.value; |
|||
|
|||
this.actionChanged.emit(action); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue