From 58fa01cd37275cb2c24f37adddfada03741ea641 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Tue, 19 Mar 2024 16:49:09 +0200 Subject: [PATCH] Added bridge support --- Apps.Lionbridge/ApplicationConstants.cs | 6 ++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 2 + .../Webhooks/Bridge/BridgeService.cs | 38 +++++++++ .../Bridge/Models/BridgeGetResponse.cs | 8 ++ .../Webhooks/Handlers/BaseWebhookHandler.cs | 85 +++++++++++++++++++ .../Handlers/JobStatusUpdatedHandler.cs | 10 +++ .../Handlers/RequestStatusUpdatedHandler.cs | 10 +++ .../Webhooks/Inputs/WebhookInput.cs | 6 ++ .../Webhooks/Responses/ListenerDto.cs | 27 ++++++ .../Webhooks/Responses/ListenersResponse.cs | 10 +++ Apps.Lionbridge/Webhooks/WebhookList.cs | 20 ++--- 11 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 Apps.Lionbridge/ApplicationConstants.cs create mode 100644 Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs create mode 100644 Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs create mode 100644 Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs create mode 100644 Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs create mode 100644 Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs create mode 100644 Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs create mode 100644 Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs create mode 100644 Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs diff --git a/Apps.Lionbridge/ApplicationConstants.cs b/Apps.Lionbridge/ApplicationConstants.cs new file mode 100644 index 0000000..ac5b80d --- /dev/null +++ b/Apps.Lionbridge/ApplicationConstants.cs @@ -0,0 +1,6 @@ +namespace Apps.Lionbridge; + +public class ApplicationConstants +{ + public const string BlackbirdToken = "#{APP_LIONBRIDGE_BLACKBIRD_TOKEN}#"; // bridge validates this token +} \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 336e501..20115b3 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -16,4 +16,6 @@ public static class ApiEndpoints public const string SupportAssets = "/supportassets"; public const string TranslationMemories = "/tmupdates"; public const string Retrieve = "/retrieve"; + public const string StatusUpdates = "/statusupdates"; + public const string Listeners = StatusUpdates + "/listeners"; } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs new file mode 100644 index 0000000..7c456ba --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -0,0 +1,38 @@ +using Apps.Lionbridge.Webhooks.Bridge.Models; +using Blackbird.Applications.Sdk.Common.Authentication; +using RestSharp; + +namespace Apps.Lionbridge.Webhooks.Bridge; + +public class BridgeService +{ + private string BridgeServiceUrl { get; set; } + + public BridgeService(IEnumerable authenticationCredentialsProviders, string bridgeServiceUrl) + { + BridgeServiceUrl = bridgeServiceUrl; + } + + public void Subscribe(string _event, string listenerId, string url) + { + var client = new RestClient(BridgeServiceUrl); + var request = new RestRequest($"/{listenerId}/{_event}", Method.Post); + request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + request.AddBody(url); + + client.Execute(request); + } + + public void Unsubscribe(string _event, string listenerId, string url) + { + var client = new RestClient(BridgeServiceUrl); + var requestGet = new RestRequest($"/{listenerId}/{_event}", Method.Get); + requestGet.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + var webhooks = client.Get>(requestGet); + var webhook = webhooks.FirstOrDefault(w => w.Value == url); + + var requestDelete = new RestRequest($"/{listenerId}/{_event}/{webhook.Id}", Method.Delete); + requestDelete.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + client.Delete(requestDelete); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs b/Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs new file mode 100644 index 0000000..220dd95 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs @@ -0,0 +1,8 @@ +namespace Apps.Lionbridge.Webhooks.Bridge.Models; + +public class BridgeGetResponse +{ + public string Id { get; set; } + + public string Value { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs new file mode 100644 index 0000000..d77db86 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -0,0 +1,85 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Webhooks.Bridge; +using Apps.Lionbridge.Webhooks.Responses; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.Sdk.Common.Webhooks; +using RestSharp; + +namespace Apps.Lionbridge.Webhooks.Handlers; + +public class BaseWebhookHandler : BaseInvocable, IWebhookEventHandler +{ + private readonly string _bridgeServiceUrl; + + private string SubscriptionEvent { get; set; } + + protected readonly LionbridgeClient Client; + + public BaseWebhookHandler(InvocationContext invocationContext, string subEvent) : base(invocationContext) + { + SubscriptionEvent = subEvent; + _bridgeServiceUrl = $"{invocationContext.UriInfo.BridgeServiceUrl.ToString().TrimEnd('/')}/webhooks/lionbridge"; + Client = new LionbridgeClient(invocationContext.AuthenticationCredentialsProviders); + } + + + public async Task SubscribeAsync(IEnumerable authenticationCredentialsProviders, + Dictionary values) + { + var listener = await GetListenerIdAsync(); + if (listener == null) + { + listener = await CreateListenerAsync(); + } + + var bridge = new BridgeService(authenticationCredentialsProviders, _bridgeServiceUrl); + bridge.Subscribe(SubscriptionEvent, listener.ListenerId, values["payloadUrl"]); + } + + public async Task UnsubscribeAsync(IEnumerable authenticationCredentialsProviders, + Dictionary values) + { + var listener = await GetListenerIdAsync(); + if (listener != null) + { + var bridge = new BridgeService(authenticationCredentialsProviders, _bridgeServiceUrl); + bridge.Unsubscribe(SubscriptionEvent, listener.ListenerId, values["payloadUrl"]); + await DeleteListenerAsync(listener.ListenerId); + } + } + + private async Task GetListenerIdAsync() + { + var request = new LionbridgeRequest(ApiEndpoints.Listeners, Method.Get); + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Embedded.Listeners.FirstOrDefault(x => x.Type == SubscriptionEvent); + } + + private async Task CreateListenerAsync() + { + var request = new LionbridgeRequest(ApiEndpoints.Listeners, Method.Post) + .AddJsonBody(new + { + uri = _bridgeServiceUrl, + type = SubscriptionEvent, + statusCodes = new [] + { + "IN_TRANSLATION" // TODO: change in the future + }, + acknowledgeStatusUpdate = true + }); + + var response = await Client.ExecuteWithErrorHandling(request); + return response; + } + + private async Task DeleteListenerAsync(string listenerId) + { + var request = new LionbridgeRequest($"{ApiEndpoints.Listeners}/{listenerId}", Method.Delete); + await Client.ExecuteWithErrorHandling(request); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs new file mode 100644 index 0000000..6c22234 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs @@ -0,0 +1,10 @@ +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.Webhooks.Handlers; + +public class JobStatusUpdatedHandler : BaseWebhookHandler +{ + const string SubscriptionEvent = "JOB_STATUS_UPDATED"; + + public JobStatusUpdatedHandler(InvocationContext invocationContext) : base(invocationContext, SubscriptionEvent) { } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs new file mode 100644 index 0000000..fdc9b3f --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs @@ -0,0 +1,10 @@ +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.Webhooks.Handlers; + +public class RequestStatusUpdatedHandler : BaseWebhookHandler +{ + const string SubscriptionEvent = "REQUEST_STATUS_UPDATED"; + + public RequestStatusUpdatedHandler(InvocationContext invocationContext) : base(invocationContext, SubscriptionEvent) { } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs b/Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs new file mode 100644 index 0000000..507818e --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs @@ -0,0 +1,6 @@ +namespace Apps.Lionbridge.Webhooks.Inputs; + +public class WebhookInput +{ + public string ListenerId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs b/Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs new file mode 100644 index 0000000..8a6539c --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Webhooks.Responses; + +public class ListenerDto +{ + [JsonProperty("listenerId")] + public string ListenerId { get; set; } + + [JsonProperty("uri")] + public string Uri { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("statusCodes")] + public string[] StatusCodes { get; set; } + + [JsonProperty("authType")] + public string AuthType { get; set; } + + [JsonProperty("acknowledgeStatusUpdate")] + public bool AcknowledgeStatusUpdate { get; set; } + + [JsonProperty("disabled")] + public bool Disabled { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs b/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs new file mode 100644 index 0000000..c15367f --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs @@ -0,0 +1,10 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Webhooks.Responses; + +public class ListenersResponse : EmbeddedItemsWrapper +{ + +} + +public record ListenersWrapper(IEnumerable Listeners); \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/WebhookList.cs b/Apps.Lionbridge/Webhooks/WebhookList.cs index ed64c9a..89e27d3 100644 --- a/Apps.Lionbridge/Webhooks/WebhookList.cs +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -1,7 +1,7 @@ using Apps.Lionbridge.Api; using Apps.Lionbridge.Constants; using Apps.Lionbridge.Models.Dtos; -using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Webhooks.Handlers; using Apps.Lionbridge.Webhooks.Inputs; using Apps.Lionbridge.Webhooks.Payload; using Apps.Lionbridge.Webhooks.Responses; @@ -16,7 +16,7 @@ public class WebhookList(InvocationContext invocationContext) : LionbridgeInvoca { #region Job Webhooks - [Webhook("Job status updated", + [Webhook("Job status updated", typeof(JobStatusUpdatedHandler), Description = "Check for updates on jobs, as you are directly informed when the job is finished or cancelled")] public async Task> OnJobStatusUpdated(WebhookRequest webhookRequest, [WebhookParameter] JobStatusUpdatedInput input) @@ -35,15 +35,15 @@ public async Task> OnJobStatusUpdated( { return preflightResponse; } - + bool archived = input.Archived ?? false; - if(archived != data.Archived) + if (archived != data.Archived) { return preflightResponse; } - + bool deleted = input.Deleted ?? false; - if(deleted != data.Deleted) + if (deleted != data.Deleted) { return preflightResponse; } @@ -66,7 +66,7 @@ public async Task> OnJobStatusUpdated( #region Request Webhooks - [Webhook("On request status updated", + [Webhook("On request status updated", typeof(RequestStatusUpdatedHandler), Description = "Check for updates on requests, as you are directly informed when the request is finished or cancelled")] public async Task> OnRequestStatusUpdated( @@ -81,7 +81,7 @@ public async Task> OnRequestStatus HttpResponseMessage = null, ReceivedWebhookRequestType = WebhookRequestType.Preflight }; - + if (!string.IsNullOrEmpty(requests.JobId) && requests.JobId != data.JobId) { return preflightResponse; @@ -108,7 +108,7 @@ private async Task GetJobDto(string jobId) var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}"); return await Client.ExecuteWithErrorHandling(apiRequest); } - + private async Task> GetRequests(string jobId, IEnumerable requestIds) { var requests = new List(); @@ -118,7 +118,7 @@ private async Task> GetRequests(string jobId, IEnumerable