Merge pull request 'borschevskaya_anna_lab_3 is ready' (#38) from borschevskaya_anna_lab_3 into main
Reviewed-on: #38
This commit is contained in:
commit
2f46c05849
58
borschevskaya_anna_lab_3/README.md
Normal file
58
borschevskaya_anna_lab_3/README.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Отчет. Лабораторная работа 3
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
В рамках лабораторной работы № 3 были реализованы два сервиса (Java + Spring), осуществляющие CRUD-операции над сущностями.
|
||||||
|
|
||||||
|
Модель данных следующая:
|
||||||
|
|
||||||
|
Сущность "Компания" (сервис company)
|
||||||
|
- идентификатор компании
|
||||||
|
- название
|
||||||
|
- адрес
|
||||||
|
|
||||||
|
Сущность "Вакансия" (сервис vacancy)
|
||||||
|
- идентификатор вакансии
|
||||||
|
- название
|
||||||
|
- описание
|
||||||
|
- нижняя граница зарплаты
|
||||||
|
- верхняя граница зарплаты
|
||||||
|
- идентификатор компании
|
||||||
|
|
||||||
|
Компания с вакансией связана как "один ко многим".
|
||||||
|
|
||||||
|
Каждый из сервисов имеет API с пятью эндпоинтами. При этом в сервисе vacancy при запросе вакансии по id происходит
|
||||||
|
дополнительный запрос в сервис company для получения информации по компании, связанной с вакансией.
|
||||||
|
Происходит это взаимодействие с помощью библиотеки OpenFeign, которая "под капотом" использует HttpClient.
|
||||||
|
|
||||||
|
В качестве хранилища данных использовалась СУБД Postgres. У каждого сервиса своя база данных в поднятой СУБД.
|
||||||
|
Для создания схемы БД была использована библиотека Flyway, которая применила написанные миграции при старте приложения.
|
||||||
|
|
||||||
|
Запросы к сервисам проксирует шлюз на основе Nginx. Для этого перед запуском nginx был описан конфигурационный файл nginx.conf,
|
||||||
|
в котором описан прослушиваемый порт и название сервера (в блоке server), маршруты до микросервисов и параметры проксирования (в блоке location).
|
||||||
|
|
||||||
|
Таким образом, с помощью Docker Compose были подняты сервисы:
|
||||||
|
- company
|
||||||
|
- vacancy
|
||||||
|
- postgres
|
||||||
|
- nginx
|
||||||
|
## Как запустить
|
||||||
|
Для того, чтобы запустить сервисы, необходимо выполнить следующие действия:
|
||||||
|
1. Установить и запустить Docker Engine или Docker Desktop
|
||||||
|
2. Через консоль перейти в папку, в которой расположен файл docker-compose.yml
|
||||||
|
3. Выполнить команду:
|
||||||
|
```
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
В случае успешного запуска всех контейнеров в консоли будет выведено следующее сообщение:
|
||||||
|
```
|
||||||
|
[+] Running 5/5
|
||||||
|
✔ Network borschevskaya_anna_lab_3_default Created 0.0s
|
||||||
|
✔ Container postgres Started 0.6s
|
||||||
|
✔ Container vacancy Started 1.1s
|
||||||
|
✔ Container company Started 0.9s
|
||||||
|
✔ Container borschevskaya_anna_lab_3-nginx-1 Started
|
||||||
|
```
|
||||||
|
Далее можно осуществлять запросы к сервисам по адресу http://localhost/{location}, где часть пути location меняется в зависимости от сервиса и запроса.
|
||||||
|
## Видео-отчет
|
||||||
|
Работоспособность лабораторной работы можно оценить в следующем [видео](https://disk.yandex.ru/i/KPNBfnlcgl1auw).
|
||||||
|
Демонстрация взаимодействия с системой производится с применением сервиса Postman.
|
38
borschevskaya_anna_lab_3/company-service/.gitignore
vendored
Normal file
38
borschevskaya_anna_lab_3/company-service/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
21
borschevskaya_anna_lab_3/company-service/Dockerfile
Normal file
21
borschevskaya_anna_lab_3/company-service/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Используем образ Maven для сборки
|
||||||
|
FROM maven:3.8-eclipse-temurin-21-alpine AS build
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем остальные исходные файлы
|
||||||
|
COPY pom.xml .
|
||||||
|
COPY src src
|
||||||
|
|
||||||
|
# Собираем весь проект
|
||||||
|
RUN mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# Используем официальный образ JDK для запуска собранного jar-файла
|
||||||
|
FROM eclipse-temurin:21-jdk-alpine
|
||||||
|
|
||||||
|
# Копируем jar-файл из предыдущего этапа
|
||||||
|
COPY --from=build /app/target/*.jar /app.jar
|
||||||
|
|
||||||
|
# Указываем команду для запуска приложения
|
||||||
|
CMD ["java", "-jar", "app.jar"]
|
66
borschevskaya_anna_lab_3/company-service/pom.xml
Normal file
66
borschevskaya_anna_lab_3/company-service/pom.xml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>ru.somecompany</groupId>
|
||||||
|
<artifactId>company-service</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.3</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-annotations</artifactId>
|
||||||
|
<version>2.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,12 @@
|
|||||||
|
package ru.somecompany;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class CompanyApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(CompanyApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package ru.somecompany.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
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.RequestBody;
|
||||||
|
import ru.somecompany.domain.CompanyEntity;
|
||||||
|
import ru.somecompany.domain.CreateCompanyRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
||||||
|
@Validated
|
||||||
|
@Tag(name = "company", description = "API для управления компаниями")
|
||||||
|
public interface CompanyController {
|
||||||
|
|
||||||
|
@Operation(summary = "Получение всех компаний")
|
||||||
|
@GetMapping(value = "/api/v1/company")
|
||||||
|
List<CompanyEntity> getCompanies();
|
||||||
|
|
||||||
|
@Operation(summary = "Создание компании")
|
||||||
|
@PostMapping(value = "/api/v1/company")
|
||||||
|
CompanyEntity createCompany(@RequestBody @NotNull CreateCompanyRequest companyRequest);
|
||||||
|
|
||||||
|
@Operation(summary = "Получение информации о компании по id")
|
||||||
|
@GetMapping(value = "/api/v1/company/{companyId}")
|
||||||
|
CompanyEntity getCompany(@PathVariable UUID companyId);
|
||||||
|
|
||||||
|
@Operation(summary = "Редактирование компании")
|
||||||
|
@PutMapping(value = "/api/v1/company/{companyId}")
|
||||||
|
CompanyEntity updateCompany(@PathVariable UUID companyId,
|
||||||
|
@RequestBody @NotNull CreateCompanyRequest companyRequest);
|
||||||
|
|
||||||
|
@Operation(summary = "Удалении компании по id")
|
||||||
|
@DeleteMapping(value = "/api/v1/company/{companyId}")
|
||||||
|
void deleteCompany(@PathVariable UUID companyId);
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package ru.somecompany.controller;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import ru.somecompany.domain.CompanyEntity;
|
||||||
|
import ru.somecompany.domain.CreateCompanyRequest;
|
||||||
|
import ru.somecompany.service.CompanyService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CompanyControllerImpl implements CompanyController {
|
||||||
|
|
||||||
|
private final CompanyService companyService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CompanyEntity> getCompanies() {
|
||||||
|
return companyService.getCompanies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompanyEntity createCompany(CreateCompanyRequest companyRequest) {
|
||||||
|
return companyService.createCompany(companyRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompanyEntity getCompany(UUID companyId) {
|
||||||
|
return companyService.getCompany(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompanyEntity updateCompany(UUID companyId, CreateCompanyRequest companyRequest) {
|
||||||
|
return companyService.updateCompany(companyId, companyRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteCompany(UUID companyId) {
|
||||||
|
companyService.deleteCompany(companyId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package ru.somecompany.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Builder
|
||||||
|
@Table(name = "company")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CompanyEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(name = "name", unique = true, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "address", unique = true, nullable = false)
|
||||||
|
private String address;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.somecompany.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CreateCompanyRequest {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String address;
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractWebExceptionHandler {
|
||||||
|
|
||||||
|
public static ResponseEntity<ResponseError> buildResponse(ErrorCode errorCode, HttpStatus status, String msg,
|
||||||
|
boolean details, Throwable ex) {
|
||||||
|
return buildResponse(errorCode.name(), status, msg, details, ex, LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseEntity<ResponseError> buildResponse(String errorCode, HttpStatus status, String msg,
|
||||||
|
boolean details, Throwable ex, LocalDateTime timestamp) {
|
||||||
|
if (details) {
|
||||||
|
log.error(msg, ex); // with stack-trace
|
||||||
|
} else {
|
||||||
|
var message = StringUtils.equals(msg, ex.getMessage()) ? msg : msg + ": " + ex.getMessage();
|
||||||
|
if (status == NOT_FOUND) {
|
||||||
|
log.warn(message);
|
||||||
|
} else {
|
||||||
|
log.error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.status(status.value())
|
||||||
|
.body(ResponseError.builder()
|
||||||
|
.code(errorCode)
|
||||||
|
.message(msg)
|
||||||
|
.timestamp(timestamp)
|
||||||
|
.status(status.value())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
public enum BasicError implements ErrorCode {
|
||||||
|
NOT_FOUND
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class CompanyAppRuntimeException extends RuntimeException {
|
||||||
|
private final ErrorCode errorCode;
|
||||||
|
private final HttpStatus status;
|
||||||
|
private final boolean details;
|
||||||
|
|
||||||
|
public CompanyAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message) {
|
||||||
|
this(errorCode, status, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompanyAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, Throwable error) {
|
||||||
|
this(errorCode, status, message, true, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompanyAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, boolean details, Throwable error) {
|
||||||
|
super(message, error);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.status = status;
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public interface ErrorCode extends Serializable {
|
||||||
|
|
||||||
|
String name();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import static ru.somecompany.exception.BasicError.NOT_FOUND;
|
||||||
|
|
||||||
|
public class ResourceNotFoundException extends CompanyAppRuntimeException {
|
||||||
|
|
||||||
|
public ResourceNotFoundException(ErrorCode errorCode, String message) {
|
||||||
|
this(errorCode, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceNotFoundException(ErrorCode errorCode, String message, Throwable error) {
|
||||||
|
super(errorCode, HttpStatus.NOT_FOUND, message, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceNotFoundException notFound(String message, Object... args) {
|
||||||
|
return new ResourceNotFoundException(NOT_FOUND, String.format(message, args), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceNotFoundException notFound() {
|
||||||
|
return new ResourceNotFoundException(NOT_FOUND, "Запрашиваемые данные не найдены", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceNotFoundException notFound(Throwable error) {
|
||||||
|
return new ResourceNotFoundException(NOT_FOUND, error.getMessage(), error);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ResponseError {
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private Integer status;
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private String message;
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private String code;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@RestControllerAdvice
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebExceptionHandler extends AbstractWebExceptionHandler {
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@ExceptionHandler(CompanyAppRuntimeException.class)
|
||||||
|
public ResponseEntity<ResponseError> handleException(CompanyAppRuntimeException ex) {
|
||||||
|
return buildResponse(ex.getErrorCode(), ex.getStatus(), ex.getMessage(), ex.isDetails(), ex);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package ru.somecompany.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import ru.somecompany.domain.CompanyEntity;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CompanyRepository extends JpaRepository<CompanyEntity, UUID> {
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package ru.somecompany.service;
|
||||||
|
|
||||||
|
import ru.somecompany.domain.CompanyEntity;
|
||||||
|
import ru.somecompany.domain.CreateCompanyRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CompanyService {
|
||||||
|
|
||||||
|
List<CompanyEntity> getCompanies();
|
||||||
|
|
||||||
|
CompanyEntity createCompany(CreateCompanyRequest companyRequest);
|
||||||
|
|
||||||
|
CompanyEntity updateCompany(UUID companyId, CreateCompanyRequest companyRequest);
|
||||||
|
|
||||||
|
void deleteCompany(UUID companyId);
|
||||||
|
|
||||||
|
CompanyEntity getCompany(UUID companyId);
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package ru.somecompany.service;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.somecompany.domain.CompanyEntity;
|
||||||
|
import ru.somecompany.domain.CreateCompanyRequest;
|
||||||
|
import ru.somecompany.repository.CompanyRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static ru.somecompany.exception.ResourceNotFoundException.notFound;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CompanyServiceImpl implements CompanyService {
|
||||||
|
|
||||||
|
private final CompanyRepository companyRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CompanyEntity> getCompanies() {
|
||||||
|
return companyRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompanyEntity createCompany(CreateCompanyRequest companyRequest) {
|
||||||
|
var entity = CompanyEntity.builder()
|
||||||
|
.name(companyRequest.getName())
|
||||||
|
.address(companyRequest.getAddress())
|
||||||
|
.build();
|
||||||
|
return companyRepository.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompanyEntity updateCompany(UUID companyId, CreateCompanyRequest companyRequest) {
|
||||||
|
var company = getById(companyId);
|
||||||
|
company.setName(companyRequest.getName());
|
||||||
|
company.setAddress(companyRequest.getAddress());
|
||||||
|
return companyRepository.save(company);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteCompany(UUID companyId) {
|
||||||
|
var company = getById(companyId);
|
||||||
|
companyRepository.delete(company);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompanyEntity getCompany(UUID companyId) {
|
||||||
|
return getById(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompanyEntity getById(UUID companyId) {
|
||||||
|
return companyRepository.findById(companyId)
|
||||||
|
.orElseThrow(() -> notFound("Компания не найдена"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
server:
|
||||||
|
port: ${SERVER_PORT:8080}
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: company-app
|
||||||
|
jpa:
|
||||||
|
database: POSTGRESQL
|
||||||
|
open-in-view: false
|
||||||
|
show-sql: false
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: none
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
datasource:
|
||||||
|
url: ${DB_URL:jdbc:postgresql://postgres:5433/company}
|
||||||
|
username: ${DB_USERNAME:postgres}
|
||||||
|
password: ${DB_PASSWORD:postgres}
|
||||||
|
driverClassName: org.postgresql.Driver
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
hikari:
|
||||||
|
maximum-pool-size: 15
|
||||||
|
minimum-idle: 4
|
||||||
|
idle-timeout: 180000
|
||||||
|
max-lifetime: 599000
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
locations: db/migration/
|
@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE company (
|
||||||
|
id UUID
|
||||||
|
CONSTRAINT company_pk PRIMARY KEY,
|
||||||
|
name VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
address VARCHAR(255) NOT NULL
|
||||||
|
);
|
43
borschevskaya_anna_lab_3/docker-compose.yml
Normal file
43
borschevskaya_anna_lab_3/docker-compose.yml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
container_name: postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USERNAME: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
PGDATA: "/var/lib/postgresql/data/pgdata"
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- ./postgres_data:/var/lib/postgresql/data/
|
||||||
|
- ./init-database.sh:/docker-entrypoint-initdb.d/init-database.sh
|
||||||
|
company:
|
||||||
|
build: ./company-service
|
||||||
|
container_name: company
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
environment:
|
||||||
|
SERVER_PORT: 8080
|
||||||
|
DB_URL: jdbc:postgresql://postgres:5432/company
|
||||||
|
DB_USERNAME: postgres
|
||||||
|
DB_PASSWORD: postgres
|
||||||
|
vacancy:
|
||||||
|
build: ./vacancy-service
|
||||||
|
container_name: vacancy
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
environment:
|
||||||
|
SERVER_PORT: 8080
|
||||||
|
DB_URL: jdbc:postgresql://postgres:5432/vacancy
|
||||||
|
DB_USERNAME: postgres
|
||||||
|
DB_PASSWORD: postgres
|
||||||
|
COMPANY_URL: http://nginx/
|
||||||
|
nginx:
|
||||||
|
image: nginx
|
||||||
|
depends_on:
|
||||||
|
- vacancy
|
||||||
|
- company
|
||||||
|
volumes:
|
||||||
|
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
ports:
|
||||||
|
- 80:80
|
8
borschevskaya_anna_lab_3/init-database.sh
Normal file
8
borschevskaya_anna_lab_3/init-database.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Создаем БД
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
|
CREATE DATABASE company;
|
||||||
|
CREATE DATABASE vacancy;
|
||||||
|
EOSQL
|
21
borschevskaya_anna_lab_3/nginx/nginx.conf
Normal file
21
borschevskaya_anna_lab_3/nginx/nginx.conf
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location /api/v1/company {
|
||||||
|
proxy_pass http://company:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/v1/vacancy {
|
||||||
|
proxy_pass http://vacancy:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
38
borschevskaya_anna_lab_3/vacancy-service/.gitignore
vendored
Normal file
38
borschevskaya_anna_lab_3/vacancy-service/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
21
borschevskaya_anna_lab_3/vacancy-service/Dockerfile
Normal file
21
borschevskaya_anna_lab_3/vacancy-service/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Используем образ Maven для сборки
|
||||||
|
FROM maven:3.8-eclipse-temurin-21-alpine AS build
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем остальные исходные файлы
|
||||||
|
COPY pom.xml .
|
||||||
|
COPY src src
|
||||||
|
|
||||||
|
# Собираем весь проект
|
||||||
|
RUN mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# Используем официальный образ JDK для запуска собранного jar-файла
|
||||||
|
FROM eclipse-temurin:21-jdk-alpine
|
||||||
|
|
||||||
|
# Копируем jar-файл из предыдущего этапа
|
||||||
|
COPY --from=build /app/target/*.jar /app.jar
|
||||||
|
|
||||||
|
# Указываем команду для запуска приложения
|
||||||
|
CMD ["java", "-jar", "app.jar"]
|
128
borschevskaya_anna_lab_3/vacancy-service/pom.xml
Normal file
128
borschevskaya_anna_lab_3/vacancy-service/pom.xml
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>ru.somecompany</groupId>
|
||||||
|
<artifactId>vacancy-service</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.3</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||||
|
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
|
||||||
|
<spring-cloud.version>2023.0.3</spring-cloud.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId>
|
||||||
|
<version>${mapstruct.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||||
|
<version>${lombok-mapstruct-binding.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-annotations</artifactId>
|
||||||
|
<version>2.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
|
<version>${spring-cloud.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>${mapstruct.version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||||
|
<version>${lombok-mapstruct-binding.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-Amapstruct.defaultComponentModel=spring</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,14 @@
|
|||||||
|
package ru.somecompany;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableFeignClients
|
||||||
|
public class VacancyApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(VacancyApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package ru.somecompany.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
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.RequestBody;
|
||||||
|
import ru.somecompany.domain.CreateVacancyRequest;
|
||||||
|
import ru.somecompany.domain.VacancyEntity;
|
||||||
|
import ru.somecompany.domain.VacancyResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Validated
|
||||||
|
@Tag(name = "vacancy", description = "API для управления вакансиями")
|
||||||
|
public interface VacancyController {
|
||||||
|
|
||||||
|
@Operation(summary = "Получение всех вакансий")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Данные успешно переданы"),
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/api/v1/vacancy")
|
||||||
|
List<VacancyEntity> getVacancies();
|
||||||
|
|
||||||
|
@Operation(summary = "Создание вакансии")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Вакансия успешно создана"),
|
||||||
|
})
|
||||||
|
@PostMapping(value = "/api/v1/vacancy")
|
||||||
|
VacancyEntity createVacancy(@RequestBody @NotNull CreateVacancyRequest request);
|
||||||
|
|
||||||
|
@Operation(summary = "Получение информации о вакансии по id")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Данные успешно переданы"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Вакансия не найдена")
|
||||||
|
})
|
||||||
|
@GetMapping(value = "/api/v1/vacancy/{vacancyId}")
|
||||||
|
VacancyResponse getVacancy(@PathVariable UUID vacancyId);
|
||||||
|
|
||||||
|
@Operation(summary = "Редактирование вакансии")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Данные вакансии успешно изменены"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Вакансия не найдена")
|
||||||
|
})
|
||||||
|
@PutMapping(value = "/api/v1/vacancy/{vacancyId}")
|
||||||
|
VacancyEntity updateVacancy(@PathVariable UUID vacancyId,
|
||||||
|
@RequestBody @NotNull CreateVacancyRequest request);
|
||||||
|
|
||||||
|
@Operation(summary = "Удалении вакансии по id")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Вакансия успешно удалена"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Вакансия не найдена")
|
||||||
|
})
|
||||||
|
@DeleteMapping(value = "/api/v1/vacancy/{vacancyId}")
|
||||||
|
void deleteVacancy(@PathVariable UUID vacancyId);
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package ru.somecompany.controller;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import ru.somecompany.domain.CreateVacancyRequest;
|
||||||
|
import ru.somecompany.domain.VacancyEntity;
|
||||||
|
import ru.somecompany.domain.VacancyResponse;
|
||||||
|
import ru.somecompany.service.VacancyService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class VacancyControllerImpl implements VacancyController {
|
||||||
|
|
||||||
|
private final VacancyService vacancyService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<VacancyEntity> getVacancies() {
|
||||||
|
return vacancyService.getVacancies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VacancyEntity createVacancy(CreateVacancyRequest request) {
|
||||||
|
return vacancyService.createVacancy(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VacancyResponse getVacancy(UUID vacancyId) {
|
||||||
|
return vacancyService.getVacancy(vacancyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VacancyEntity updateVacancy(UUID vacancyId, CreateVacancyRequest request) {
|
||||||
|
return vacancyService.updateVacancy(vacancyId, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteVacancy(UUID vacancyId) {
|
||||||
|
vacancyService.deleteVacancy(vacancyId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package ru.somecompany.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CompanyResponse {
|
||||||
|
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String address;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package ru.somecompany.domain;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CreateVacancyRequest {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private Integer salaryLow;
|
||||||
|
|
||||||
|
private Integer salaryHigh;
|
||||||
|
|
||||||
|
private UUID companyId;
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package ru.somecompany.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Builder
|
||||||
|
@Table(name = "vacancy")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class VacancyEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(name = "name", nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "description", nullable = true, length = 1000)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column(name = "salary_low", nullable = false)
|
||||||
|
private Integer salaryLow;
|
||||||
|
|
||||||
|
@Column(name = "salary_high", nullable = false)
|
||||||
|
private Integer salaryHigh;
|
||||||
|
|
||||||
|
@Column(name = "company_id", nullable = false)
|
||||||
|
private UUID companyId;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package ru.somecompany.domain;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "Данные по вакансии")
|
||||||
|
public class VacancyResponse {
|
||||||
|
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private Integer salaryLow;
|
||||||
|
|
||||||
|
private Integer salaryHigh;
|
||||||
|
|
||||||
|
private CompanyResponse company;
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractWebExceptionHandler {
|
||||||
|
|
||||||
|
public static ResponseEntity<ResponseError> buildResponse(ErrorCode errorCode, HttpStatus status, String msg,
|
||||||
|
boolean details, Throwable ex) {
|
||||||
|
return buildResponse(errorCode.name(), status, msg, details, ex, LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseEntity<ResponseError> buildResponse(String errorCode, HttpStatus status, String msg,
|
||||||
|
boolean details, Throwable ex, LocalDateTime timestamp) {
|
||||||
|
if (details) {
|
||||||
|
log.error(msg, ex); // with stack-trace
|
||||||
|
} else {
|
||||||
|
var message = StringUtils.equals(msg, ex.getMessage()) ? msg : msg + ": " + ex.getMessage();
|
||||||
|
if (status == NOT_FOUND) {
|
||||||
|
log.warn(message);
|
||||||
|
} else {
|
||||||
|
log.error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.status(status.value())
|
||||||
|
.body(ResponseError.builder()
|
||||||
|
.code(errorCode)
|
||||||
|
.message(msg)
|
||||||
|
.timestamp(timestamp)
|
||||||
|
.status(status.value())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
public enum BasicError implements ErrorCode {
|
||||||
|
NOT_FOUND
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class CompanyAppRuntimeException extends RuntimeException {
|
||||||
|
private final ErrorCode errorCode;
|
||||||
|
private final HttpStatus status;
|
||||||
|
private final boolean details;
|
||||||
|
|
||||||
|
public CompanyAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message) {
|
||||||
|
this(errorCode, status, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompanyAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, Throwable error) {
|
||||||
|
this(errorCode, status, message, true, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompanyAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, boolean details, Throwable error) {
|
||||||
|
super(message, error);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.status = status;
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public interface ErrorCode extends Serializable {
|
||||||
|
|
||||||
|
String name();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import static ru.somecompany.exception.BasicError.NOT_FOUND;
|
||||||
|
|
||||||
|
public class ResourceNotFoundException extends CompanyAppRuntimeException {
|
||||||
|
|
||||||
|
public ResourceNotFoundException(ErrorCode errorCode, String message) {
|
||||||
|
this(errorCode, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceNotFoundException(ErrorCode errorCode, String message, Throwable error) {
|
||||||
|
super(errorCode, HttpStatus.NOT_FOUND, message, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceNotFoundException notFound(String message, Object... args) {
|
||||||
|
return new ResourceNotFoundException(NOT_FOUND, String.format(message, args), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceNotFoundException notFound() {
|
||||||
|
return new ResourceNotFoundException(NOT_FOUND, "Запрашиваемые данные не найдены", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceNotFoundException notFound(Throwable error) {
|
||||||
|
return new ResourceNotFoundException(NOT_FOUND, error.getMessage(), error);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ResponseError {
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private Integer status;
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private String message;
|
||||||
|
@JsonProperty(required = true)
|
||||||
|
private String code;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package ru.somecompany.exception;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@RestControllerAdvice
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebExceptionHandler extends AbstractWebExceptionHandler {
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@ExceptionHandler(CompanyAppRuntimeException.class)
|
||||||
|
public ResponseEntity<ResponseError> handleException(CompanyAppRuntimeException ex) {
|
||||||
|
return buildResponse(ex.getErrorCode(), ex.getStatus(), ex.getMessage(), ex.isDetails(), ex);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package ru.somecompany.external;
|
||||||
|
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import ru.somecompany.domain.CompanyResponse;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@FeignClient(name = "company", url = "${app.feign.company-url}")
|
||||||
|
public interface CompanyClient {
|
||||||
|
|
||||||
|
@GetMapping(value = "/api/v1/company/{companyId}")
|
||||||
|
CompanyResponse getCompany(@PathVariable UUID companyId);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package ru.somecompany.mapper;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.MappingTarget;
|
||||||
|
import ru.somecompany.domain.CompanyResponse;
|
||||||
|
import ru.somecompany.domain.CreateVacancyRequest;
|
||||||
|
import ru.somecompany.domain.VacancyEntity;
|
||||||
|
import ru.somecompany.domain.VacancyResponse;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Mapper(imports = UUID.class)
|
||||||
|
public interface VacancyMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "company", source = "company")
|
||||||
|
@Mapping(target = "name", source = "entity.name")
|
||||||
|
@Mapping(target = "id", source = "entity.id")
|
||||||
|
VacancyResponse map(VacancyEntity entity, CompanyResponse company);
|
||||||
|
|
||||||
|
VacancyEntity map(CreateVacancyRequest request);
|
||||||
|
|
||||||
|
VacancyEntity map(@MappingTarget VacancyEntity entity, CreateVacancyRequest request);
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package ru.somecompany.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.somecompany.domain.VacancyEntity;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface VacancyRepository extends JpaRepository<VacancyEntity, UUID> {
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package ru.somecompany.service;
|
||||||
|
|
||||||
|
import ru.somecompany.domain.CreateVacancyRequest;
|
||||||
|
import ru.somecompany.domain.VacancyEntity;
|
||||||
|
import ru.somecompany.domain.VacancyResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface VacancyService {
|
||||||
|
|
||||||
|
List<VacancyEntity> getVacancies();
|
||||||
|
|
||||||
|
VacancyEntity createVacancy(CreateVacancyRequest request);
|
||||||
|
|
||||||
|
VacancyEntity updateVacancy(UUID vacancyId, CreateVacancyRequest request);
|
||||||
|
|
||||||
|
void deleteVacancy(UUID vacancyId);
|
||||||
|
|
||||||
|
VacancyResponse getVacancy(UUID vacancyId);
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package ru.somecompany.service;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.somecompany.domain.CreateVacancyRequest;
|
||||||
|
import ru.somecompany.domain.VacancyEntity;
|
||||||
|
import ru.somecompany.domain.VacancyResponse;
|
||||||
|
import ru.somecompany.mapper.VacancyMapper;
|
||||||
|
import ru.somecompany.repository.VacancyRepository;
|
||||||
|
import ru.somecompany.service.adapter.CompanyAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static ru.somecompany.exception.ResourceNotFoundException.notFound;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class VacancyServiceImpl implements VacancyService {
|
||||||
|
|
||||||
|
private final VacancyRepository vacancyRepository;
|
||||||
|
private final CompanyAdapter companyAdapter;
|
||||||
|
private final VacancyMapper mapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<VacancyEntity> getVacancies() {
|
||||||
|
return vacancyRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VacancyEntity createVacancy(CreateVacancyRequest request) {
|
||||||
|
var entity = mapper.map(request);
|
||||||
|
return vacancyRepository.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VacancyEntity updateVacancy(UUID vacancyId, CreateVacancyRequest request) {
|
||||||
|
var entity = getById(vacancyId);
|
||||||
|
entity = mapper.map(entity, request);
|
||||||
|
|
||||||
|
return vacancyRepository.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteVacancy(UUID vacancyId) {
|
||||||
|
var entity = getById(vacancyId);
|
||||||
|
vacancyRepository.delete(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VacancyResponse getVacancy(UUID vacancyId) {
|
||||||
|
var entity = getById(vacancyId);
|
||||||
|
var company = companyAdapter.getCompanyById(entity.getCompanyId());
|
||||||
|
|
||||||
|
return mapper.map(entity, company);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VacancyEntity getById(UUID vacancyId) {
|
||||||
|
var entity = vacancyRepository.findById(vacancyId);
|
||||||
|
if (entity.isEmpty()) {
|
||||||
|
log.warn("The vacancy with id '{}' was not found", vacancyId);
|
||||||
|
throw notFound("Вакансия не найдена");
|
||||||
|
}
|
||||||
|
return entity.get();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.somecompany.service.adapter;
|
||||||
|
|
||||||
|
import ru.somecompany.domain.CompanyResponse;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CompanyAdapter {
|
||||||
|
|
||||||
|
CompanyResponse getCompanyById(UUID companyId);
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package ru.somecompany.service.adapter;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.somecompany.domain.CompanyResponse;
|
||||||
|
import ru.somecompany.external.CompanyClient;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CompanyAdapterImpl implements CompanyAdapter {
|
||||||
|
|
||||||
|
private final CompanyClient companyClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompanyResponse getCompanyById(UUID companyId) {
|
||||||
|
return companyClient.getCompany(companyId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
server:
|
||||||
|
port: ${SERVER_PORT:8080}
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: vacancy-app
|
||||||
|
jpa:
|
||||||
|
database: POSTGRESQL
|
||||||
|
open-in-view: false
|
||||||
|
show-sql: false
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: none
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
datasource:
|
||||||
|
url: ${DB_URL:jdbc:postgresql://localhost:5433/vacancy}
|
||||||
|
username: ${DB_USERNAME:postgres}
|
||||||
|
password: ${DB_PASSWORD:postgres}
|
||||||
|
driverClassName: org.postgresql.Driver
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
hikari:
|
||||||
|
maximum-pool-size: 15
|
||||||
|
minimum-idle: 4
|
||||||
|
idle-timeout: 180000
|
||||||
|
max-lifetime: 599000
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
locations: db/migration/
|
||||||
|
|
||||||
|
app:
|
||||||
|
feign:
|
||||||
|
company-url: ${COMPANY_URL:http://localhost:8081}
|
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE vacancy (
|
||||||
|
id UUID
|
||||||
|
CONSTRAINT company_pk PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description VARCHAR(1000),
|
||||||
|
salary_low int8 NOT NULL,
|
||||||
|
salary_high int8 NOT NULL,
|
||||||
|
company_id UUID NOT NULL
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user