mirror of https://github.com/Squidex/squidex.git
33 changed files with 2518 additions and 1701 deletions
@ -0,0 +1,36 @@ |
|||
// ==========================================================================
|
|||
// 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(MediumAction))] |
|||
public sealed class MediumAction : RuleAction |
|||
{ |
|||
public string AccessToken { get; set; } |
|||
|
|||
public string Author { get; set; } |
|||
|
|||
public string Publication { get; set; } |
|||
|
|||
public string Tags { get; set; } |
|||
|
|||
public string Title { get; set; } |
|||
|
|||
public string CanonicalUrl { get; set; } |
|||
|
|||
public string Content { get; set; } |
|||
|
|||
public bool IsHtml { get; set; } |
|||
|
|||
public override T Accept<T>(IRuleActionVisitor<T> visitor) |
|||
{ |
|||
return visitor.Visit(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,137 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
#pragma warning disable SA1649 // File name must match first type name
|
|||
|
|||
using System; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
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 MediumJob |
|||
{ |
|||
public string RequestUrl { get; set; } |
|||
public string RequestBody { get; set; } |
|||
|
|||
public string AccessToken { get; set; } |
|||
} |
|||
|
|||
public sealed class MediumActionHandler : RuleActionHandler<MediumAction, MediumJob> |
|||
{ |
|||
private const string Description = "Post to medium"; |
|||
|
|||
private readonly RuleEventFormatter formatter; |
|||
|
|||
public MediumActionHandler(RuleEventFormatter formatter) |
|||
{ |
|||
Guard.NotNull(formatter, nameof(formatter)); |
|||
|
|||
this.formatter = formatter; |
|||
} |
|||
|
|||
protected override async Task<(string Description, MediumJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, MediumAction action) |
|||
{ |
|||
var requestUrl = |
|||
!string.IsNullOrWhiteSpace(action.Author) ? |
|||
$"https://api.medium.com/v1/users/{action.Author}/posts" : |
|||
$"https://api.medium.com/v1/publication/{action.Publication}/posts"; |
|||
|
|||
var requestBody = |
|||
new JObject( |
|||
new JProperty("title", await formatter.FormatStringAsync(action.Title, @event)), |
|||
new JProperty("contentFormat", action.IsHtml ? "html" : "markdown"), |
|||
new JProperty("content", await formatter.FormatStringAsync(action.Content, @event)), |
|||
new JProperty("canonicalUrl", await formatter.FormatStringAsync(action.CanonicalUrl, @event)), |
|||
new JProperty("tags", await ParseTagsAsync(@event, action))); |
|||
|
|||
var ruleJob = new MediumJob |
|||
{ |
|||
AccessToken = action.AccessToken, |
|||
RequestUrl = requestUrl, |
|||
RequestBody = requestBody.ToString(Formatting.Indented) |
|||
}; |
|||
|
|||
return (Description, ruleJob); |
|||
} |
|||
|
|||
private async Task<JArray> ParseTagsAsync(Envelope<AppEvent> @event, MediumAction action) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(action.Tags)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
string[] tags; |
|||
try |
|||
{ |
|||
var jsonTags = await formatter.FormatStringAsync(action.Tags, @event); |
|||
|
|||
tags = JsonConvert.DeserializeObject<string[]>(jsonTags); |
|||
} |
|||
catch |
|||
{ |
|||
tags = action.Tags.Split(','); |
|||
} |
|||
|
|||
return new JArray(tags); |
|||
} |
|||
|
|||
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job) |
|||
{ |
|||
var requestBody = job.RequestBody; |
|||
var requestMessage = BuildRequest(job, requestBody); |
|||
|
|||
HttpResponseMessage response = null; |
|||
|
|||
try |
|||
{ |
|||
response = await HttpClientPool.GetHttpClient().SendAsync(requestMessage); |
|||
|
|||
var responseString = await response.Content.ReadAsStringAsync(); |
|||
var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, responseString, TimeSpan.Zero, false); |
|||
|
|||
Exception ex = null; |
|||
|
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
ex = new HttpRequestException($"Response code does not indicate success: {(int)response.StatusCode} ({response.StatusCode})."); |
|||
} |
|||
|
|||
return (requestDump, ex); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
var requestDump = DumpFormatter.BuildDump(requestMessage, response, requestBody, ex.ToString(), TimeSpan.Zero, false); |
|||
|
|||
return (requestDump, ex); |
|||
} |
|||
} |
|||
|
|||
private static HttpRequestMessage BuildRequest(MediumJob job, string requestBody) |
|||
{ |
|||
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) |
|||
{ |
|||
Content = new StringContent(requestBody, Encoding.UTF8, "application/json") |
|||
}; |
|||
|
|||
request.Headers.Add("Accept", "application/json"); |
|||
request.Headers.Add("Accept-Charset", "utf-8"); |
|||
request.Headers.Add("Authorization", $"Bearer {job.AccessToken}"); |
|||
|
|||
return request; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.HandleRules |
|||
{ |
|||
public interface IContentResolver |
|||
{ |
|||
Task<NamedContentData> GetContentDataAsync(Guid id); |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
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("Medium")] |
|||
public class MediumActionDto : RuleActionDto |
|||
{ |
|||
/// <summary>
|
|||
/// The self issued access token.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string AccessToken { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The author name.
|
|||
/// </summary>
|
|||
public string Author { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The author name.
|
|||
/// </summary>
|
|||
public string Publication { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The optional comma separated list of tags.
|
|||
/// </summary>
|
|||
public string Tags { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The title, used for the url.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Title { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The content, either html or markdown.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Content { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The original home of this content, if it was originally published elsewhere.
|
|||
/// </summary>
|
|||
public string CanonicalUrl { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the content is markdown or html.
|
|||
/// </summary>
|
|||
public bool IsHtml { get; set; } |
|||
|
|||
public override RuleAction ToAction() |
|||
{ |
|||
return SimpleMapper.Map(this, new MediumAction()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
<h3 class="wizard-title">Post to Medium</h3> |
|||
|
|||
<div [formGroup]="actionForm" class="form-horizontal"> |
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="accessToken">Access Token</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="accessToken" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="accessToken" formControlName="accessToken" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The self issued access token. Can be created under <a target="_blank" href="https://medium.com/me/settings">https://medium.com/me/settings</a>. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="author">Author</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="author" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="author" formControlName="author" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The name of the author. You can also define the publication. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="publication">Publication</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="publication" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="publication" formControlName="publication" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The name of the publication. You can also define the author. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="title">Title</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="title" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="title" formControlName="title" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The title of the post. Note that this title is used for SEO and when rendering the post as a listing. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="content">Content</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="content" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<textarea class="form-control" id="content" formControlName="content"></textarea> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The body of the post, in a valid, semantic, HTML fragment, or Markdown. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<div class="col col-9 offset-3"> |
|||
<div class="form-check"> |
|||
<input class="form-check-input" type="checkbox" id="isHtml" formControlName="isHtml" /> |
|||
<label class="form-check-label" for="isHtml"> |
|||
Is Html content |
|||
</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="canonicalUrl">Canonical URL</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="canonicalUrl" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="canonicalUrl" formControlName="canonicalUrl" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
The original home of this content, if it was originally published elsewhere. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col col-3 col-form-label" for="tags">Tags</label> |
|||
|
|||
<div class="col col-9"> |
|||
<sqx-control-errors for="tags" [submitted]="actionFormSubmitted"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control" id="tags" formControlName="tags" /> |
|||
|
|||
<small class="form-text text-muted"> |
|||
Comma-separated list of tags. |
|||
</small> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,6 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
textarea { |
|||
height: 150px; |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Component, Input, OnInit } from '@angular/core'; |
|||
import { FormControl, FormGroup, Validators } from '@angular/forms'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-medium-action', |
|||
styleUrls: ['./medium-action.component.scss'], |
|||
templateUrl: './medium-action.component.html' |
|||
}) |
|||
export class MediumActionComponent implements OnInit { |
|||
@Input() |
|||
public action: any; |
|||
|
|||
@Input() |
|||
public actionForm: FormGroup; |
|||
|
|||
@Input() |
|||
public actionFormSubmitted = false; |
|||
|
|||
public ngOnInit() { |
|||
this.actionForm.setControl('accessToken', |
|||
new FormControl(this.action.accessToken || '', [ |
|||
Validators.required |
|||
])); |
|||
|
|||
this.actionForm.setControl('title', |
|||
new FormControl(this.action.title || '', [ |
|||
Validators.required |
|||
])); |
|||
|
|||
this.actionForm.setControl('content', |
|||
new FormControl(this.action.content || '', [ |
|||
Validators.required |
|||
])); |
|||
|
|||
this.actionForm.setControl('author', |
|||
new FormControl(this.action.author || '')); |
|||
|
|||
this.actionForm.setControl('publication', |
|||
new FormControl(this.action.publication || '')); |
|||
|
|||
this.actionForm.setControl('canonicalUrl', |
|||
new FormControl(this.action.canonicalUrl || '')); |
|||
|
|||
this.actionForm.setControl('tags', |
|||
new FormControl(this.action.tags || '')); |
|||
|
|||
this.actionForm.setControl('isHtml', |
|||
new FormControl(this.action.isHtml || false)); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Loading…
Reference in new issue