diff --git a/src/Controllers/Storage/ProcessController.cs b/src/Controllers/Storage/ProcessController.cs index 52d1b662..a778d500 100644 --- a/src/Controllers/Storage/ProcessController.cs +++ b/src/Controllers/Storage/ProcessController.cs @@ -21,6 +21,7 @@ public class ProcessController : ControllerBase { private readonly IInstanceRepository _instanceRepository; private readonly IInstanceEventRepository _instanceEventRepository; + private readonly IInstanceAndEventsRepository _instanceAndEventsRepository; private readonly string _storageBaseAndHost; private readonly IAuthorization _authorizationService; private readonly IInstanceEventService _instanceEventService; @@ -30,18 +31,21 @@ public class ProcessController : ControllerBase /// /// the instance repository handler /// the instance event repository service + /// the instance and events repository /// the general settings /// the authorization service /// the instance event service public ProcessController( IInstanceRepository instanceRepository, IInstanceEventRepository instanceEventRepository, + IInstanceAndEventsRepository instanceAndEventsRepository, IOptions generalsettings, IAuthorization authorizationService, IInstanceEventService instanceEventService) { _instanceRepository = instanceRepository; _instanceEventRepository = instanceEventRepository; + _instanceAndEventsRepository = instanceAndEventsRepository; _storageBaseAndHost = $"{generalsettings.Value.Hostname}/storage/api/v1/"; _authorizationService = authorizationService; _instanceEventService = instanceEventService; @@ -63,72 +67,94 @@ public ProcessController( public async Task> PutProcess( int instanceOwnerPartyId, Guid instanceGuid, - [FromBody] ProcessState processState - ) + [FromBody] ProcessState processState) { - Instance existingInstance = await _instanceRepository.GetOne( - instanceOwnerPartyId, - instanceGuid - ); + Instance existingInstance = await _instanceRepository.GetOne(instanceOwnerPartyId, instanceGuid); if (existingInstance is null) { return NotFound(); } - string taskId = null; - string altinnTaskType = existingInstance.Process?.CurrentTask?.AltinnTaskType; + var (action, taskId) = ActionMapping(processState, existingInstance); - if (processState?.CurrentTask?.FlowType == "AbandonCurrentMoveToNext") - { - altinnTaskType = "reject"; - } - else if ( - processState?.CurrentTask?.FlowType is not null - && processState.CurrentTask.FlowType != "CompleteCurrentMoveToNext" - ) + bool authorized = await _authorizationService.AuthorizeInstanceAction(existingInstance, action, taskId); + + if (!authorized) { - altinnTaskType = processState.CurrentTask.AltinnTaskType; - taskId = processState.CurrentTask.ElementId; + return Forbid(); } - string action = altinnTaskType switch + UpdateInstance(existingInstance, processState, out _); + + Instance updatedInstance = await _instanceRepository.Update(existingInstance); + + if (processState?.CurrentTask?.AltinnTaskType == "signing") { - "data" or "feedback" => "write", - "payment" => "pay", - "confirmation" => "confirm", - "signing" => "sign", - _ => altinnTaskType, - }; + await _instanceEventService.DispatchEvent(InstanceEventType.SentToSign, updatedInstance); + } - bool authorized = await _authorizationService.AuthorizeInstanceAction(existingInstance, action, taskId); + updatedInstance.SetPlatformSelfLinks(_storageBaseAndHost); + return Ok(updatedInstance); + } + + /// + /// Updates the process state of an instance. + /// + /// The party id of the instance owner. + /// The id of the instance that should have its process updated. + /// The new process state of the instance (including instance events). + /// + [Authorize] + [HttpPut("instanceandevents")] + [Consumes("application/json")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [Produces("application/json")] + public async Task> PutInstanceAndEvents( + int instanceOwnerPartyId, + Guid instanceGuid, + [FromBody] ProcessStateUpdate processStateUpdate) + { + Instance existingInstance = await _instanceRepository.GetOne(instanceOwnerPartyId, instanceGuid); - if (!authorized) + if (existingInstance is null) { - return Forbid(); + return NotFound(); } - // Archiving instance if process was ended - if (existingInstance.Process?.Ended is null && processState?.Ended is not null) + foreach (var instanceEvent in processStateUpdate.Events ?? []) { - existingInstance.Status ??= new InstanceStatus(); - existingInstance.Status.IsArchived = true; - existingInstance.Status.Archived = processState.Ended; + if (string.IsNullOrWhiteSpace(instanceEvent.InstanceId)) + { + return BadRequest("Missing parameter values: instance event must exist and instanceId must be set"); + } + + instanceEvent.Created = instanceEvent.Created?.ToUniversalTime() ?? DateTime.UtcNow; } - existingInstance.Process = processState; - existingInstance.LastChangedBy = User.GetUserOrOrgId(); - existingInstance.LastChanged = DateTime.UtcNow; + ProcessState processState = processStateUpdate.State; - Instance updatedInstance; + var (action, taskId) = ActionMapping(processState, existingInstance); - updatedInstance = await _instanceRepository.Update(existingInstance); + bool authorized = await _authorizationService.AuthorizeInstanceAction(existingInstance, action, taskId); + if (!authorized) + { + return Forbid(); + } + + processStateUpdate.Events ??= []; + UpdateInstance(existingInstance, processState, out var updateProperties); if (processState?.CurrentTask?.AltinnTaskType == "signing") { - await _instanceEventService.DispatchEvent(InstanceEventType.SentToSign, updatedInstance); + InstanceEvent instanceEvent = _instanceEventService.BuildInstanceEvent(InstanceEventType.SentToSign, existingInstance); + processStateUpdate.Events.Add(instanceEvent); } + Instance updatedInstance = await _instanceAndEventsRepository.Update(existingInstance, updateProperties, processStateUpdate.Events); + updatedInstance.SetPlatformSelfLinks(_storageBaseAndHost); return Ok(updatedInstance); } @@ -156,5 +182,56 @@ public async Task> GetProcessHistory( return Ok(processHistoryList); } + + private void UpdateInstance(Instance existingInstance, ProcessState processState, out List updateProperties) + { + // Archiving instance if process was ended + updateProperties = [ + nameof(existingInstance.Process), + nameof(existingInstance.LastChanged), + nameof(existingInstance.LastChangedBy) + ]; + if (existingInstance.Process?.Ended is null && processState?.Ended is not null) + { + existingInstance.Status ??= new InstanceStatus(); + existingInstance.Status.IsArchived = true; + existingInstance.Status.Archived = processState.Ended; + updateProperties.Add(nameof(existingInstance.Status)); + updateProperties.Add(nameof(existingInstance.Status.IsArchived)); + updateProperties.Add(nameof(existingInstance.Status.Archived)); + } + + existingInstance.Process = processState; + existingInstance.LastChangedBy = User.GetUserOrOrgId(); + existingInstance.LastChanged = DateTime.UtcNow; + } + + private (string Action, string TaskId) ActionMapping(ProcessState processState, Instance existingInstance) + { + string taskId = null; + string altinnTaskType = existingInstance.Process?.CurrentTask?.AltinnTaskType; + + if (processState?.CurrentTask?.FlowType == "AbandonCurrentMoveToNext") + { + altinnTaskType = "reject"; + } + else if (processState?.CurrentTask?.FlowType is not null + && processState.CurrentTask.FlowType != "CompleteCurrentMoveToNext") + { + altinnTaskType = processState.CurrentTask.AltinnTaskType; + taskId = processState.CurrentTask.ElementId; + } + + string action = altinnTaskType switch + { + "data" or "feedback" => "write", + "payment" => "pay", + "confirmation" => "confirm", + "signing" => "sign", + _ => altinnTaskType, + }; + + return (action, taskId); + } } } diff --git a/src/LocalTest.csproj b/src/LocalTest.csproj index 68052349..a3949524 100644 --- a/src/LocalTest.csproj +++ b/src/LocalTest.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Services/Storage/Implementation/InstanceAndEventsRepository.cs b/src/Services/Storage/Implementation/InstanceAndEventsRepository.cs new file mode 100644 index 00000000..766b3d74 --- /dev/null +++ b/src/Services/Storage/Implementation/InstanceAndEventsRepository.cs @@ -0,0 +1,41 @@ +using System.Data; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.Platform.Storage.Repository; + +/// +/// Represents an implementation of . +/// +public class InstanceAndEventsRepository : IInstanceAndEventsRepository +{ + private readonly ILogger _logger; + private readonly IInstanceRepository _instanceRepository; + private readonly IInstanceEventRepository _instanceEventRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The logger to use when writing to logs. + /// Instance repo + public InstanceAndEventsRepository( + ILogger logger, + IInstanceRepository instanceRepository, + IInstanceEventRepository instanceEventRepository) + { + _logger = logger; + _instanceRepository = instanceRepository; + _instanceEventRepository = instanceEventRepository; + } + + /// + public async Task Update(Instance instance, List updateProperties, List events) + { + instance = await _instanceRepository.Update(instance); + foreach (var instanceEvent in events) + { + await _instanceEventRepository.InsertInstanceEvent(instanceEvent); + } + + return instance; + } +} diff --git a/src/Services/Storage/Implementation/InstanceEventRepository.cs b/src/Services/Storage/Implementation/InstanceEventRepository.cs index 6728bec5..5143188d 100644 --- a/src/Services/Storage/Implementation/InstanceEventRepository.cs +++ b/src/Services/Storage/Implementation/InstanceEventRepository.cs @@ -83,6 +83,14 @@ public Task> ListInstanceEvents(string instanceId, string[] private string GetInstanceEventPath(string instanceId, Guid instanceEventID) { + if (string.IsNullOrEmpty(instanceId)) + { + throw new ArgumentException("Instance ID cannot be null or empty"); + } + if (instanceEventID == Guid.Empty) + { + throw new ArgumentException("Instance event ID cannot be null or empty"); + } return GetInstanceEventFolder() + instanceId.Replace("/", "_") + "_" + instanceEventID.ToString() + ".json"; } diff --git a/src/Services/Storage/Implementation/InstanceEventService.cs b/src/Services/Storage/Implementation/InstanceEventService.cs index 438dab17..0f1abfb0 100644 --- a/src/Services/Storage/Implementation/InstanceEventService.cs +++ b/src/Services/Storage/Implementation/InstanceEventService.cs @@ -28,7 +28,7 @@ public InstanceEventService(IInstanceEventRepository repository, IHttpContextAcc } /// - public async Task DispatchEvent(InstanceEventType eventType, Instance instance) + public InstanceEvent BuildInstanceEvent(InstanceEventType eventType, Instance instance) { var user = _contextAccessor.HttpContext.User; @@ -48,6 +48,14 @@ public async Task DispatchEvent(InstanceEventType eventType, Instance instance) Created = DateTime.UtcNow, }; + return instanceEvent; + } + + /// + public async Task DispatchEvent(InstanceEventType eventType, Instance instance) + { + var instanceEvent = BuildInstanceEvent(eventType, instance); + await _repository.InsertInstanceEvent(instanceEvent); } diff --git a/src/Services/Storage/Interface/IInstanceAndEventsRepository.cs b/src/Services/Storage/Interface/IInstanceAndEventsRepository.cs new file mode 100644 index 00000000..a1c115fa --- /dev/null +++ b/src/Services/Storage/Interface/IInstanceAndEventsRepository.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.Platform.Storage.Repository; + +/// +/// Represents an implementation of . +/// +public interface IInstanceAndEventsRepository +{ + /// + /// update existing instance including instance events + /// + /// the instance to update + /// a list of which properties should be updated + /// the events to add + /// The updated instance + Task Update(Instance instance, List updateProperties, List events); +} diff --git a/src/Services/Storage/Interface/IInstanceEventService.cs b/src/Services/Storage/Interface/IInstanceEventService.cs index e1878e88..d9534a74 100644 --- a/src/Services/Storage/Interface/IInstanceEventService.cs +++ b/src/Services/Storage/Interface/IInstanceEventService.cs @@ -10,6 +10,14 @@ namespace Altinn.Platform.Storage.Services /// public interface IInstanceEventService { + /// + /// Construct an instance event given a type + /// + /// Event type + /// Instance + /// + public InstanceEvent BuildInstanceEvent(InstanceEventType eventType, Instance instance); + /// /// Dispatch an instance event to the repository /// diff --git a/src/Startup.cs b/src/Startup.cs index 03ab99d8..8256fd43 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -92,6 +92,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();