Add flow publication and finalization controls
This commit is contained in:
parent
ee14e5d7d1
commit
8c26b5d8fc
|
|
@ -23,6 +23,7 @@ import it.cnr.isti.workflow.manager.flows.FlowAccessDeniedException;
|
|||
import it.cnr.isti.workflow.manager.flows.FlowNotFoundException;
|
||||
import it.cnr.isti.workflow.manager.flows.FlowService;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowCreateRequest;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowFlagUpdateRequest;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowView;
|
||||
|
||||
|
||||
|
|
@ -118,4 +119,36 @@ public class FlowController {
|
|||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/published")
|
||||
@Operation(summary = "Update published flag", description = "Updates the published flag of a flow owned by the authenticated user.")
|
||||
public ResponseEntity<FlowView> updatePublished(@PathVariable String id,
|
||||
@RequestBody @Valid FlowFlagUpdateRequest request,
|
||||
@AuthenticationPrincipal LoginEntity userDetails) {
|
||||
try {
|
||||
return ResponseEntity.ok(flowService.updatePublished(id, userDetails.getUsername(), request));
|
||||
} catch (FlowNotFoundException e) {
|
||||
logger.warn("Update published flag for flow {} failed for user {}: not found", id, userDetails.getUsername());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||
} catch (FlowAccessDeniedException e) {
|
||||
logger.warn("Update published flag for flow {} forbidden for user {}", id, userDetails.getUsername());
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/finalized")
|
||||
@Operation(summary = "Update finalized flag", description = "Updates the finalized flag of a flow owned by the authenticated user.")
|
||||
public ResponseEntity<FlowView> updateFinalized(@PathVariable String id,
|
||||
@RequestBody @Valid FlowFlagUpdateRequest request,
|
||||
@AuthenticationPrincipal LoginEntity userDetails) {
|
||||
try {
|
||||
return ResponseEntity.ok(flowService.updateFinalized(id, userDetails.getUsername(), request));
|
||||
} catch (FlowNotFoundException e) {
|
||||
logger.warn("Update finalized flag for flow {} failed for user {}: not found", id, userDetails.getUsername());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||
} catch (FlowAccessDeniedException e) {
|
||||
logger.warn("Update finalized flag for flow {} forbidden for user {}", id, userDetails.getUsername());
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowCreateRequest;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowFlagUpdateRequest;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowView;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowViewStatus;
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowEntity;
|
||||
|
|
@ -48,6 +49,7 @@ public class FlowService {
|
|||
logger.warn("User {} attempted to update flow {} owned by {}", owner, id, entity.getOwner());
|
||||
throw new FlowAccessDeniedException();
|
||||
}
|
||||
ensureMutable(entity);
|
||||
|
||||
validateFlow(request);
|
||||
FlowMapper.updateEntity(entity, request);
|
||||
|
|
@ -63,10 +65,45 @@ public class FlowService {
|
|||
logger.warn("User {} attempted to delete flow {} owned by {}", owner, id, entity.getOwner());
|
||||
throw new FlowAccessDeniedException();
|
||||
}
|
||||
ensureMutable(entity);
|
||||
|
||||
flowRepository.delete(entity);
|
||||
}
|
||||
|
||||
public FlowView updatePublished(String id, String owner, FlowFlagUpdateRequest request) {
|
||||
FlowEntity entity = flowRepository.findById(id)
|
||||
.orElseThrow(() -> new FlowNotFoundException(id));
|
||||
|
||||
if (!entity.getOwner().equals(owner)) {
|
||||
logger.warn("User {} attempted to change publish flag of flow {} owned by {}", owner, id, entity.getOwner());
|
||||
throw new FlowAccessDeniedException();
|
||||
}
|
||||
|
||||
entity.setPublished(Boolean.TRUE.equals(request.value()));
|
||||
entity.setLastUpdateAt(java.time.LocalDateTime.now());
|
||||
return toView(flowRepository.save(entity));
|
||||
}
|
||||
|
||||
public FlowView updateFinalized(String id, String owner, FlowFlagUpdateRequest request) {
|
||||
FlowEntity entity = flowRepository.findById(id)
|
||||
.orElseThrow(() -> new FlowNotFoundException(id));
|
||||
|
||||
if (!entity.getOwner().equals(owner)) {
|
||||
logger.warn("User {} attempted to change finalized flag of flow {} owned by {}", owner, id, entity.getOwner());
|
||||
throw new FlowAccessDeniedException();
|
||||
}
|
||||
if (entity.isFinalized()) {
|
||||
if (Boolean.TRUE.equals(request.value())) {
|
||||
return toView(entity);
|
||||
}
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Flow is finalized");
|
||||
}
|
||||
|
||||
entity.setFinalized(Boolean.TRUE.equals(request.value()));
|
||||
entity.setLastUpdateAt(java.time.LocalDateTime.now());
|
||||
return toView(flowRepository.save(entity));
|
||||
}
|
||||
|
||||
public List<FlowView> getAllFlows(String owner) {
|
||||
return flowRepository.findFlowsByOwnerOrPublic(owner).stream().map(this::toView).toList();
|
||||
}
|
||||
|
|
@ -96,6 +133,12 @@ public class FlowService {
|
|||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
private void ensureMutable(FlowEntity entity) {
|
||||
if (entity != null && entity.isFinalized()) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Flow is finalized");
|
||||
}
|
||||
}
|
||||
|
||||
private FlowView toView(FlowEntity entity) {
|
||||
FlowViewStatus status = flowExecutionValidator.isExecutable(entity.getFlow())
|
||||
? FlowViewStatus.EXECUTABLE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package it.cnr.isti.workflow.manager.flows.model;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record FlowFlagUpdateRequest(
|
||||
@NotNull Boolean value) {
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ import it.cnr.isti.workflow.manager.flows.model.Connection;
|
|||
import it.cnr.isti.workflow.manager.flows.model.Flow;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowCreateRequest;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowData;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowFlagUpdateRequest;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowView;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowViewStatus;
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowRepository;
|
||||
|
|
@ -336,6 +337,170 @@ public class FlowControllerTest {
|
|||
assertEquals(404, response.getStatusCode().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ownerCanUpdatePublishedAndFinalizedFlags() {
|
||||
LLMDescriptor llmDescriptor = LLMDescriptor.builder()
|
||||
.provider("testProvider")
|
||||
.model("testModel")
|
||||
.build();
|
||||
|
||||
Flow flow = flowTestCreator.createFlowWithConnection(llmDescriptor);
|
||||
FlowCreateRequest request = new FlowCreateRequest(
|
||||
flow.getName(),
|
||||
flow.getDescription(),
|
||||
FlowData.builder()
|
||||
.blocks(flow.getBlocks())
|
||||
.connections(flow.getConnections())
|
||||
.build());
|
||||
|
||||
ResponseEntity<FlowView> createResponse = flowController.createFlow(
|
||||
request,
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
|
||||
assertTrue(createResponse.getStatusCode().is2xxSuccessful());
|
||||
assertNotNull(createResponse.getBody());
|
||||
|
||||
ResponseEntity<FlowView> publishedResponse = flowController.updatePublished(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(true),
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
assertEquals(200, publishedResponse.getStatusCode().value());
|
||||
assertNotNull(publishedResponse.getBody());
|
||||
assertTrue(publishedResponse.getBody().published());
|
||||
|
||||
ResponseEntity<FlowView> finalizedResponse = flowController.updateFinalized(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(true),
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
assertEquals(200, finalizedResponse.getStatusCode().value());
|
||||
assertNotNull(finalizedResponse.getBody());
|
||||
assertTrue(finalizedResponse.getBody().finalized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonOwnerCannotUpdatePublishedAndFinalizedFlags() {
|
||||
LLMDescriptor llmDescriptor = LLMDescriptor.builder()
|
||||
.provider("testProvider")
|
||||
.model("testModel")
|
||||
.build();
|
||||
|
||||
Flow flow = flowTestCreator.createFlowWithConnection(llmDescriptor);
|
||||
FlowCreateRequest request = new FlowCreateRequest(
|
||||
flow.getName(),
|
||||
flow.getDescription(),
|
||||
FlowData.builder()
|
||||
.blocks(flow.getBlocks())
|
||||
.connections(flow.getConnections())
|
||||
.build());
|
||||
|
||||
ResponseEntity<FlowView> createResponse = flowController.createFlow(
|
||||
request,
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
|
||||
assertTrue(createResponse.getStatusCode().is2xxSuccessful());
|
||||
assertNotNull(createResponse.getBody());
|
||||
|
||||
ResponseEntity<FlowView> publishedResponse = flowController.updatePublished(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(true),
|
||||
new LoginEntity("otheruser", "testpassword"));
|
||||
assertEquals(403, publishedResponse.getStatusCode().value());
|
||||
|
||||
ResponseEntity<FlowView> finalizedResponse = flowController.updateFinalized(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(true),
|
||||
new LoginEntity("otheruser", "testpassword"));
|
||||
assertEquals(403, finalizedResponse.getStatusCode().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finalizedFlowCannotBeModified() {
|
||||
LLMDescriptor llmDescriptor = LLMDescriptor.builder()
|
||||
.provider("testProvider")
|
||||
.model("testModel")
|
||||
.build();
|
||||
|
||||
Flow flow = flowTestCreator.createFlowWithConnection(llmDescriptor);
|
||||
FlowCreateRequest createRequest = new FlowCreateRequest(
|
||||
flow.getName(),
|
||||
flow.getDescription(),
|
||||
FlowData.builder()
|
||||
.blocks(flow.getBlocks())
|
||||
.connections(flow.getConnections())
|
||||
.build());
|
||||
|
||||
ResponseEntity<FlowView> createResponse = flowController.createFlow(
|
||||
createRequest,
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
|
||||
assertTrue(createResponse.getStatusCode().is2xxSuccessful());
|
||||
assertNotNull(createResponse.getBody());
|
||||
|
||||
ResponseEntity<FlowView> finalizedResponse = flowController.updateFinalized(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(true),
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
assertEquals(200, finalizedResponse.getStatusCode().value());
|
||||
assertTrue(finalizedResponse.getBody().finalized());
|
||||
|
||||
Flow updatedFlowDefinition = flowTestCreator.createFlowWithInteraction(llmDescriptor);
|
||||
FlowCreateRequest updateRequest = new FlowCreateRequest(
|
||||
updatedFlowDefinition.getName(),
|
||||
updatedFlowDefinition.getDescription(),
|
||||
FlowData.builder()
|
||||
.blocks(updatedFlowDefinition.getBlocks())
|
||||
.connections(updatedFlowDefinition.getConnections())
|
||||
.build());
|
||||
|
||||
ResponseStatusException updateException = assertThrows(
|
||||
ResponseStatusException.class,
|
||||
() -> flowController.updateFlow(
|
||||
createResponse.getBody().id(),
|
||||
updateRequest,
|
||||
new LoginEntity("testuser", "testpassword")));
|
||||
assertEquals(HttpStatus.CONFLICT, updateException.getStatusCode());
|
||||
|
||||
ResponseStatusException publishException = assertThrows(
|
||||
ResponseStatusException.class,
|
||||
() -> flowController.updateFinalized(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(false),
|
||||
new LoginEntity("testuser", "testpassword")));
|
||||
assertEquals(HttpStatus.CONFLICT, publishException.getStatusCode());
|
||||
|
||||
ResponseEntity<FlowView> unpublishedResponse = flowController.updatePublished(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(false),
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
assertEquals(200, unpublishedResponse.getStatusCode().value());
|
||||
assertNotNull(unpublishedResponse.getBody());
|
||||
assertTrue(unpublishedResponse.getBody().finalized());
|
||||
assertEquals(false, unpublishedResponse.getBody().published());
|
||||
|
||||
ResponseEntity<FlowView> publishedResponse = flowController.updatePublished(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(true),
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
assertEquals(200, publishedResponse.getStatusCode().value());
|
||||
assertNotNull(publishedResponse.getBody());
|
||||
assertTrue(publishedResponse.getBody().finalized());
|
||||
assertTrue(publishedResponse.getBody().published());
|
||||
|
||||
ResponseEntity<FlowView> idempotentFinalizeResponse = flowController.updateFinalized(
|
||||
createResponse.getBody().id(),
|
||||
new FlowFlagUpdateRequest(true),
|
||||
new LoginEntity("testuser", "testpassword"));
|
||||
assertEquals(200, idempotentFinalizeResponse.getStatusCode().value());
|
||||
assertTrue(idempotentFinalizeResponse.getBody().finalized());
|
||||
|
||||
ResponseStatusException deleteException = assertThrows(
|
||||
ResponseStatusException.class,
|
||||
() -> flowController.deleteFlow(
|
||||
createResponse.getBody().id(),
|
||||
new LoginEntity("testuser", "testpassword")));
|
||||
assertEquals(HttpStatus.CONFLICT, deleteException.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteFlow() {
|
||||
LLMDescriptor llmDescriptor = LLMDescriptor.builder()
|
||||
|
|
|
|||
Loading…
Reference in New Issue