From a9676eac1b984b0246ab1b837d2426c938708744 Mon Sep 17 00:00:00 2001 From: SamPanDonte Date: Sat, 19 Nov 2022 22:11:52 +0100 Subject: [PATCH 1/7] remove editing released --- .../dev/vernite/vernite/release/Release.java | 15 -------- .../vernite/release/ReleaseRequest.java | 16 +------- .../release/ReleaseControllerTests.java | 38 +++++++++---------- 3 files changed, 20 insertions(+), 49 deletions(-) diff --git a/src/main/java/dev/vernite/vernite/release/Release.java b/src/main/java/dev/vernite/vernite/release/Release.java index fc61edc36..c747fc7ac 100644 --- a/src/main/java/dev/vernite/vernite/release/Release.java +++ b/src/main/java/dev/vernite/vernite/release/Release.java @@ -42,8 +42,6 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; import dev.vernite.vernite.project.Project; import dev.vernite.vernite.task.Task; @@ -90,19 +88,6 @@ public void update(@NotNull ReleaseRequest request) { request.getName().ifPresent(this::setName); request.getDescription().ifPresent(this::setDescription); request.getDeadline().ifPresent(this::setDeadline); - request.getReleased().ifPresent(aReleased -> { - if (Boolean.TRUE.equals(aReleased)) { - for (Task task : getTasks()) { - if (!task.getStatus().isFinal()) { - throw new ResponseStatusException(HttpStatus.CONFLICT, - "Cannot release release with non-final tasks"); - } - } - this.setReleased(true); - } else { - this.setReleased(false); - } - }); } public long getId() { diff --git a/src/main/java/dev/vernite/vernite/release/ReleaseRequest.java b/src/main/java/dev/vernite/vernite/release/ReleaseRequest.java index fa774d82f..1bf5ad331 100644 --- a/src/main/java/dev/vernite/vernite/release/ReleaseRequest.java +++ b/src/main/java/dev/vernite/vernite/release/ReleaseRequest.java @@ -43,17 +43,14 @@ public class ReleaseRequest { private Optional description = Optional.empty(); @Schema(description = "Estimated time to publish release.") private Optional deadline = Optional.empty(); - @Schema(description = "Whether release has been released. Ignored when creating release.") - private Optional released = Optional.empty(); public ReleaseRequest() { } - public ReleaseRequest(String name, String description, Date deadline, boolean released) { + public ReleaseRequest(String name, String description, Date deadline) { this.name = Optional.ofNullable(name); this.description = Optional.ofNullable(description); this.deadline = Optional.ofNullable(deadline); - this.released = Optional.ofNullable(released); } /** @@ -107,15 +104,4 @@ public Optional getDeadline() { public void setDeadline(Date deadline) { this.deadline = Optional.ofNullable(deadline); } - - public Optional getReleased() { - return released; - } - - public void setReleased(Boolean released) { - if (released == null) { - throw new FieldErrorException("released", NULL_VALUE); - } - this.released = Optional.of(released); - } } diff --git a/src/test/java/dev/vernite/vernite/release/ReleaseControllerTests.java b/src/test/java/dev/vernite/vernite/release/ReleaseControllerTests.java index 143449d71..30e5ba655 100644 --- a/src/test/java/dev/vernite/vernite/release/ReleaseControllerTests.java +++ b/src/test/java/dev/vernite/vernite/release/ReleaseControllerTests.java @@ -134,7 +134,7 @@ void getAllNotFound() { void createSuccess() { Release release = client.post().uri("/project/{projectId}/release", project.getId()) .cookie(AuthController.COOKIE_NAME, session.getSession()) - .bodyValue(new ReleaseRequest("Name", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name", null, null)).exchange() .expectStatus().isOk().expectBody(Release.class).returnResult().getResponseBody(); assertNotNull(release); @@ -145,31 +145,31 @@ void createSuccess() { @Test void createBadRequest() { client.post().uri("/project/{projectId}/release", project.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(null, null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(null, null, null)) .exchange().expectStatus().isBadRequest(); client.post().uri("/project/{projectId}/release", project.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("", null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("", null, null)) .exchange().expectStatus().isBadRequest(); client.post().uri("/project/{projectId}/release", project.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(" ", null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(" ", null, null)) .exchange().expectStatus().isBadRequest(); client.post().uri("/project/{projectId}/release", project.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("a".repeat(51), null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("a".repeat(51), null, null)) .exchange().expectStatus().isBadRequest(); } @Test void createUnauthorized() { client.post().uri("/project/{projectId}/release", project.getId()) - .bodyValue(new ReleaseRequest("Name", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name", null, null)).exchange() .expectStatus().isUnauthorized(); client.post().uri("/project/{projectId}/release", project.getId()) .cookie(AuthController.COOKIE_NAME, "invalid") - .bodyValue(new ReleaseRequest("Name", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name", null, null)).exchange() .expectStatus().isUnauthorized(); } @@ -177,13 +177,13 @@ void createUnauthorized() { void createNotFound() { client.post().uri("/project/{projectId}/release", 0) .cookie(AuthController.COOKIE_NAME, session.getSession()) - .bodyValue(new ReleaseRequest("Name", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name", null, null)).exchange() .expectStatus().isNotFound(); Project project2 = projectRepository.save(new Project("Sprint Tests 2")); client.post().uri("/project/{projectId}/release", project2.getId()) .cookie(AuthController.COOKIE_NAME, session.getSession()) - .bodyValue(new ReleaseRequest("Name", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name", null, null)).exchange() .expectStatus().isNotFound(); } @@ -227,7 +227,7 @@ void updateSuccess() { Release release = releaseRepository.save(new Release("Name", project)); Release result = client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), release.getId()) .cookie(AuthController.COOKIE_NAME, session.getSession()) - .bodyValue(new ReleaseRequest("Name 2", "null", null, false)).exchange().expectStatus().isOk() + .bodyValue(new ReleaseRequest("Name 2", "null", null)).exchange().expectStatus().isOk() .expectBody(Release.class).returnResult().getResponseBody(); release.setName("Name 2"); release.setDescription("null"); @@ -240,19 +240,19 @@ void updateSuccess() { void updateBadRequest() { Release release = releaseRepository.save(new Release("Name", project)); client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), release.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(null, null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(null, null, null)) .exchange().expectStatus().isBadRequest(); client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), release.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("", null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("", null, null)) .exchange().expectStatus().isBadRequest(); client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), release.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(" ", null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest(" ", null, null)) .exchange().expectStatus().isBadRequest(); client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), release.getId()) - .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("a".repeat(51), null, null, false)) + .cookie(AuthController.COOKIE_NAME, session.getSession()).bodyValue(new ReleaseRequest("a".repeat(51), null, null)) .exchange().expectStatus().isBadRequest(); } @@ -260,12 +260,12 @@ void updateBadRequest() { void updateUnauthorized() { Release release = releaseRepository.save(new Release("Name", project)); client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), release.getId()) - .bodyValue(new ReleaseRequest("Name 2", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name 2", null, null)).exchange() .expectStatus().isUnauthorized(); client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), release.getId()) .cookie(AuthController.COOKIE_NAME, "invalid") - .bodyValue(new ReleaseRequest("Name 2", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name 2", null, null)).exchange() .expectStatus().isUnauthorized(); } @@ -274,19 +274,19 @@ void updateNotFound() { Release release = releaseRepository.save(new Release("Name", project)); client.put().uri("/project/{projectId}/release/{releaseId}", project.getId(), 0) .cookie(AuthController.COOKIE_NAME, session.getSession()) - .bodyValue(new ReleaseRequest("Name 2", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name 2", null, null)).exchange() .expectStatus().isNotFound(); client.put().uri("/project/{projectId}/release/{releaseId}", 0, release.getId()) .cookie(AuthController.COOKIE_NAME, session.getSession()) - .bodyValue(new ReleaseRequest("Name 2", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name 2", null, null)).exchange() .expectStatus().isNotFound(); Project project2 = projectRepository.save(new Project("Sprint Tests 2")); projectWorkspaceRepository.save(new ProjectWorkspace(project2, workspace, 1L)); client.put().uri("/project/{projectId}/release/{releaseId}", project2.getId(), release.getId()) .cookie(AuthController.COOKIE_NAME, session.getSession()) - .bodyValue(new ReleaseRequest("Name 2", null, null, false)).exchange() + .bodyValue(new ReleaseRequest("Name 2", null, null)).exchange() .expectStatus().isNotFound(); } From e66c52ad85c72aabdbd467b9275a83a2c515a38f Mon Sep 17 00:00:00 2001 From: SamPanDonte Date: Sun, 20 Nov 2022 01:17:31 +0100 Subject: [PATCH 2/7] releases on github --- .../integration/git/GitTaskService.java | 5 +++ .../integration/git/github/GitHubService.java | 25 +++++++++++ .../git/github/data/GitHubRelease.java | 44 +++++++++++++++++++ .../dev/vernite/vernite/project/Project.java | 5 +++ .../vernite/release/ReleaseController.java | 28 ++++++++++++ 5 files changed, 107 insertions(+) create mode 100644 src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java diff --git a/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java b/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java index ddb008515..564cc94a0 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java +++ b/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java @@ -31,6 +31,7 @@ import dev.vernite.vernite.integration.git.github.GitHubService; import dev.vernite.vernite.project.Project; +import dev.vernite.vernite.release.Release; import dev.vernite.vernite.task.Task; import org.springframework.beans.factory.annotation.Autowired; @@ -171,4 +172,8 @@ public Mono connectPullRequest(Task task, PullRequest pullRequest) public void deletePullRequest(Task task) { gitHubService.deletePullRequest(task); } + + public Mono publishRelease(Release release) { + return gitHubService.publishRelease(release); + } } diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java b/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java index c002c869b..1a7ea428a 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java +++ b/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java @@ -44,6 +44,7 @@ import dev.vernite.vernite.integration.git.github.data.GitHubIssue; import dev.vernite.vernite.integration.git.github.data.GitHubMergeInfo; import dev.vernite.vernite.integration.git.github.data.GitHubPullRequest; +import dev.vernite.vernite.integration.git.github.data.GitHubRelease; import dev.vernite.vernite.integration.git.github.data.GitHubRepository; import dev.vernite.vernite.integration.git.github.data.GitHubUser; import dev.vernite.vernite.integration.git.github.data.GitHubInstallationRepositories; @@ -58,6 +59,7 @@ import dev.vernite.vernite.integration.git.github.entity.task.GitHubTaskPull; import dev.vernite.vernite.integration.git.github.entity.task.GitHubTaskPullRepository; import dev.vernite.vernite.project.Project; +import dev.vernite.vernite.release.Release; import dev.vernite.vernite.task.Task; import dev.vernite.vernite.user.User; import dev.vernite.vernite.utils.ExternalApiException; @@ -659,6 +661,19 @@ private Flux apiGetRepositoryCollaborators(GitHubInstallation instal .onErrorMap(error -> new ExternalApiException(GITHUB, error.getMessage())); } + private Mono apiCreateRelease(GitHubInstallation installation, + GitHubIntegration integration, GitHubRelease release) { + return client.post() + .uri("/repos/{owner}/{repo}/releases", integration.getRepositoryOwner(), + integration.getRepositoryName()) + .header(AUTHORIZATION, BEARER + installation.getToken()) + .header(ACCEPT, APPLICATION_JSON_GITHUB) + .bodyValue(release) + .retrieve() + .bodyToMono(GitHubPullRequest.class) + .onErrorMap(error -> new ExternalApiException(GITHUB, error.getMessage())).then(); + } + /** * Creates Json Web Token from file with private key. Token created with this * method lasts 10 minutes. @@ -681,4 +696,14 @@ private static String createJWT() { throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Unable to create JWT"); } } + + public Mono publishRelease(Release release) { + GitHubIntegration integration = release.getProject().getGitHubIntegrationEntity(); + if (integration == null) { + return Mono.empty(); + } + GitHubRelease gitHubRelease = new GitHubRelease(release); + return refreshToken(integration.getInstallation()) + .flatMap(installation -> apiCreateRelease(installation, integration, gitHubRelease)); + } } diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java new file mode 100644 index 000000000..e99b133ee --- /dev/null +++ b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java @@ -0,0 +1,44 @@ +package dev.vernite.vernite.integration.git.github.data; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import dev.vernite.vernite.release.Release; + +public class GitHubRelease { + + @JsonProperty("tag_name") + private String tagName; + private String body; + @JsonProperty("generate_release_notes") + private boolean generateReleaseNotes; + + public GitHubRelease(Release release) { + this.tagName = release.getName(); + this.body = release.getDescription(); + this.generateReleaseNotes = true; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getTagName() { + return tagName; + } + + public void setGenerateReleaseNotes(boolean generateReleaseNotes) { + this.generateReleaseNotes = generateReleaseNotes; + } + + public boolean getGenerateReleaseNotes() { + return generateReleaseNotes; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } +} diff --git a/src/main/java/dev/vernite/vernite/project/Project.java b/src/main/java/dev/vernite/vernite/project/Project.java index cdb49e7d4..b81d92839 100644 --- a/src/main/java/dev/vernite/vernite/project/Project.java +++ b/src/main/java/dev/vernite/vernite/project/Project.java @@ -241,6 +241,11 @@ public String getGitHubIntegration() { : null; } + @JsonIgnore + public GitHubIntegration getGitHubIntegrationEntity() { + return gitHubIntegration; + } + public void setGitHubIntegration(GitHubIntegration gitHubIntegration) { this.gitHubIntegration = gitHubIntegration; } diff --git a/src/main/java/dev/vernite/vernite/release/ReleaseController.java b/src/main/java/dev/vernite/vernite/release/ReleaseController.java index 98de79bce..cffb62512 100644 --- a/src/main/java/dev/vernite/vernite/release/ReleaseController.java +++ b/src/main/java/dev/vernite/vernite/release/ReleaseController.java @@ -32,6 +32,7 @@ import javax.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -40,7 +41,9 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; +import dev.vernite.vernite.integration.git.GitTaskService; import dev.vernite.vernite.project.Project; import dev.vernite.vernite.project.ProjectRepository; import dev.vernite.vernite.user.User; @@ -51,6 +54,7 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import reactor.core.publisher.Mono; @RestController @RequestMapping("/project/{projectId}/release") @@ -59,6 +63,8 @@ public class ReleaseController { private ProjectRepository projectRepository; @Autowired private ReleaseRepository releaseRepository; + @Autowired + private GitTaskService gitTaskService; @Operation(summary = "Retrieve all releases", description = "Retrieve all releases for a given project. Results are sorted by deadline.") @ApiResponse(description = "List of releases", responseCode = "200") @@ -126,6 +132,28 @@ public Release update(@NotNull @Parameter(hidden = true) User user, @PathVariabl return releaseRepository.save(release); } + @Operation(summary = "Publish a release", description = "Publish a release for a given project.") + @ApiResponse(description = "Release published", responseCode = "200", content = @Content(schema = @Schema(implementation = Release.class))) + @ApiResponse(description = "No user logged in.", responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorType.class))) + @ApiResponse(description = "Project or release not found.", responseCode = "404", content = @Content(schema = @Schema(implementation = ErrorType.class))) + @PutMapping("/{id}/publish") + public Mono publish(@NotNull @Parameter(hidden = true) User user, @PathVariable long projectId, @PathVariable long id) { + Project project = projectRepository.findByIdOrThrow(projectId); + if (project.member(user) == -1) { + throw new ObjectNotFoundException(); + } + Release release = releaseRepository.findByIdOrThrow(id); + if (release.getProject().getId() != projectId) { + throw new ObjectNotFoundException(); + } + if (release.getReleased()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Release already published"); + } + release.setReleased(true); + release = releaseRepository.save(release); + return gitTaskService.publishRelease(release).thenReturn(release); + } + @Operation(summary = "Delete a release", description = "Delete a release for a given project.") @ApiResponse(description = "Release deleted", responseCode = "200") @ApiResponse(description = "No user logged in.", responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorType.class))) From 68e8815d87d6790465a93e7c56aa301b07cb125c Mon Sep 17 00:00:00 2001 From: SamPanDonte Date: Sun, 20 Nov 2022 01:22:11 +0100 Subject: [PATCH 3/7] add required parameter whether to publish on git service --- .../dev/vernite/vernite/release/ReleaseController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/vernite/vernite/release/ReleaseController.java b/src/main/java/dev/vernite/vernite/release/ReleaseController.java index cffb62512..7e3a87469 100644 --- a/src/main/java/dev/vernite/vernite/release/ReleaseController.java +++ b/src/main/java/dev/vernite/vernite/release/ReleaseController.java @@ -137,7 +137,7 @@ public Release update(@NotNull @Parameter(hidden = true) User user, @PathVariabl @ApiResponse(description = "No user logged in.", responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorType.class))) @ApiResponse(description = "Project or release not found.", responseCode = "404", content = @Content(schema = @Schema(implementation = ErrorType.class))) @PutMapping("/{id}/publish") - public Mono publish(@NotNull @Parameter(hidden = true) User user, @PathVariable long projectId, @PathVariable long id) { + public Mono publish(@NotNull @Parameter(hidden = true) User user, @PathVariable long projectId, @PathVariable long id, boolean publishGitService) { Project project = projectRepository.findByIdOrThrow(projectId); if (project.member(user) == -1) { throw new ObjectNotFoundException(); @@ -151,7 +151,11 @@ public Mono publish(@NotNull @Parameter(hidden = true) User user, @Path } release.setReleased(true); release = releaseRepository.save(release); - return gitTaskService.publishRelease(release).thenReturn(release); + if (publishGitService) { + return gitTaskService.publishRelease(release).thenReturn(release); + } else { + return Mono.just(release); + } } @Operation(summary = "Delete a release", description = "Delete a release for a given project.") From 1f9b4d085cdca93f8fbc11273b1e5ef447f409ab Mon Sep 17 00:00:00 2001 From: SamPanDonte Date: Sun, 20 Nov 2022 01:29:44 +0100 Subject: [PATCH 4/7] fix name containing spaces --- .../vernite/integration/git/github/data/GitHubRelease.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java index e99b133ee..f6f81f3bd 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java +++ b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java @@ -13,7 +13,7 @@ public class GitHubRelease { private boolean generateReleaseNotes; public GitHubRelease(Release release) { - this.tagName = release.getName(); + this.tagName = release.getName().replace(" ", "-"); this.body = release.getDescription(); this.generateReleaseNotes = true; } From a02c3a029e400165266e144c54b047bfd70ad648 Mon Sep 17 00:00:00 2001 From: SamPanDonte Date: Sun, 20 Nov 2022 11:52:23 +0100 Subject: [PATCH 5/7] allow to choose branch to release from --- .../vernite/integration/git/Branch.java | 17 +++++++ .../integration/git/GitTaskService.java | 8 ++- .../integration/git/github/GitHubService.java | 29 ++++++++++- .../git/github/data/GitHubBranchRead.java | 50 +++++++++++++++++++ .../git/github/data/GitHubRelease.java | 12 +++++ .../vernite/project/ProjectController.java | 14 ++++++ .../vernite/release/ReleaseController.java | 6 ++- 7 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 src/main/java/dev/vernite/vernite/integration/git/Branch.java create mode 100644 src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubBranchRead.java diff --git a/src/main/java/dev/vernite/vernite/integration/git/Branch.java b/src/main/java/dev/vernite/vernite/integration/git/Branch.java new file mode 100644 index 000000000..f4dd9fa6f --- /dev/null +++ b/src/main/java/dev/vernite/vernite/integration/git/Branch.java @@ -0,0 +1,17 @@ +package dev.vernite.vernite.integration.git; + +public class Branch { + private String name; + + public Branch(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java b/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java index 564cc94a0..1c4f231af 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java +++ b/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java @@ -173,7 +173,11 @@ public void deletePullRequest(Task task) { gitHubService.deletePullRequest(task); } - public Mono publishRelease(Release release) { - return gitHubService.publishRelease(release); + public Mono publishRelease(Release release, String branch) { + return gitHubService.publishRelease(release, branch); + } + + public Flux getBranches(Project project) { + return Flux.concat(List.of(gitHubService.getBranches(project))); } } diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java b/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java index 1a7ea428a..be09eadcb 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java +++ b/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java @@ -38,8 +38,10 @@ import java.util.List; import java.util.Optional; +import dev.vernite.vernite.integration.git.Branch; import dev.vernite.vernite.integration.git.Issue; import dev.vernite.vernite.integration.git.PullRequest; +import dev.vernite.vernite.integration.git.github.data.GitHubBranchRead; import dev.vernite.vernite.integration.git.github.data.GitHubInstallationApi; import dev.vernite.vernite.integration.git.github.data.GitHubIssue; import dev.vernite.vernite.integration.git.github.data.GitHubMergeInfo; @@ -674,6 +676,18 @@ private Mono apiCreateRelease(GitHubInstallation installation, .onErrorMap(error -> new ExternalApiException(GITHUB, error.getMessage())).then(); } + private Flux apiGetBranches(GitHubInstallation installation, + GitHubIntegration integration) { + return client.get() + .uri("/repos/{owner}/{repo}/branches", integration.getRepositoryOwner(), + integration.getRepositoryName()) + .header(AUTHORIZATION, BEARER + installation.getToken()) + .header(ACCEPT, APPLICATION_JSON_GITHUB) + .retrieve() + .bodyToFlux(GitHubBranchRead.class) + .onErrorMap(error -> new ExternalApiException(GITHUB, error.getMessage())); + } + /** * Creates Json Web Token from file with private key. Token created with this * method lasts 10 minutes. @@ -697,13 +711,26 @@ private static String createJWT() { } } - public Mono publishRelease(Release release) { + public Mono publishRelease(Release release, String branch) { GitHubIntegration integration = release.getProject().getGitHubIntegrationEntity(); if (integration == null) { return Mono.empty(); } GitHubRelease gitHubRelease = new GitHubRelease(release); + if (branch != null) { + gitHubRelease.setTargetCommitish(branch); + } return refreshToken(integration.getInstallation()) .flatMap(installation -> apiCreateRelease(installation, integration, gitHubRelease)); } + + public Flux getBranches(Project project) { + GitHubIntegration integration = project.getGitHubIntegrationEntity(); + if (integration == null) { + return Flux.empty(); + } + return refreshToken(integration.getInstallation()) + .flatMapMany(installation -> apiGetBranches(installation, integration)) + .map(branch -> new Branch(branch.getName())); + } } diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubBranchRead.java b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubBranchRead.java new file mode 100644 index 000000000..097331cd8 --- /dev/null +++ b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubBranchRead.java @@ -0,0 +1,50 @@ +/* + * BSD 2-Clause License + * + * Copyright (c) 2022, [Aleksandra Serba, Marcin Czerniak, Bartosz Wawrzyniak, Adrian Antkowiak] + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package dev.vernite.vernite.integration.git.github.data; + +/** + * Object to represent a GitHub Rest api branch. + */ +public class GitHubBranchRead { + private String name; + + public GitHubBranchRead() { + } + + public GitHubBranchRead(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java index f6f81f3bd..598bb603f 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java +++ b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java @@ -1,9 +1,11 @@ package dev.vernite.vernite.integration.git.github.data; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import dev.vernite.vernite.release.Release; +@JsonInclude(JsonInclude.Include.NON_NULL) public class GitHubRelease { @JsonProperty("tag_name") @@ -11,6 +13,8 @@ public class GitHubRelease { private String body; @JsonProperty("generate_release_notes") private boolean generateReleaseNotes; + @JsonProperty("target_commitish") + private String targetCommitish; public GitHubRelease(Release release) { this.tagName = release.getName().replace(" ", "-"); @@ -41,4 +45,12 @@ public boolean getGenerateReleaseNotes() { public void setTagName(String tagName) { this.tagName = tagName; } + + public String getTargetCommitish() { + return targetCommitish; + } + + public void setTargetCommitish(String targetCommitish) { + this.targetCommitish = targetCommitish; + } } diff --git a/src/main/java/dev/vernite/vernite/project/ProjectController.java b/src/main/java/dev/vernite/vernite/project/ProjectController.java index ac876a76f..0fe5c537f 100644 --- a/src/main/java/dev/vernite/vernite/project/ProjectController.java +++ b/src/main/java/dev/vernite/vernite/project/ProjectController.java @@ -58,6 +58,7 @@ import dev.vernite.vernite.event.EventService; import dev.vernite.vernite.integration.calendar.CalendarIntegration; import dev.vernite.vernite.integration.calendar.CalendarIntegrationRepository; +import dev.vernite.vernite.integration.git.Branch; import dev.vernite.vernite.integration.git.GitTaskService; import dev.vernite.vernite.integration.git.Issue; import dev.vernite.vernite.integration.git.PullRequest; @@ -303,6 +304,19 @@ public Flux getPullRequests(@NotNull @Parameter(hidden = true) User return service.getPullRequests(project); } + @Operation(summary = "Retrieve git branches for project", description = "Retrieves all branches from all integrated git services for project.") + @ApiResponse(description = "List of branches.", responseCode = "200", content = @Content(schema = @Schema(implementation = Branch.class))) + @ApiResponse(description = "No user logged in.", responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorType.class))) + @ApiResponse(description = "Project not found.", responseCode = "404", content = @Content(schema = @Schema(implementation = ErrorType.class))) + @GetMapping("/{id}/integration/git/branch") + public Flux getBranches(@NotNull @Parameter(hidden = true) User user, @PathVariable long id) { + Project project = projectRepository.findByIdOrThrow(id); + if (project.member(user) == -1) { + throw new ObjectNotFoundException(); + } + return service.getBranches(project); + } + @Operation(summary = "Retrieve events for project", description = "Retrieves events from specified timestamp for project.") @ApiResponse(description = "List of events.", responseCode = "200") @ApiResponse(description = "No user logged in.", responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorType.class))) diff --git a/src/main/java/dev/vernite/vernite/release/ReleaseController.java b/src/main/java/dev/vernite/vernite/release/ReleaseController.java index 7e3a87469..62613884f 100644 --- a/src/main/java/dev/vernite/vernite/release/ReleaseController.java +++ b/src/main/java/dev/vernite/vernite/release/ReleaseController.java @@ -51,6 +51,7 @@ import dev.vernite.vernite.utils.ObjectNotFoundException; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -137,7 +138,8 @@ public Release update(@NotNull @Parameter(hidden = true) User user, @PathVariabl @ApiResponse(description = "No user logged in.", responseCode = "401", content = @Content(schema = @Schema(implementation = ErrorType.class))) @ApiResponse(description = "Project or release not found.", responseCode = "404", content = @Content(schema = @Schema(implementation = ErrorType.class))) @PutMapping("/{id}/publish") - public Mono publish(@NotNull @Parameter(hidden = true) User user, @PathVariable long projectId, @PathVariable long id, boolean publishGitService) { + public Mono publish(@NotNull @Parameter(hidden = true) User user, @PathVariable long projectId, + @PathVariable long id, boolean publishGitService, @Parameter(required = false, in = ParameterIn.QUERY, description = "Not required") String branch) { Project project = projectRepository.findByIdOrThrow(projectId); if (project.member(user) == -1) { throw new ObjectNotFoundException(); @@ -152,7 +154,7 @@ public Mono publish(@NotNull @Parameter(hidden = true) User user, @Path release.setReleased(true); release = releaseRepository.save(release); if (publishGitService) { - return gitTaskService.publishRelease(release).thenReturn(release); + return gitTaskService.publishRelease(release, branch).thenReturn(release); } else { return Mono.just(release); } From 14d687c55048b842e86f224414c5bf478f2cbbaa Mon Sep 17 00:00:00 2001 From: SamPanDonte Date: Sun, 20 Nov 2022 23:54:11 +0100 Subject: [PATCH 6/7] allow releasing on github after initial release --- .../vernite/integration/git/GitTaskService.java | 2 +- .../integration/git/github/GitHubService.java | 9 +++++---- .../integration/git/github/data/GitHubRelease.java | 13 +++++++++++++ .../java/dev/vernite/vernite/release/Release.java | 13 +++++++++++++ .../vernite/vernite/release/ReleaseController.java | 14 ++++++++++---- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java b/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java index 1c4f231af..52f8e4aa3 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java +++ b/src/main/java/dev/vernite/vernite/integration/git/GitTaskService.java @@ -173,7 +173,7 @@ public void deletePullRequest(Task task) { gitHubService.deletePullRequest(task); } - public Mono publishRelease(Release release, String branch) { + public Mono publishRelease(Release release, String branch) { return gitHubService.publishRelease(release, branch); } diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java b/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java index be09eadcb..bba46ca60 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java +++ b/src/main/java/dev/vernite/vernite/integration/git/github/GitHubService.java @@ -663,7 +663,7 @@ private Flux apiGetRepositoryCollaborators(GitHubInstallation instal .onErrorMap(error -> new ExternalApiException(GITHUB, error.getMessage())); } - private Mono apiCreateRelease(GitHubInstallation installation, + private Mono apiCreateRelease(GitHubInstallation installation, GitHubIntegration integration, GitHubRelease release) { return client.post() .uri("/repos/{owner}/{repo}/releases", integration.getRepositoryOwner(), @@ -672,8 +672,9 @@ private Mono apiCreateRelease(GitHubInstallation installation, .header(ACCEPT, APPLICATION_JSON_GITHUB) .bodyValue(release) .retrieve() - .bodyToMono(GitHubPullRequest.class) - .onErrorMap(error -> new ExternalApiException(GITHUB, error.getMessage())).then(); + .bodyToMono(GitHubRelease.class) + .onErrorMap(error -> new ExternalApiException(GITHUB, error.getMessage())) + .map(GitHubRelease::getId); } private Flux apiGetBranches(GitHubInstallation installation, @@ -711,7 +712,7 @@ private static String createJWT() { } } - public Mono publishRelease(Release release, String branch) { + public Mono publishRelease(Release release, String branch) { GitHubIntegration integration = release.getProject().getGitHubIntegrationEntity(); if (integration == null) { return Mono.empty(); diff --git a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java index 598bb603f..10e6c139e 100644 --- a/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java +++ b/src/main/java/dev/vernite/vernite/integration/git/github/data/GitHubRelease.java @@ -8,6 +8,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class GitHubRelease { + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private Long id; @JsonProperty("tag_name") private String tagName; private String body; @@ -16,6 +18,9 @@ public class GitHubRelease { @JsonProperty("target_commitish") private String targetCommitish; + public GitHubRelease() { + } + public GitHubRelease(Release release) { this.tagName = release.getName().replace(" ", "-"); this.body = release.getDescription(); @@ -53,4 +58,12 @@ public String getTargetCommitish() { public void setTargetCommitish(String targetCommitish) { this.targetCommitish = targetCommitish; } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } } diff --git a/src/main/java/dev/vernite/vernite/release/Release.java b/src/main/java/dev/vernite/vernite/release/Release.java index c747fc7ac..e0c2a5918 100644 --- a/src/main/java/dev/vernite/vernite/release/Release.java +++ b/src/main/java/dev/vernite/vernite/release/Release.java @@ -43,6 +43,8 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; +import com.fasterxml.jackson.annotation.JsonIgnore; + import dev.vernite.vernite.project.Project; import dev.vernite.vernite.task.Task; import dev.vernite.vernite.utils.SoftDeleteEntity; @@ -70,6 +72,9 @@ public class Release extends SoftDeleteEntity { @OneToMany(mappedBy = "release") private List tasks; + @JsonIgnore + private long gitReleaseId = 0; + public Release() { } @@ -145,4 +150,12 @@ public List getTasks() { public void setTasks(List tasks) { this.tasks = tasks; } + + public long getGitReleaseId() { + return gitReleaseId; + } + + public void setGitReleaseId(long gitReleaseI) { + this.gitReleaseId = gitReleaseI; + } } diff --git a/src/main/java/dev/vernite/vernite/release/ReleaseController.java b/src/main/java/dev/vernite/vernite/release/ReleaseController.java index 62613884f..a27765d67 100644 --- a/src/main/java/dev/vernite/vernite/release/ReleaseController.java +++ b/src/main/java/dev/vernite/vernite/release/ReleaseController.java @@ -129,6 +129,9 @@ public Release update(@NotNull @Parameter(hidden = true) User user, @PathVariabl if (release.getProject().getId() != projectId) { throw new ObjectNotFoundException(); } + if (release.getReleased()) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot update a released release."); + } release.update(request); return releaseRepository.save(release); } @@ -148,15 +151,18 @@ public Mono publish(@NotNull @Parameter(hidden = true) User user, @Path if (release.getProject().getId() != projectId) { throw new ObjectNotFoundException(); } - if (release.getReleased()) { + if (release.getReleased() && release.getGitReleaseId() > 0) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Release already published"); } release.setReleased(true); - release = releaseRepository.save(release); + Release finalRelease = releaseRepository.save(release); if (publishGitService) { - return gitTaskService.publishRelease(release, branch).thenReturn(release); + return gitTaskService.publishRelease(finalRelease, branch).map(gitId -> { + finalRelease.setGitReleaseId(gitId); + return releaseRepository.save(finalRelease); + }); } else { - return Mono.just(release); + return Mono.just(finalRelease); } } From e2ee46b8816c9a6836bad484196d12342873815b Mon Sep 17 00:00:00 2001 From: SamPanDonte Date: Sun, 20 Nov 2022 23:57:54 +0100 Subject: [PATCH 7/7] allow release only when all tasks are completed --- .../java/dev/vernite/vernite/release/ReleaseController.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/dev/vernite/vernite/release/ReleaseController.java b/src/main/java/dev/vernite/vernite/release/ReleaseController.java index a27765d67..6c8cdd8aa 100644 --- a/src/main/java/dev/vernite/vernite/release/ReleaseController.java +++ b/src/main/java/dev/vernite/vernite/release/ReleaseController.java @@ -46,6 +46,7 @@ import dev.vernite.vernite.integration.git.GitTaskService; import dev.vernite.vernite.project.Project; import dev.vernite.vernite.project.ProjectRepository; +import dev.vernite.vernite.task.Task; import dev.vernite.vernite.user.User; import dev.vernite.vernite.utils.ErrorType; import dev.vernite.vernite.utils.ObjectNotFoundException; @@ -154,6 +155,11 @@ public Mono publish(@NotNull @Parameter(hidden = true) User user, @Path if (release.getReleased() && release.getGitReleaseId() > 0) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Release already published"); } + for (Task task : release.getTasks()) { + if (!task.getStatus().isFinal()) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Release contains tasks not done"); + } + } release.setReleased(true); Release finalRelease = releaseRepository.save(release); if (publishGitService) {