Skip to content

Commit

Permalink
Add 'instanceandevents' endpoint to Storage
Browse files Browse the repository at this point in the history
  • Loading branch information
martinothamar committed Nov 28, 2024
1 parent 60215e4 commit 45a63b8
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 41 deletions.
155 changes: 116 additions & 39 deletions src/Controllers/Storage/ProcessController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,18 +31,21 @@ public class ProcessController : ControllerBase
/// </summary>
/// <param name="instanceRepository">the instance repository handler</param>
/// <param name="instanceEventRepository">the instance event repository service</param>
/// <param name="instanceAndEventsRepository">the instance and events repository</param>
/// <param name="generalsettings">the general settings</param>
/// <param name="authorizationService">the authorization service</param>
/// <param name="instanceEventService">the instance event service</param>
public ProcessController(
IInstanceRepository instanceRepository,
IInstanceEventRepository instanceEventRepository,
IInstanceAndEventsRepository instanceAndEventsRepository,
IOptions<GeneralSettings> generalsettings,
IAuthorization authorizationService,
IInstanceEventService instanceEventService)
{
_instanceRepository = instanceRepository;
_instanceEventRepository = instanceEventRepository;
_instanceAndEventsRepository = instanceAndEventsRepository;
_storageBaseAndHost = $"{generalsettings.Value.Hostname}/storage/api/v1/";
_authorizationService = authorizationService;
_instanceEventService = instanceEventService;
Expand All @@ -63,72 +67,94 @@ public ProcessController(
public async Task<ActionResult<Instance>> 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);
}

/// <summary>
/// Updates the process state of an instance.
/// </summary>
/// <param name="instanceOwnerPartyId">The party id of the instance owner.</param>
/// <param name="instanceGuid">The id of the instance that should have its process updated.</param>
/// <param name="processStateUpdate">The new process state of the instance (including instance events).</param>
/// <returns></returns>
[Authorize]
[HttpPut("instanceandevents")]
[Consumes("application/json")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Produces("application/json")]
public async Task<ActionResult<Instance>> 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);
}
Expand Down Expand Up @@ -156,5 +182,56 @@ public async Task<ActionResult<ProcessHistoryList>> GetProcessHistory(

return Ok(processHistoryList);
}

private void UpdateInstance(Instance existingInstance, ProcessState processState, out List<string> 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);
}
}
}
2 changes: 1 addition & 1 deletion src/LocalTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageReference Include="Altinn.Authorization.ABAC" Version="0.0.8" />
<PackageReference Include="Altinn.Common.PEP" Version="3.0.0" />
<PackageReference Include="Altinn.Platform.Models" Version="1.6.1" />
<PackageReference Include="Altinn.Platform.Storage.Interface" Version="3.30.0" />
<PackageReference Include="Altinn.Platform.Storage.Interface" Version="4.0.4" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="FluentValidation" Version="11.10.0" />
<PackageReference Include="JWTCookieAuthentication" Version="2.4.2" />
Expand Down
41 changes: 41 additions & 0 deletions src/Services/Storage/Implementation/InstanceAndEventsRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Data;
using Altinn.Platform.Storage.Interface.Models;

namespace Altinn.Platform.Storage.Repository;

/// <summary>
/// Represents an implementation of <see cref="IInstanceAndEventsRepository"/>.
/// </summary>
public class InstanceAndEventsRepository : IInstanceAndEventsRepository
{
private readonly ILogger<InstanceAndEventsRepository> _logger;
private readonly IInstanceRepository _instanceRepository;
private readonly IInstanceEventRepository _instanceEventRepository;

/// <summary>
/// Initializes a new instance of the <see cref="InstanceAndEventsRepository"/> class.
/// </summary>
/// <param name="logger">The logger to use when writing to logs.</param>
/// <param name="instanceRepository">Instance repo</param>
public InstanceAndEventsRepository(
ILogger<InstanceAndEventsRepository> logger,
IInstanceRepository instanceRepository,
IInstanceEventRepository instanceEventRepository)
{
_logger = logger;
_instanceRepository = instanceRepository;
_instanceEventRepository = instanceEventRepository;
}

/// <inheritdoc/>
public async Task<Instance> Update(Instance instance, List<string> updateProperties, List<InstanceEvent> events)
{
instance = await _instanceRepository.Update(instance);
foreach (var instanceEvent in events)
{
await _instanceEventRepository.InsertInstanceEvent(instanceEvent);
}

return instance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ public Task<List<InstanceEvent>> 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";
}

Expand Down
10 changes: 9 additions & 1 deletion src/Services/Storage/Implementation/InstanceEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public InstanceEventService(IInstanceEventRepository repository, IHttpContextAcc
}

/// <inheritdoc/>
public async Task DispatchEvent(InstanceEventType eventType, Instance instance)
public InstanceEvent BuildInstanceEvent(InstanceEventType eventType, Instance instance)
{
var user = _contextAccessor.HttpContext.User;

Expand All @@ -48,6 +48,14 @@ public async Task DispatchEvent(InstanceEventType eventType, Instance instance)
Created = DateTime.UtcNow,
};

return instanceEvent;
}

/// <inheritdoc/>
public async Task DispatchEvent(InstanceEventType eventType, Instance instance)
{
var instanceEvent = BuildInstanceEvent(eventType, instance);

await _repository.InsertInstanceEvent(instanceEvent);
}

Expand Down
20 changes: 20 additions & 0 deletions src/Services/Storage/Interface/IInstanceAndEventsRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Altinn.Platform.Storage.Interface.Models;

namespace Altinn.Platform.Storage.Repository;

/// <summary>
/// Represents an implementation of <see cref="IInstanceAndEventsRepository"/>.
/// </summary>
public interface IInstanceAndEventsRepository
{
/// <summary>
/// update existing instance including instance events
/// </summary>
/// <param name="instance">the instance to update</param>
/// <param name="updateProperties">a list of which properties should be updated</param>
/// <param name="events">the events to add</param>
/// <returns>The updated instance</returns>
Task<Instance> Update(Instance instance, List<string> updateProperties, List<InstanceEvent> events);
}
8 changes: 8 additions & 0 deletions src/Services/Storage/Interface/IInstanceEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ namespace Altinn.Platform.Storage.Services
/// </summary>
public interface IInstanceEventService
{
/// <summary>
/// Construct an instance event given a type
/// </summary>
/// <param name="eventType">Event type</param>
/// <param name="instance">Instance</param>
/// <returns></returns>
public InstanceEvent BuildInstanceEvent(InstanceEventType eventType, Instance instance);

/// <summary>
/// Dispatch an instance event to the repository
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<Altinn.Platform.Authorization.Services.Interface.IParties, PartiesService>();
services.AddSingleton<IClaims, ClaimsService>();
services.AddSingleton<IInstanceRepository, InstanceRepository>();
services.AddSingleton<IInstanceAndEventsRepository, InstanceAndEventsRepository>();
services.AddSingleton<IDataRepository, DataRepository>();
services.AddSingleton<IInstanceEventRepository, InstanceEventRepository>();
services.AddSingleton<IEventsRepository, EventsRepository>();
Expand Down

0 comments on commit 45a63b8

Please sign in to comment.