diff --git a/uSync.BackOffice/Models/SyncFinalActionRequest.cs b/uSync.BackOffice/Models/SyncFinalActionRequest.cs new file mode 100644 index 00000000..799ed7a5 --- /dev/null +++ b/uSync.BackOffice/Models/SyncFinalActionRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +using uSync.BackOffice.SyncHandlers.Models; + +namespace uSync.BackOffice.Models; + +public class SyncFinalActionRequest +{ + public required Guid RequestId { get; set; } + public HandlerActions HandlerAction { get; set; } = HandlerActions.None; + public required SyncActionOptions ActionOptions { get; set; } + public IEnumerable Actions { get; set; } = []; + public string? Username { get; set; } + public uSyncCallbacks? Callbacks { get; set; } +} + diff --git a/uSync.BackOffice/Services/ISyncActionService.cs b/uSync.BackOffice/Services/ISyncActionService.cs index df28d193..37bdcb4f 100644 --- a/uSync.BackOffice/Services/ISyncActionService.cs +++ b/uSync.BackOffice/Services/ISyncActionService.cs @@ -53,11 +53,18 @@ SyncActionResult ImportHandler(SyncActionOptions options, uSyncCallbacks? callba [Obsolete("use ImportPostAsync will be removed in v16")] SyncActionResult ImportPost(SyncActionOptions options, uSyncCallbacks? callbacks) => ImportPostAsync(options, callbacks).Result; - + + /// + /// run an export based on the options provided + /// + [Obsolete("use ImportPostAsync with finalActionRequest , will be removed in v16")] + Task ImportPostAsync(SyncActionOptions options, uSyncCallbacks? callbacks) + => ImportPostAsync(new SyncFinalActionRequest { RequestId = Guid.NewGuid(), ActionOptions = options, Callbacks = callbacks }); + /// /// run an export based on the options provided /// - Task ImportPostAsync(SyncActionOptions options, uSyncCallbacks? callbacks); + Task ImportPostAsync(SyncFinalActionRequest request); /// /// run a report for a given handler based on the options provided. @@ -85,7 +92,21 @@ SyncActionResult ReportHandler(SyncActionOptions options, uSyncCallbacks? callba /// /// finish the bulk process /// - Task FinishProcessAsync(HandlerActions action, IEnumerable actions, string username); + [Obsolete("use FinishProcessAsync with FinalActionRequest will be removed in v16")] + Task FinishProcessAsync(HandlerActions action, IEnumerable actions, string username) + => FinishProcessAsync(new SyncFinalActionRequest + { + HandlerAction = action, + RequestId = Guid.NewGuid(), + ActionOptions = new(), + Actions = actions, + Username = username + }); + + /// + /// finish the bulk process + /// + Task FinishProcessAsync(SyncFinalActionRequest request); /// /// returns the export folder zipped up as a stream diff --git a/uSync.BackOffice/Services/SyncActionService.cs b/uSync.BackOffice/Services/SyncActionService.cs index d43825ae..78a76b74 100644 --- a/uSync.BackOffice/Services/SyncActionService.cs +++ b/uSync.BackOffice/Services/SyncActionService.cs @@ -115,15 +115,14 @@ public async Task ImportHandlerAsync(SyncActionOptions options return new SyncActionResult(actions); } - public async Task ImportPostAsync(SyncActionOptions options, uSyncCallbacks? callbacks) + public async Task ImportPostAsync(SyncFinalActionRequest request) { var actions = await _uSyncService.PerformPostImportAsync( - options.GetFoldersOrDefault(_uSyncConfig.GetFolders()), - options.GetSetOrDefault(_uSyncConfig.Settings.DefaultSet), - options.Actions); - - callbacks?.Update?.Invoke("Import Complete", 1, 1); + request.ActionOptions.GetFoldersOrDefault(_uSyncConfig.GetFolders()), + request.ActionOptions.GetSetOrDefault(_uSyncConfig.Settings.DefaultSet), + request.Actions); + request.Callbacks?.Update?.Invoke("Post Import Complete", 1, 1); return new SyncActionResult(actions.Where(x => x.Change > Core.ChangeType.NoChange).ToList()); } @@ -185,12 +184,16 @@ public async Task StartProcessAsync(HandlerActions action) => await _uSyncService.StartBulkProcessAsync(action); /// - public async Task FinishProcessAsync(HandlerActions action, IEnumerable actions, string username) + public async Task FinishProcessAsync(SyncFinalActionRequest request) { - await _uSyncService.FinishBulkProcessAsync(action, actions); + await _uSyncService.FinishBulkProcessAsync(request.HandlerAction, request.Actions); _logger.LogInformation("{user} finished {action} process ({changes} changes)", - username, action, actions.Count()); + request.Username, request.HandlerAction, request.Actions.Count()); + + request.Callbacks?.Update?.Invoke("Process completed", 1, 1); + + return new SyncActionResult(request.Actions.ToList()); } public Stream GetExportFolderAsStream() diff --git a/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncPerformActionController.cs b/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncPerformActionController.cs index c8910a61..2269eaa6 100644 --- a/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncPerformActionController.cs +++ b/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncPerformActionController.cs @@ -7,6 +7,7 @@ using System.Net.Mime; using System.Text; +using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using uSync.Backoffice.Management.Api.Models; @@ -22,16 +23,22 @@ public class uSyncPerformActionController : uSyncControllerBase private readonly ISyncManagementService _managementService; private readonly ITemporaryFileService _temporaryFileService; - public uSyncPerformActionController(ISyncManagementService managementService, ITemporaryFileService temporaryFileService) + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public uSyncPerformActionController( + ISyncManagementService managementService, + ITemporaryFileService temporaryFileService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { _managementService = managementService; _temporaryFileService = temporaryFileService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } [HttpPost("Perform")] [ProducesResponseType(typeof(PerformActionResponse), 200)] public async Task PerformAction(PerformActionRequest model) - => await _managementService.PerformActionAsync(model); + => await _managementService.PerformActionAsync(model, _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser); [HttpPost("Download")] diff --git a/uSync.Backoffice.Management.Api/Models/PerformActionRequest.cs b/uSync.Backoffice.Management.Api/Models/PerformActionRequest.cs index 511930d9..c3cec66a 100644 --- a/uSync.Backoffice.Management.Api/Models/PerformActionRequest.cs +++ b/uSync.Backoffice.Management.Api/Models/PerformActionRequest.cs @@ -9,4 +9,6 @@ public class PerformActionRequest public string Action { get; set; } = HandlerActions.Report.ToString(); public int StepNumber { get; set; } public uSyncOptions? Options { get; set; } + + public string? Username { get; set; } } diff --git a/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs b/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs index 9ee60dfc..913de691 100644 --- a/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs +++ b/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs @@ -1,4 +1,6 @@ -using uSync.Backoffice.Management.Api.Models; +using Umbraco.Cms.Core.Models.Membership; + +using uSync.Backoffice.Management.Api.Models; using uSync.BackOffice; using uSync.BackOffice.Models; using uSync.BackOffice.SyncHandlers.Models; @@ -9,6 +11,11 @@ public interface ISyncManagementService Stream CompressExportFolder(); List GetActions(); Func> GetHandlerMethodAsync(HandlerActions action); - Task PerformActionAsync(PerformActionRequest actionRequest); + + [Obsolete("Pass in IUser for better logging, will be removed in v16")] + Task PerformActionAsync(PerformActionRequest actionRequest) + => PerformActionAsync(actionRequest, null); + + Task PerformActionAsync(PerformActionRequest actionRequest, IUser? user); UploadImportResult UnpackStream(Stream stream); } \ No newline at end of file diff --git a/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs b/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs index cbee8f18..36dd7503 100644 --- a/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs +++ b/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.SignalR; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Extensions; using uSync.Backoffice.Management.Api.Extensions; @@ -155,39 +156,10 @@ public List GetActions() } return actionGroups; - - - - // List actions = [ - // new SyncActionGroup - // { - // GroupName = "Settings", - // Icon = "icon-settings-alt", - // Key = "settings", - // Buttons = defaultButtons - // }, - // new SyncActionGroup - // { - // GroupName = "Content", - // Icon = "icon-documents", - // Key = "content", - // Buttons = defaultButtons - // }, - // new SyncActionGroup - // { - // GroupName = "Everything", - // Icon = "icon-paper-plane-alt", - // Key = "all", - // Buttons = everythingButtons - // } - //]; - - // return actions; - } - public async Task PerformActionAsync(PerformActionRequest actionRequest) + public async Task PerformActionAsync(PerformActionRequest actionRequest, IUser? user) { if (Enum.TryParse(actionRequest.Action, out HandlerActions action) is false) throw new ArgumentException($"Invalid action {actionRequest.Action}"); @@ -197,6 +169,8 @@ public async Task PerformActionAsync(PerformActionRequest if (action == HandlerActions.Export && string.IsNullOrWhiteSpace(actionRequest.RequestId)) { + await _syncActionService.StartProcessAsync(action); + // first step in an export. if (actionRequest.Options?.Clean is true) { @@ -214,38 +188,32 @@ public async Task PerformActionAsync(PerformActionRequest } uSyncCallbacks callbacks = hubClient?.Callbacks() ?? new uSyncCallbacks(null, null); - if (actionRequest.StepNumber >= handlers.Count) - { - var finalActions = _syncManagementCache.GetCachedActions(requestId); - // when complete we clean out our action cache. - _syncManagementCache.Clear(requestId); - callbacks?.Update?.Invoke("Finished", 1, 1); + var handlerOptions = new SyncActionOptions() + { + Folders = _configService.GetFolders(), + Set = actionRequest.Options?.Set ?? _configService.Settings.DefaultSet, + Force = actionRequest.Options?.Force ?? false, + Actions = new List(), + }; + if (actionRequest.StepNumber >= handlers.Count) + { + var actions = await PerformFinalSteps(requestId, action, handlerOptions, callbacks, user?.Username); // finished. return new PerformActionResponse - { + { RequestId = requestId.ToString(), - Actions = finalActions.Select(x => x.ToActionView()), + Actions = actions.Select(x => x.ToActionView()), Complete = true, - Status = GetSummaries(handlers, actionRequest.StepNumber, finalActions) + Status = GetSummaries(action, handlers, actionRequest.StepNumber + 1, actions) }; } - var currentHandler = handlers[actionRequest.StepNumber]; + handlerOptions.Handler = handlers[actionRequest.StepNumber].Alias; var method = GetHandlerMethodAsync(action); - - var handlerOptions = new SyncActionOptions() - { - Folders = _configService.GetFolders(), - Set = actionRequest.Options?.Set ?? _configService.Settings.DefaultSet, - Force = actionRequest.Options?.Force ?? false, - Actions = new List(), - Handler = currentHandler.Alias - }; - var results = await method(handlerOptions, callbacks); _syncManagementCache.CacheItems(requestId, results.Actions, false); @@ -253,11 +221,51 @@ public async Task PerformActionAsync(PerformActionRequest { RequestId = requestId.ToString(), Actions = results.Actions.Select(x => x.ToActionView()), - Status = GetSummaries(handlers, actionRequest.StepNumber, results.Actions.ToList()), + Status = GetSummaries(action, handlers, actionRequest.StepNumber, results.Actions.ToList()), Complete = false }; } + private async Task> PerformFinalSteps(Guid requestId, HandlerActions action, SyncActionOptions options, uSyncCallbacks callbacks, string? username) + { + var finalActions = _syncManagementCache.GetCachedActions(requestId); + var finalSteps = GetFinalStep(action); + + var request = new SyncFinalActionRequest + { + RequestId = requestId, + HandlerAction = action, + ActionOptions = options, + Actions = finalActions, + Callbacks = callbacks, + Username = username ?? "" + }; + + foreach (var step in finalSteps) + { + var result = await step(request); + } + + // when complete we clean out our action cache. + _syncManagementCache.Clear(requestId); + + callbacks?.Update?.Invoke("Finished", 1, 1); + return finalActions; + } + + private IEnumerable>> GetFinalStep(HandlerActions action) + { + var steps = new List>>(); + + if (action == HandlerActions.Import) + { + steps.Add(_syncActionService.ImportPostAsync); + } + + steps.Add(_syncActionService.FinishProcessAsync); + return steps; + } + private Guid GetRequestId(PerformActionRequest actionRequest) { Guid requestId; @@ -268,7 +276,7 @@ private Guid GetRequestId(PerformActionRequest actionRequest) return requestId; } - private IEnumerable GetSummaries(List handlers, int step, List actions) + private IEnumerable GetSummaries(HandlerActions action, List handlers, int step, List actions) { var nextStep = step + 1; for (int n = 0; n < handlers.Count; n++) @@ -285,6 +293,19 @@ private IEnumerable GetSummaries(List handl InError = handlerActions.Any(x => x.Change >= Core.ChangeType.Fail) }; } + + if (action == HandlerActions.Import) + { + yield return new SyncHandlerSummary + { + Name = $"Post Import", + Icon = "icon-directions", + Status = nextStep == handlers.Count ? HandlerStatus.Processing : + nextStep > handlers.Count ? HandlerStatus.Complete : HandlerStatus.Pending, + Changes = 0, + InError = false + }; + } }