From 8c275ab443b01ae986412ce5b9c91b24cf8119b9 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 11:03:47 +0000 Subject: [PATCH] feat: Implement create project route (SCRUM-7) (!11) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/11 Reviewed-by: Jan Gleytenhoover --- GetBearerToken.http | 4 +- build.gradle.kts | 1 + gradlew.bat | 188 +++++++++--------- requests/createProject.http | 15 ++ .../GlobalExceptionHandler.java | 23 +++ .../project/CreateProjectAction.java | 40 ++++ .../lf8_starter/project/ProjectEntity.java | 2 + .../lf8_starter/project/ProjectMapper.java | 40 ++++ .../lf8_starter/project/ProjectService.java | 16 ++ .../project/dto/CreateProjectDto.java | 42 ++++ .../project/dto/GetProjectDto.java | 43 ++++ 11 files changed, 319 insertions(+), 95 deletions(-) create mode 100644 requests/createProject.http create mode 100644 src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java create mode 100644 src/main/java/de/szut/lf8_starter/project/ProjectMapper.java create mode 100644 src/main/java/de/szut/lf8_starter/project/ProjectService.java create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java diff --git a/GetBearerToken.http b/GetBearerToken.http index d198851..f5209ca 100644 --- a/GetBearerToken.http +++ b/GetBearerToken.http @@ -1,4 +1,6 @@ POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded -grant_type=password&client_id=employee-management-service&username=user&password=test \ No newline at end of file +grant_type=password&client_id=employee-management-service&username=user&password=test + +> {% client.global.set("auth_token", response.body.access_token); %} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 8aa3eba..9c30a46 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,6 +44,7 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-validation") compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/gradlew.bat b/gradlew.bat index 9b42019..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/requests/createProject.http b/requests/createProject.http new file mode 100644 index 0000000..add2394 --- /dev/null +++ b/requests/createProject.http @@ -0,0 +1,15 @@ +### GET request to example server +POST http://localhost:8080/projects +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractorName": "Peter File", + "comment": "goal of project", + "startDate": "01.01.2000", + "plannedEndDate": "01.01.2001" +} \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index 828331b..68a15a7 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -3,8 +3,10 @@ package de.szut.lf8_starter.exceptionHandling; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.ConstraintViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; @@ -24,5 +26,26 @@ public class GlobalExceptionHandler { return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); } + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { + String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); + + ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } } diff --git a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java new file mode 100644 index 0000000..c41b134 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java @@ -0,0 +1,40 @@ +package de.szut.lf8_starter.project; + +import de.szut.lf8_starter.project.dto.CreateProjectDto; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import io.swagger.v3.oas.annotations.Operation; +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 io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/projects") +public class CreateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Creates a new Project") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @PostMapping + public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + + projectEntity = this.projectService.create(projectEntity); + + return this.projectMapper.mapToGetDto(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java index 6596350..d9c35a1 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java @@ -37,5 +37,7 @@ public class ProjectEntity { @CreatedDate private LocalDate startDate; + private LocalDate plannedEndDate; + private LocalDate endDate; } diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java b/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java new file mode 100644 index 0000000..6567749 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java @@ -0,0 +1,40 @@ +package de.szut.lf8_starter.project; + +import de.szut.lf8_starter.project.dto.CreateProjectDto; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import org.springframework.stereotype.Service; + +@Service +public class ProjectMapper { + public ProjectEntity mapCreateDtoToEntity(CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = new ProjectEntity(); + + projectEntity.setName(createProjectDto.getName()); + projectEntity.setComment(createProjectDto.getComment()); + projectEntity.setLeadingEmployee(createProjectDto.getLeadingEmployee()); + projectEntity.setEmployees(createProjectDto.getEmployees()); + projectEntity.setContractor(createProjectDto.getContractor()); + projectEntity.setContractorName(createProjectDto.getContractorName()); + projectEntity.setStartDate(createProjectDto.getStartDate()); + projectEntity.setPlannedEndDate(createProjectDto.getPlannedEndDate()); + projectEntity.setEndDate(createProjectDto.getEndDate()); + + return projectEntity; + } + + public GetProjectDto mapToGetDto(ProjectEntity projectEntity) { + GetProjectDto getProjectDto = new GetProjectDto(); + + getProjectDto.setName(projectEntity.getName()); + getProjectDto.setComment(projectEntity.getComment()); + getProjectDto.setLeadingEmployee(projectEntity.getLeadingEmployee()); + getProjectDto.setEmployees(projectEntity.getEmployees()); + getProjectDto.setContractor(projectEntity.getContractor()); + getProjectDto.setContractorName(projectEntity.getContractorName()); + getProjectDto.setStartDate(projectEntity.getStartDate()); + getProjectDto.setPlannedEndDate(projectEntity.getPlannedEndDate()); + getProjectDto.setEndDate(projectEntity.getEndDate()); + + return getProjectDto; + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectService.java b/src/main/java/de/szut/lf8_starter/project/ProjectService.java new file mode 100644 index 0000000..a12228f --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/ProjectService.java @@ -0,0 +1,16 @@ +package de.szut.lf8_starter.project; + +import org.springframework.stereotype.Service; + +@Service +public class ProjectService { + private final ProjectRepository projectRepository; + + public ProjectService(ProjectRepository projectRepository) { + this.projectRepository = projectRepository; + } + + public ProjectEntity create(ProjectEntity projectEntity) { + return this.projectRepository.save(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java new file mode 100644 index 0000000..4c6ab0d --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -0,0 +1,42 @@ +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class CreateProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java new file mode 100644 index 0000000..cc337c7 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java @@ -0,0 +1,43 @@ +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class GetProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +}