Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions api-project/openapi/api-project-internal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
openapi: 3.0.3
info:
title: ODS API Server
description: API documentation for ODS (Open DevStack) API Service
contact:
name: ODS Team
version: v0.0.1
servers:
- url: http://{baseurl}/api/pub/v0
variables:
baseurl:
default: localhost:8080
description: Development environment
tags:
- name: Projects Internal
description: API for manage EDP projects with extended methods
paths:
/projects/{projectKey}:
patch:
tags:
- Projects Internal
summary: Updates projects by project key.
description: Updates the current status and details of the project identified by the given project key.
operationId: updateProject
parameters:
- name: projectKey
in: path
required: true
schema:
type: string
description: Project key to retrieve information.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateProjectRequest'
responses:
'204':
description: Project information updated successfully or no project found for the provided projectKey.
"400":
description: Bad Request.
"401":
description: Invalid client token on the request.
"403":
description: Insufficient permissions for the client to access the resource.
'500':
description: Internal server error.
components:
schemas:
RestErrorMessage:
properties:
message:
type: string
required:
- message
UpdateProjectRequest:
type: object
properties:
status:
type: string
description: Status of the project. If not in the allowed list, error INVALID_STATUS (030) is returned.
additionalProperties: false
2 changes: 1 addition & 1 deletion api-project/openapi/api-project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ servers:
default: localhost:8080
description: Development environment
tags:
- name: Project
- name: Projects
description: API for manage EDP projects.
paths:
/projects:
Expand Down
29 changes: 29 additions & 0 deletions api-project/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,35 @@
</configOptions>
</configuration>
</execution>
<execution>
<id>generate-api-project-extended</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<generatorName>spring</generatorName>
<output>${project.basedir}</output>
<library>spring-boot</library>
<inputSpec>${project.basedir}/openapi/api-project-internal.yaml</inputSpec>
<apiPackage>org.opendevstack.apiservice.project.api</apiPackage>
<modelPackage>org.opendevstack.apiservice.project.model</modelPackage>
<invokerPackage>org.opendevstack.apiservice.project</invokerPackage>
<skipOverwrite>false</skipOverwrite>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<generateApiDocumentation>false</generateApiDocumentation>
<generateModelDocumentation>false</generateModelDocumentation>
<generateSupportingFiles>false</generateSupportingFiles>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<useSpringBoot3>true</useSpringBoot3>
<documentationProvider>springdoc</documentationProvider>
<skipDefaultInterface>true</skipDefaultInterface>
<hideGenerationTimestamp>true</hideGenerationTimestamp>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class ProjectController implements ProjectsApi {
@PostMapping
@Override
public ResponseEntity<CreateProjectResponse> createProject(@Valid @RequestBody CreateProjectRequest createProjectRequest) {
projectRequestValidator.validate(createProjectRequest);
projectRequestValidator.validateCreateRequest(createProjectRequest);
UUID clientId = JwtUtils.getClientId();

CreateProjectResponse projectResponse = projectsFacade.createProject(createProjectRequest, clientId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.opendevstack.apiservice.project.controller;

import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opendevstack.apiservice.core.security.jwt.JwtUtils;
import org.opendevstack.apiservice.project.api.ProjectsInternalApi;
import org.opendevstack.apiservice.project.facade.ProjectsFacade;
import org.opendevstack.apiservice.project.model.UpdateProjectRequest;
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
import org.opendevstack.apiservice.project.validation.ProjectRequestValidator;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@RequestMapping(ProjectInternalController.API_BASE_PATH)
@AllArgsConstructor
@Slf4j
public class ProjectInternalController implements ProjectsInternalApi {

public static final String API_BASE_PATH = "/api/pub/v1/projects";

private static final String HTTP_HEADER_LOCATION = "Location";

private final ProjectsFacade projectsFacade;

private final ProjectRequestValidator projectRequestValidator;

@PatchMapping("/{projectKey}")
@Override
public ResponseEntity<Void> updateProject(@PathVariable String projectKey,
@Valid @RequestBody UpdateProjectRequest updateProjectRequest) {
String location = API_BASE_PATH + "/" + projectKey;
projectRequestValidator.validateUpdateRequest(updateProjectRequest);

projectsFacade.updateProject(projectKey, updateProjectRequest);

return ResponseEntity
.status(HttpStatus.NO_CONTENT)
.header(HTTP_HEADER_LOCATION, location)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.opendevstack.apiservice.project.controller.advice;

import lombok.extern.slf4j.Slf4j;
import org.opendevstack.apiservice.project.controller.ProjectInternalController;
import org.opendevstack.apiservice.project.exception.ProjectUpdateValidationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice(assignableTypes = ProjectInternalController.class)
@Slf4j
public class ProjectInternalExceptionHandler {

@ExceptionHandler(ProjectUpdateValidationException.class)
public ResponseEntity<Void> handleUpdateValidationException(ProjectUpdateValidationException ex) {
log.warn("Validation error: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public enum ErrorKey {
PROJECT_SAME_PROJECT_NAME_ALREADY_EXISTS("026", ErrorMessage.PROJECT_WITH_SAME_PROJECT_NAME_ALREADY_EXISTS),
CLIENT_APP_NOT_REGISTERED("027", ErrorMessage.CLIENT_APP_NOT_REGISTERED_MANUAL_REGISTRATION_REQUIRED),
INVALID_PROJECT_FLAVOR("028", ErrorMessage.BAD_REQUEST),
INVALID_CONFIG_ITEM("029", ErrorMessage.BAD_REQUEST);
INVALID_CONFIG_ITEM("029", ErrorMessage.BAD_REQUEST),
INVALID_STATUS("030", ErrorMessage.INVALID_STATUS);

private String key;
private String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class ErrorMessage {
public static final String SUCCESS = "Success";

public static final String INVALID_LOCATION = "Incorrect location. Valid locations are:";
public static final String INVALID_STATUS = "Incorrect status. Valid status are:";
public static final String RECORD_ALREADY_EXISTS = "Record already exists";
public static final String PROJECT_KEY_NOT_MET_THE_PATTERN = "projectKey not met the pattern ^[A-Z] {2}[A-Z0-9] {1,8}$";
public static final String PROJECT_NAME_NOT_MET_THE_PATTERN = "projectName not met the pattern ^[A-Za-z0-9 ] {0,80}$";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.opendevstack.apiservice.project.exception;

import lombok.Getter;

@Getter
public class ProjectUpdateValidationException extends RuntimeException {

private final ErrorKey errorKey;

public ProjectUpdateValidationException(ErrorKey errorKey) {
super(errorKey.getMessage());
this.errorKey = errorKey;
}

public ProjectUpdateValidationException(ErrorKey errorKey, String additionalMessage) {
super(errorKey.getMessage() + " " + additionalMessage);
this.errorKey = errorKey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.opendevstack.apiservice.project.model.CreateProjectRequest;
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
import org.opendevstack.apiservice.project.model.UpdateProjectRequest;

import java.util.UUID;

Expand All @@ -10,4 +11,6 @@ public interface ProjectsFacade {
CreateProjectResponse createProject(CreateProjectRequest request, UUID clientId);

CreateProjectResponse getProject(String projectKey);

void updateProject(String projectKey, UpdateProjectRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.opendevstack.apiservice.project.mapper.ProjectMapper;
import org.opendevstack.apiservice.project.model.CreateProjectRequest;
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
import org.opendevstack.apiservice.project.model.UpdateProjectRequest;
import org.opendevstack.apiservice.project.service.ClientAppService;
import org.opendevstack.apiservice.serviceproject.model.ProjectRequest;
import org.opendevstack.apiservice.serviceproject.model.ProjectResponse;
Expand Down Expand Up @@ -84,6 +85,11 @@ public CreateProjectResponse getProject(String projectKey) {
return projectMapper.toApiResponse(projectService.getProject(projectKey));
}

@Override
public void updateProject(String projectKey, UpdateProjectRequest request) {
projectService.updateProjectStatus(projectKey, request.getStatus());
}

private ProjectResponse registerProject(ProjectCreationCommand command) {

ProjectRequest projectRequest = projectMapper.toServiceRequest(command);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package org.opendevstack.apiservice.project.validation;

import org.opendevstack.apiservice.project.exception.ErrorKey;
import org.opendevstack.apiservice.project.exception.ProjectUpdateValidationException;
import org.opendevstack.apiservice.project.exception.ProjectValidationException;
import org.opendevstack.apiservice.project.model.CreateProjectRequest;
import org.opendevstack.apiservice.project.model.UpdateProjectRequest;
import org.opendevstack.apiservice.serviceproject.model.Status;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
Expand All @@ -14,11 +18,15 @@ public class ProjectRequestValidator {
@Value("${apis.projects.locations}")
private List<String> locations;

public void validate(CreateProjectRequest request) {
public void validateCreateRequest(CreateProjectRequest request) {
validateFlavorOrConfigItem(request);
validateOwnerIfX2AccountIsPresent(request);
validateLocation(request);
}

public void validateUpdateRequest(UpdateProjectRequest request) {
validateStatus(request);
}

private void validateFlavorOrConfigItem(CreateProjectRequest request) {
String projectFlavor = request.getProjectFlavor();
Expand Down Expand Up @@ -60,4 +68,20 @@ private void validateLocation(CreateProjectRequest request) {
String.join(", ", locations));
}
}

private void validateStatus(UpdateProjectRequest request) {
String status = request.getStatus();

if (status == null || status.trim().isEmpty()) {
return;
}

List<String> statusList = Arrays.stream(Status.values())
.map(e -> e.getDbValue())
.toList();
if (!statusList.contains(status)) {
throw new ProjectUpdateValidationException(ErrorKey.INVALID_STATUS,
String.join(", ", statusList));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ void create_project_returns_ok_when_creation_succeeds() {
assertThat(result.getBody().getError()).isNull();
assertThat(result.getBody().getErrorKey()).isEqualTo("000");
assertThat(result.getBody().getErrorDescription()).isNull();
verify(projectRequestValidator).validate(request);
verify(projectRequestValidator).validateCreateRequest(request);
verify(projectsFacade).createProject(any(CreateProjectRequest.class), any(UUID.class));
}

Expand Down
Loading
Loading