Skip to content

Commit c273bbe

Browse files
authored
Feature/create project v0 (#14)
1 parent d2bd7be commit c273bbe

45 files changed

Lines changed: 1399 additions & 397 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api-project/openapi/api-project.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,21 @@ components:
138138
maxLength: 255
139139
projectFlavor:
140140
type: string
141-
description: Flavor of the project. Either projectFlavor or configurationItem must be provided.
141+
description: Project flavor. Must be provided if configurationItem is not present. If both projectFlavor and configurationItem are missing, error BAD_REQUEST_FLAVOR_CONFIG_ITEM (023) is returned.
142142
configurationItem:
143143
type: string
144-
description: Configuration item for the project. Either projectFlavor or configurationItem must be provided.
144+
description: Configuration item. Must be present if projectFlavor is not provided. If both projectFlavor and configurationItem are missing, error BAD_REQUEST_FLAVOR_CONFIG_ITEM (023) is returned.
145145
location:
146146
type: string
147-
description: Location of the project.
147+
description: Location of the project. Must be one of the allowed locations. If not in the allowed list, error INVALID_LOCATION (011) is returned.
148148
x2OdsAccount:
149149
type: string
150150
description: Technical account of the project.
151+
pattern: "^x2[a-zA-Z0-9]{0,13}$"
151152
owner:
152153
type: string
153-
description: Owner of the project.
154+
description: Owner of the project. If x2OdsAccount is provided but owner is missing, error MANDATORY_OWNER (024) is returned.
155+
pattern: "^[a-z]{1,10}$"
154156
oneOf:
155157
- required: [projectFlavor]
156158
- required: [configurationItem]

api-project/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
<groupId>org.springframework.boot</groupId>
3434
<artifactId>spring-boot-starter-validation</artifactId>
3535
</dependency>
36+
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-devtools</artifactId>
40+
<scope>runtime</scope>
41+
<optional>true</optional>
42+
</dependency>
3643

3744
<dependency>
3845
<groupId>org.springdoc</groupId>
@@ -44,6 +51,12 @@
4451
<artifactId>service-projects</artifactId>
4552
<version>${project.version}</version>
4653
</dependency>
54+
55+
<dependency>
56+
<groupId>org.opendevstack.apiservice</groupId>
57+
<artifactId>persistence</artifactId>
58+
<version>${project.version}</version>
59+
</dependency>
4760

4861
<dependency>
4962
<groupId>io.jsonwebtoken</groupId>

api-project/src/main/java/org/opendevstack/apiservice/project/controller/ProjectController.java

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
import lombok.AllArgsConstructor;
55
import lombok.extern.slf4j.Slf4j;
66
import org.opendevstack.apiservice.project.api.ProjectsApi;
7-
import org.opendevstack.apiservice.project.exception.ProjectCreationException;
87
import org.opendevstack.apiservice.project.facade.ProjectsFacade;
98
import org.opendevstack.apiservice.project.model.CreateProjectRequest;
109
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
11-
import org.opendevstack.apiservice.project.exception.ProjectKeyGenerationException;
1210
import org.opendevstack.apiservice.project.validation.ProjectRequestValidator;
1311
import org.springframework.http.HttpStatus;
1412
import org.springframework.http.ResponseEntity;
@@ -19,6 +17,8 @@
1917
import org.springframework.web.bind.annotation.RequestMapping;
2018
import org.springframework.web.bind.annotation.RestController;
2119

20+
import java.util.UUID;
21+
2222
@RestController
2323
@RequestMapping(ProjectController.API_BASE_PATH)
2424
@AllArgsConstructor
@@ -37,44 +37,27 @@ public class ProjectController implements ProjectsApi {
3737
@Override
3838
public ResponseEntity<CreateProjectResponse> createProject(@Valid @RequestBody CreateProjectRequest createProjectRequest) {
3939
projectRequestValidator.validate(createProjectRequest);
40-
try {
41-
return ResponseEntity
42-
.status(HttpStatus.OK)
43-
.header(HTTP_HEADER_LOCATION, API_BASE_PATH)
44-
.body(projectsFacade.createProject(createProjectRequest));
45-
} catch (ProjectCreationException e) {
46-
log.error("Project creation conflict: {}", e.getMessage());
47-
return ResponseEntity.status(HttpStatus.CONFLICT)
48-
.header(HTTP_HEADER_LOCATION, API_BASE_PATH)
49-
.body(ProjectResponseFactory.conflict(e.getMessage(), API_BASE_PATH));
50-
} catch (ProjectKeyGenerationException e) {
51-
log.error("Failed to generate project key: {}", e.getMessage(), e);
52-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
53-
.header(HTTP_HEADER_LOCATION, API_BASE_PATH)
54-
.body(ProjectResponseFactory.projectKeyGenerationFailed(API_BASE_PATH));
55-
}
40+
UUID clientId = UUID.fromString("00000000-0000-0000-0000-000000000001");
41+
CreateProjectResponse projectResponse = projectsFacade.createProject(createProjectRequest, clientId);
42+
projectResponse.setLocation(API_BASE_PATH + "/" + projectResponse.getProjectKey());
43+
return ResponseEntity
44+
.status(HttpStatus.OK)
45+
.header(HTTP_HEADER_LOCATION, API_BASE_PATH)
46+
.body(projectResponse);
5647
}
5748

5849
@GetMapping("/{projectKey}")
5950
@Override
6051
public ResponseEntity<CreateProjectResponse> getProject(@PathVariable String projectKey) {
6152
String location = API_BASE_PATH + "/" + projectKey;
62-
try {
63-
CreateProjectResponse response = projectsFacade.getProject(projectKey);
64-
if (response == null) {
65-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
66-
.header(HTTP_HEADER_LOCATION, location)
67-
.body(ProjectResponseFactory.notFound(projectKey, location));
68-
}
69-
return ResponseEntity
70-
.status(HttpStatus.OK)
71-
.header(HTTP_HEADER_LOCATION, location)
72-
.body(response);
73-
} catch (Exception e) {
74-
log.error("Unexpected error retrieving project '{}': {}", projectKey, e.getMessage(), e);
75-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
53+
CreateProjectResponse response = projectsFacade.getProject(projectKey);
54+
if (response == null) {
55+
return ResponseEntity.status(HttpStatus.NOT_FOUND)
7656
.header(HTTP_HEADER_LOCATION, location)
77-
.body(ProjectResponseFactory.internalError(location));
57+
.body(ProjectResponseFactory.notFound(projectKey, location));
7858
}
59+
return ResponseEntity.status(HttpStatus.OK)
60+
.header(HTTP_HEADER_LOCATION, location)
61+
.body(response);
7962
}
8063
}

api-project/src/main/java/org/opendevstack/apiservice/project/controller/ProjectResponseFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ public static CreateProjectResponse internalError(String location) {
3939
location);
4040
}
4141

42+
public static CreateProjectResponse internalError(String location, String message) {
43+
return error(
44+
ErrorKey.INTERNAL_ERROR.getMessage(),
45+
ErrorKey.INTERNAL_ERROR.getKey(),
46+
message,
47+
location);
48+
}
49+
4250
private static CreateProjectResponse error(String error, String errorKey, String message, String location) {
4351
CreateProjectResponse response = new CreateProjectResponse();
4452
response.setError(error);

api-project/src/main/java/org/opendevstack/apiservice/project/controller/advice/ProjectExceptionHandler.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.opendevstack.apiservice.project.controller.advice;
22

33
import lombok.extern.slf4j.Slf4j;
4+
import org.opendevstack.apiservice.externalservice.aap.exception.AutomationPlatformException;
45
import org.opendevstack.apiservice.project.controller.ProjectController;
6+
import org.opendevstack.apiservice.project.exception.ClientAppNotRegisteredException;
57
import org.opendevstack.apiservice.project.exception.ErrorKey;
8+
import org.opendevstack.apiservice.project.exception.ProjectCreationException;
69
import org.opendevstack.apiservice.project.exception.ProjectValidationException;
710
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
811
import org.springframework.http.HttpStatus;
@@ -23,7 +26,9 @@ public class ProjectExceptionHandler {
2326
"projectName", ErrorKey.PROJECT_NAME_INVALID_FORMAT,
2427
"projectDescription", ErrorKey.PROJECT_DESCRIPTION_INVALID_FORMAT,
2528
"projectFlavor", ErrorKey.BAD_REQUEST_FLAVOR_CONFIG_ITEM,
26-
"configurationItem", ErrorKey.BAD_REQUEST_FLAVOR_CONFIG_ITEM
29+
"configurationItem", ErrorKey.BAD_REQUEST_FLAVOR_CONFIG_ITEM,
30+
"x2OdsAccount", ErrorKey.PROJECT_X2ACCOUNT_INVALID_FORMAT,
31+
"owner", ErrorKey.PROJECT_OWNER_INVALID_FORMAT
2732
);
2833

2934
@ExceptionHandler(MethodArgumentNotValidException.class)
@@ -55,6 +60,18 @@ public ResponseEntity<CreateProjectResponse> handleMethodArgumentNotValidExcepti
5560
return ResponseEntity.badRequest().body(response);
5661
}
5762

63+
@ExceptionHandler(ClientAppNotRegisteredException.class)
64+
public ResponseEntity<CreateProjectResponse> handleClientAppNotRegisteredException(
65+
ClientAppNotRegisteredException ex) {
66+
log.warn("ClientApp registration error: {}", ex.getMessage());
67+
CreateProjectResponse response = new CreateProjectResponse();
68+
response.setLocation(ProjectController.API_BASE_PATH);
69+
response.setError(HttpStatus.FORBIDDEN.getReasonPhrase());
70+
response.setErrorKey(ErrorKey.CLIENT_APP_NOT_REGISTERED.getKey());
71+
response.setMessage(ErrorKey.CLIENT_APP_NOT_REGISTERED.getMessage());
72+
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
73+
}
74+
5875
@ExceptionHandler(ProjectValidationException.class)
5976
public ResponseEntity<CreateProjectResponse> handleValidationException(ProjectValidationException ex) {
6077
log.warn("Validation error: {}", ex.getMessage());
@@ -66,6 +83,41 @@ public ResponseEntity<CreateProjectResponse> handleValidationException(ProjectVa
6683
response.setMessage(errorKey.getMessage());
6784
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
6885
}
86+
87+
@ExceptionHandler(ProjectCreationException.class)
88+
public ResponseEntity<CreateProjectResponse> handleProjectCreationException(
89+
ProjectCreationException ex) {
90+
log.error("Project creation error: {}", ex.getMessage(), ex);
91+
CreateProjectResponse response = new CreateProjectResponse();
92+
response.setLocation(ProjectController.API_BASE_PATH);
93+
response.setError(ErrorKey.INTERNAL_ERROR.getMessage());
94+
response.setErrorKey(ErrorKey.INTERNAL_ERROR.getKey());
95+
response.setMessage(ex.getMessage());
96+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
97+
}
98+
99+
@ExceptionHandler(AutomationPlatformException.class)
100+
public ResponseEntity<CreateProjectResponse> handleAutomationPlatformException(
101+
AutomationPlatformException ex) {
102+
log.error("Failed to execute automated job: {}", ex.getMessage(), ex);
103+
CreateProjectResponse response = new CreateProjectResponse();
104+
response.setLocation(ProjectController.API_BASE_PATH);
105+
response.setError(ErrorKey.INTERNAL_ERROR.getMessage());
106+
response.setErrorKey(ErrorKey.INTERNAL_ERROR.getKey());
107+
response.setMessage(ex.getMessage());
108+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
109+
}
110+
111+
@ExceptionHandler(Exception.class)
112+
public ResponseEntity<CreateProjectResponse> handleGenericException(Exception ex) {
113+
log.error("Unexpected error: {}", ex.getMessage(), ex);
114+
CreateProjectResponse response = new CreateProjectResponse();
115+
response.setLocation(ProjectController.API_BASE_PATH);
116+
response.setError(ErrorKey.INTERNAL_ERROR.getMessage());
117+
response.setErrorKey(ErrorKey.INTERNAL_ERROR.getKey());
118+
response.setMessage("An error occurred while processing the request.");
119+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
120+
}
69121
}
70122

71123

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.opendevstack.apiservice.project.exception;
2+
3+
public class ClientAppNotRegisteredException extends RuntimeException {
4+
5+
public ClientAppNotRegisteredException(String clientId) {
6+
super(String.format("ClientApp with clientId '%s' is not registered", clientId));
7+
}
8+
}

api-project/src/main/java/org/opendevstack/apiservice/project/exception/ErrorKey.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ public enum ErrorKey {
2828
BAD_REQUEST_FLAVOR_CONFIG_ITEM("023", "Project flavour and config item cannot be both null"),
2929
MANDATORY_OWNER("024", "Owner must be present if the X2 account is present"),
3030
PROJECT_ALREADY_EXISTS("025", "Project already exists"),
31-
PROJECT_SAME_PROJECT_NAME_ALREADY_EXISTS("026", "Project with same project name already exists");
31+
PROJECT_SAME_PROJECT_NAME_ALREADY_EXISTS("026", "Project with same project name already exists"),
32+
CLIENT_APP_NOT_REGISTERED("027", "ClientApp not registered, manual registration required"),
33+
INVALID_PROJECT_FLAVOR("028", ErrorMessage.BAD_REQUEST),
34+
INVALID_CONFIG_ITEM("029", ErrorMessage.BAD_REQUEST);
3235

3336
private String key;
3437
private String message;

api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectCreationException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.opendevstack.apiservice.project.exception;
22

3-
public class ProjectCreationException extends Exception {
3+
public class ProjectCreationException extends RuntimeException {
44

55
public ProjectCreationException(String message) {
66
super(message);

api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectKeyGenerationException.java

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package org.opendevstack.apiservice.project.facade;
22

3-
import org.opendevstack.apiservice.project.exception.ProjectCreationException;
4-
import org.opendevstack.apiservice.project.exception.ProjectKeyGenerationException;
53
import org.opendevstack.apiservice.project.model.CreateProjectRequest;
64
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
75

6+
import java.util.UUID;
7+
88
public interface ProjectsFacade {
99

10-
CreateProjectResponse createProject(CreateProjectRequest request)
11-
throws ProjectCreationException, ProjectKeyGenerationException;
10+
CreateProjectResponse createProject(CreateProjectRequest request, UUID clientId);
1211

1312
CreateProjectResponse getProject(String projectKey);
1413
}

0 commit comments

Comments
 (0)