// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System.ComponentModel.DataAnnotations; using System.Text; using Squidex.Domain.Apps.Core.Rules.Deprecated; using Squidex.Flows; using Squidex.Flows.Steps.Utils; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Validation; namespace Squidex.Extensions.Actions.Medium; [FlowStep( Title = "Medium", IconImage = "", IconColor = "#00ab6c", Display = "Post to Medium", Description = "Create a new story or post at medium.", ReadMore = "https://medium.com/")] #pragma warning disable CS0618 // Type or member is obsolete public sealed record MediumFlowStep : FlowStep, IConvertibleToAction #pragma warning restore CS0618 // Type or member is obsolete { [LocalizedRequired] [Display(Name = "Access Token", Description = "The self issued access token.")] [Editor(FlowStepEditor.Text)] public string AccessToken { get; set; } [LocalizedRequired] [Display(Name = "Title", Description = "The title, used for the url.")] [Editor(FlowStepEditor.Text)] [Expression] public string Title { get; set; } [LocalizedRequired] [Display(Name = "Content", Description = "The content, either html or markdown.")] [Editor(FlowStepEditor.TextArea)] [Expression] public string Content { get; set; } [Display(Name = "Canonical Url", Description = "The original home of this content, if it was originally published elsewhere.")] [Editor(FlowStepEditor.Text)] [Expression] public string? CanonicalUrl { get; set; } [Display(Name = "Tags", Description = "The optional comma separated list of tags.")] [Editor(FlowStepEditor.Text)] [Expression] public string? Tags { get; set; } [Display(Name = "Publication Id", Description = "Optional publication id.")] [Editor(FlowStepEditor.Text)] public string? PublicationId { get; set; } [Display(Name = "Is Html", Description = "Indicates whether the content is markdown or html.")] [Editor(FlowStepEditor.Text)] public bool IsHtml { get; set; } public override async ValueTask ExecuteAsync(FlowExecutionContext executionContext, CancellationToken ct) { var httpClient = executionContext.Resolve() .CreateClient("MediumAction"); string path; if (!string.IsNullOrWhiteSpace(PublicationId)) { path = $"/v1/publications/{PublicationId}/posts"; } else if (executionContext.IsSimulation) { path = "/v1/users/simulated-id/posts"; } else { var meRequest = BuildGetRequest("/v1/me"); var (responseString, _) = await httpClient.SendAsync(executionContext, meRequest, null, ct); var responseJson = executionContext.DeserializeJson(responseString); var id = responseJson.Data?.Id; path = $"/v1/users/{id}/posts"; } var requestBody = new { title = Title, contentFormat = IsHtml ? "html" : "markdown", content = Content, canonicalUrl = CanonicalUrl, tags = ParseTags(executionContext), }; var requestJson = executionContext.SerializeJson(requestBody); var requestMessage = BuildPostRequest(path, requestJson); if (executionContext.IsSimulation) { executionContext.LogSkipSimulation( HttpDumpFormatter.BuildDump(requestMessage, null, null)); return Next(); } var (_, dump) = await httpClient.SendAsync(executionContext, requestMessage, requestJson, ct); executionContext.Log("Post created", dump); return Next(); } private HttpRequestMessage BuildPostRequest(string path, string body) { var request = new HttpRequestMessage(HttpMethod.Post, path) { Content = new StringContent(body, Encoding.UTF8, "application/json"), }; request.Headers.Add("Authorization", $"Bearer {AccessToken}"); return request; } private HttpRequestMessage BuildGetRequest(string path) { var request = new HttpRequestMessage(HttpMethod.Get, path); request.Headers.Add("Authorization", $"Bearer {AccessToken}"); return request; } private string[]? ParseTags(FlowExecutionContext executionContext) { if (string.IsNullOrWhiteSpace(Tags)) { return null; } try { return executionContext.DeserializeJson(Tags); } catch { return Tags.Split(','); } } #pragma warning disable CS0618 // Type or member is obsolete public RuleAction ToAction() { return SimpleMapper.Map(this, new MediumAction()); } #pragma warning restore CS0618 // Type or member is obsolete private sealed class UserResponse { public UserResponseData Data { get; set; } } private sealed class UserResponseData { public string Id { get; set; } } }