diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs index 714b692ff..73205909b 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs @@ -174,7 +174,7 @@ public interface IWorkflowDefinitionsApi /// /// The files to import. /// The cancellation token. - [Post("/workflow-definitions/import")] + [Post("/workflow-definitions/import-files")] [Multipart] Task ImportFilesAsync([AliasAs("files")] List files, CancellationToken cancellationToken = default); diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs index 3849c81c9..d519706af 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs @@ -39,84 +39,29 @@ internal class Import : ElsaEndpoint Routes("workflow-definitions/import", "workflow-definitions/{definitionId}/import"); Verbs(FastEndpoints.Http.POST, FastEndpoints.Http.PUT); ConfigurePermissions("write:workflow-definitions"); - AllowFileUploads(); } /// public override async Task HandleAsync(WorkflowDefinitionModel model, CancellationToken cancellationToken) { - if (Files.Any()) - { - var count = await ImportFilesAsync(Files, cancellationToken); - - if (!ValidationFailed) - await SendOkAsync(new { Count = count }, cancellationToken); - } - else - { - var definitionId = model.DefinitionId; - var isNew = string.IsNullOrWhiteSpace(definitionId); - var result = await ImportSingleWorkflowDefinitionAsync(model, cancellationToken); - var definition = result.WorkflowDefinition; - var updatedModel = await _workflowDefinitionMapper.MapAsync(definition, cancellationToken); + var definitionId = model.DefinitionId; + var isNew = string.IsNullOrWhiteSpace(definitionId); + var result = await ImportSingleWorkflowDefinitionAsync(model, cancellationToken); + var definition = result.WorkflowDefinition; + var updatedModel = await _workflowDefinitionMapper.MapAsync(definition, cancellationToken); - if (result.Succeeded) - { - if (isNew) - await SendCreatedAtAsync(new { DefinitionId = definitionId }, updatedModel, cancellation: cancellationToken); - else - await SendOkAsync(updatedModel, cancellationToken); - } + if (result.Succeeded) + { + if (isNew) + await SendCreatedAtAsync(new { DefinitionId = definitionId }, updatedModel, cancellation: cancellationToken); + else + await SendOkAsync(updatedModel, cancellationToken); } if (ValidationFailed) await SendErrorsAsync(400, cancellationToken); } - private async Task ImportFilesAsync(IFormFileCollection files, CancellationToken cancellationToken) - { - var count = 0; - - foreach (var file in files) - { - var fileStream = file.OpenReadStream(); - - // Check if the file is a JSON file or a ZIP file. - var isJsonFile = file.ContentType == "application/json"; - - // If the file is a JSON file, read it. - if (isJsonFile) - { - await ImportJsonStreamAsync(fileStream, cancellationToken); - count++; - } - else - { - // If the file is a ZIP file, extract the JSON files and read them. - var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read); - - foreach (var entry in zipArchive.Entries) - { - if (!entry.FullName.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) - continue; - - var jsonStream = entry.Open(); - await ImportJsonStreamAsync(jsonStream, cancellationToken); - count++; - } - } - } - - return count; - } - - private async Task ImportJsonStreamAsync(Stream jsonStream, CancellationToken cancellationToken) - { - var json = await new StreamReader(jsonStream).ReadToEndAsync(); - var model = _apiSerializer.Deserialize(json); - await ImportSingleWorkflowDefinitionAsync(model, cancellationToken); - } - private async Task ImportSingleWorkflowDefinitionAsync(WorkflowDefinitionModel model, CancellationToken cancellationToken) { // Import workflow diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/ImportFiles/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/ImportFiles/Endpoint.cs new file mode 100644 index 000000000..f63519716 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/ImportFiles/Endpoint.cs @@ -0,0 +1,122 @@ +using System.IO.Compression; +using Elsa.Abstractions; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Management.Mappers; +using Elsa.Workflows.Management.Models; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.ImportFiles; + +/// +/// Imports JSON and/or ZIP files containing a workflow definitions. +/// +[PublicAPI] +internal class ImportFiles : ElsaEndpoint +{ + private readonly IWorkflowDefinitionService _workflowDefinitionService; + private readonly IWorkflowDefinitionImporter _workflowDefinitionImporter; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; + private readonly IApiSerializer _apiSerializer; + + /// + public ImportFiles( + IWorkflowDefinitionService workflowDefinitionService, + IWorkflowDefinitionImporter workflowDefinitionImporter, + WorkflowDefinitionMapper workflowDefinitionMapper, + IApiSerializer apiSerializer) + { + _workflowDefinitionService = workflowDefinitionService; + _workflowDefinitionImporter = workflowDefinitionImporter; + _workflowDefinitionMapper = workflowDefinitionMapper; + _apiSerializer = apiSerializer; + } + + /// + public override void Configure() + { + Post("workflow-definitions/import-files"); + ConfigurePermissions("write:workflow-definitions"); + AllowFileUploads(); + } + + /// + public override async Task HandleAsync(WorkflowDefinitionModel model, CancellationToken cancellationToken) + { + if (Files.Any()) + { + var count = await ImportFilesAsync(Files, cancellationToken); + + if (!ValidationFailed) + await SendOkAsync(new { Count = count }, cancellationToken); + } + + if (ValidationFailed) + await SendErrorsAsync(400, cancellationToken); + } + + private async Task ImportFilesAsync(IFormFileCollection files, CancellationToken cancellationToken) + { + var count = 0; + + foreach (var file in files) + { + var fileStream = file.OpenReadStream(); + + // Check if the file is a JSON file or a ZIP file. + var isJsonFile = file.ContentType == "application/json"; + + // If the file is a JSON file, read it. + if (isJsonFile) + { + await ImportJsonStreamAsync(fileStream, cancellationToken); + count++; + } + else + { + // If the file is a ZIP file, extract the JSON files and read them. + var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read); + + foreach (var entry in zipArchive.Entries) + { + if (!entry.FullName.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) + continue; + + var jsonStream = entry.Open(); + await ImportJsonStreamAsync(jsonStream, cancellationToken); + count++; + } + } + } + + return count; + } + + private async Task ImportJsonStreamAsync(Stream jsonStream, CancellationToken cancellationToken) + { + var json = await new StreamReader(jsonStream).ReadToEndAsync(); + var model = _apiSerializer.Deserialize(json); + await ImportSingleWorkflowDefinitionAsync(model, cancellationToken); + } + + private async Task ImportSingleWorkflowDefinitionAsync(WorkflowDefinitionModel model, CancellationToken cancellationToken) + { + // Import workflow + var saveWorkflowRequest = new SaveWorkflowDefinitionRequest + { + Model = model, + Publish = false, + }; + + var result = await _workflowDefinitionImporter.ImportAsync(saveWorkflowRequest, cancellationToken); + + if (result.Succeeded) + return result; + + foreach (var validationError in result.ValidationErrors) + AddError(validationError.Message); + + return result; + } +} \ No newline at end of file