diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index a2f19a3fd1..4206450124 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -45,7 +45,14 @@ Both `IInput` and `IDomainEvent` implement `ICause` and will thus be used to ind === New Features - https://github.com/eclipse-sirius/sirius-web/issues/3763[#3763] [diagram] Make it possible to display semantic candidates in the selection dialog using a tree - +- https://github.com/eclipse-sirius/sirius-web/issues/3979[#3979] [core] Add Project related REST APIs. +The new endpoints are: +** getProjects (`GET /api/rest/projects`): Get all projects. +** getProjectById (`GET /api/rest/projects/{projectId}`): Get project with the given id (projectId). +** createProject (`POST /projects`): Create a new project with the given name and +description (optional). +** deleteProject (`POST /api/rest/projects/{projectId}`): Delete the project with the given id (projectId). +** updateProject (`PUT /projects/{projectId}`): Update the project with the given id (projectId). === Improvements diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/dto/IRestRecord.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/dto/IRestRecord.java new file mode 100644 index 0000000000..17ee2b1fc8 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/dto/IRestRecord.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.dto; + +import java.util.List; +import java.util.UUID; + +/** + * Interface for the REST Record DTO. + * + * @author arichard + */ +public interface IRestRecord { + + UUID id(); + + String resourceIdentifier(); + + List alias(); + + String humanIdentifier(); + + String decription(); +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/controllers/ProjectRestController.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/controllers/ProjectRestController.java new file mode 100644 index 0000000000..28016dba78 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/controllers/ProjectRestController.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.project.controllers; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +import org.eclipse.sirius.components.core.api.ErrorPayload; +import org.eclipse.sirius.web.application.project.data.versioning.dto.RestBranch; +import org.eclipse.sirius.web.application.project.dto.CreateProjectInput; +import org.eclipse.sirius.web.application.project.dto.CreateProjectSuccessPayload; +import org.eclipse.sirius.web.application.project.dto.DeleteProjectInput; +import org.eclipse.sirius.web.application.project.dto.RenameProjectInput; +import org.eclipse.sirius.web.application.project.dto.RenameProjectSuccessPayload; +import org.eclipse.sirius.web.application.project.dto.RestProject; +import org.eclipse.sirius.web.application.project.services.api.IProjectApplicationService; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST Controller for the Project Endpoint. + * + * @author arichard + */ +@RestController +@RequestMapping("/api/rest/projects") +public class ProjectRestController { + + private final IProjectApplicationService projectApplicationService; + + public ProjectRestController(IProjectApplicationService projectApplicationService) { + this.projectApplicationService = Objects.requireNonNull(projectApplicationService); + } + + @GetMapping + public ResponseEntity> getProjects() { + var restProjects = this.projectApplicationService.findAll(PageRequest.of(0, 20)) + .map(project -> new RestProject(project.id(), null, List.of(), null, null, project.name(), List.of())) + .toList(); + + return new ResponseEntity<>(restProjects, HttpStatus.OK); + } + + @GetMapping(path = "/{projectId}") + public ResponseEntity getProjectById(@PathVariable UUID projectId) { + var restProject = this.projectApplicationService.findById(projectId) + .map(project -> new RestProject(project.id(), null, List.of(), null, null, project.name(), List.of())); + + if (restProject.isPresent()) { + return new ResponseEntity<>(restProject.get(), HttpStatus.OK); + } + + return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); + } + + @DeleteMapping(path = "/{projectId}") + public ResponseEntity deleteProject(@PathVariable UUID projectId) { + var restProject = this.projectApplicationService.findById(projectId) + .map(project -> new RestProject(project.id(), null, List.of(), null, null, project.name(), List.of())) + .orElse(null); + + var deleteProjectInput = new DeleteProjectInput(UUID.randomUUID(), projectId); + var deleteProjectPayload = this.projectApplicationService.deleteProject(deleteProjectInput); + + if (deleteProjectPayload instanceof ErrorPayload) { + return new ResponseEntity<>(null, HttpStatus.NO_CONTENT); + } + + return new ResponseEntity<>(restProject, HttpStatus.OK); + } + + @PostMapping + public ResponseEntity createProject(@RequestParam String name, @RequestParam Optional description) { + var createProjectInput = new CreateProjectInput(UUID.randomUUID(), name, List.of()); + var newProjectPayload = this.projectApplicationService.createProject(createProjectInput); + + if (newProjectPayload instanceof CreateProjectSuccessPayload createProjectSuccessPayload) { + var projectDTO = createProjectSuccessPayload.project(); + var restProject = new RestProject(projectDTO.id(), null, List.of(), null, null, projectDTO.name(), List.of()); + return new ResponseEntity<>(restProject, HttpStatus.CREATED); + } + // The specification does not handle other HttpStatus than HttpStatus.CREATED for this endpoint + return null; + } + + @PutMapping(path = "/{projectId}") + public ResponseEntity updateProject(@PathVariable UUID projectId, @RequestParam Optional name, @RequestParam Optional description, @RequestParam Optional branch) { + if (name.isPresent()) { + var renameProjectInput = new RenameProjectInput(UUID.randomUUID(), projectId, name.get()); + var renamedProjectPayload = this.projectApplicationService.renameProject(renameProjectInput); + if (renamedProjectPayload instanceof RenameProjectSuccessPayload) { + var restProject = new RestProject(projectId, null, List.of(), null, null, name.get(), List.of()); + return new ResponseEntity<>(restProject, HttpStatus.OK); + } + } + + return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); + } +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/data/versioning/dto/IRestCommitReference.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/data/versioning/dto/IRestCommitReference.java new file mode 100644 index 0000000000..c74f5bf40d --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/data/versioning/dto/IRestCommitReference.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.project.data.versioning.dto; + +import java.text.DateFormat; + +import org.eclipse.sirius.web.application.dto.IRestRecord; + +/** + * Interface for the REST CommitReference DTO. + * + * @author arichard + */ +public interface IRestCommitReference extends IRestRecord { + + DateFormat created(); + + String name(); +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/data/versioning/dto/RestBranch.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/data/versioning/dto/RestBranch.java new file mode 100644 index 0000000000..52f2be296d --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/data/versioning/dto/RestBranch.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.project.data.versioning.dto; + +/** + * REST Branch DTO. + * + * @author arichard + */ +public record RestBranch() { + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/dto/RestProject.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/dto/RestProject.java new file mode 100644 index 0000000000..fd18719ccf --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/project/dto/RestProject.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.project.dto; + +import java.util.List; +import java.util.UUID; + +import org.eclipse.sirius.web.application.dto.IRestRecord; +import org.eclipse.sirius.web.application.query.dto.RestQuery; + +/** + * REST Project DTO. + * + * @author arichard + */ +public record RestProject( + UUID id, + String resourceIdentifier, + List alias, + String humanIdentifier, + String decription, + String name, + List queries) implements IRestRecord { + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/query/dto/RestQuery.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/query/dto/RestQuery.java new file mode 100644 index 0000000000..8b9279684e --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/query/dto/RestQuery.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.query.dto; + +import java.util.List; +import java.util.UUID; + +import org.eclipse.sirius.web.application.dto.IRestRecord; + +/** + * REST Query DTO. + * + * @author arichard + */ +public record RestQuery( + UUID id, + String resourceIdentifier, + List alias, + String humanIdentifier, + String decription) implements IRestRecord { +} diff --git a/packages/sirius-web/backend/sirius-web/pom.xml b/packages/sirius-web/backend/sirius-web/pom.xml index 8b602a045f..a4fd591853 100644 --- a/packages/sirius-web/backend/sirius-web/pom.xml +++ b/packages/sirius-web/backend/sirius-web/pom.xml @@ -78,6 +78,11 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-webflux + test + com.tngtech.archunit archunit-junit5 diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/projects/ProjectRestControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/projects/ProjectRestControllerIntegrationTests.java new file mode 100644 index 0000000000..69d5d41b74 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/projects/ProjectRestControllerIntegrationTests.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.controllers.projects; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.sirius.web.AbstractIntegrationTests; +import org.eclipse.sirius.web.application.project.dto.RestProject; +import org.eclipse.sirius.web.data.TestIdentifiers; +import org.eclipse.sirius.web.tests.services.api.IGivenCommittedTransaction; +import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests of the project REST controller. + * + * @author arichard + */ +@Transactional +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ProjectRestControllerIntegrationTests extends AbstractIntegrationTests { + + @Autowired + private IGivenInitialServerState givenInitialServerState; + + @Autowired + private IGivenCommittedTransaction givenCommittedTransaction; + + @LocalServerPort + private String port; + + private String getHTTPBaseUrl() { + return "http://localhost:" + this.port; + } + + @BeforeEach + public void beforeEach() { + this.givenInitialServerState.initialize(); + } + + @Test + @DisplayName("Test the '/projects' REST API, should return all known projects") + @Sql(scripts = {"/scripts/initialize.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = {"/scripts/cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void restAPIgetProjects() { + this.givenCommittedTransaction.commit(); + + var webTestClient = WebTestClient.bindToServer() + .baseUrl(this.getHTTPBaseUrl()) + .build(); + + var response = webTestClient + .get() + .uri("/api/rest/projects") + .exchange(); + + response.expectStatus().isOk(); + response.expectBodyList(RestProject.class).hasSize(3); + } + + @Test + @DisplayName("Test the '/projects/{projectId}' REST API, should return the project corresponding to the given projectId") + @Sql(scripts = {"/scripts/initialize.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = {"/scripts/cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void restAPIgetProjectFromProjectId() { + this.givenCommittedTransaction.commit(); + + var webTestClient = WebTestClient.bindToServer() + .baseUrl(this.getHTTPBaseUrl()) + .build(); + + var response = webTestClient + .get() + .uri("/api/rest/projects/" + TestIdentifiers.UML_SAMPLE_PROJECT) + .exchange(); + + response.expectStatus().isOk(); + response.expectBody(RestProject.class).consumeWith(result -> { + var restProject = result.getResponseBody(); + assertEquals(TestIdentifiers.UML_SAMPLE_PROJECT, restProject.id()); + assertEquals("UML Sample", restProject.name()); + }); + } +}