diff --git a/kalyshev_yan_lab_3/README.md b/kalyshev_yan_lab_3/README.md
new file mode 100644
index 0000000..0aabe86
--- /dev/null
+++ b/kalyshev_yan_lab_3/README.md
@@ -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.
\ No newline at end of file
diff --git a/kalyshev_yan_lab_3/computer-service/.gitignore b/kalyshev_yan_lab_3/computer-service/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/.gitignore
@@ -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
\ No newline at end of file
diff --git a/kalyshev_yan_lab_3/computer-service/Dockerfile b/kalyshev_yan_lab_3/computer-service/Dockerfile
new file mode 100644
index 0000000..cc673eb
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/Dockerfile
@@ -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"]
diff --git a/kalyshev_yan_lab_3/computer-service/pom.xml b/kalyshev_yan_lab_3/computer-service/pom.xml
new file mode 100644
index 0000000..cf1ae83
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/pom.xml
@@ -0,0 +1,128 @@
+
+
+ 4.0.0
+
+ ru.computer
+ computer-service
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.3
+
+
+
+
+ 21
+ 21
+ UTF-8
+ 1.5.5.Final
+ 0.2.0
+ 2023.0.3
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.postgresql
+ postgresql
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${lombok-mapstruct-binding.version}
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.2.1
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.3.0
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${lombok-mapstruct-binding.version}
+
+
+
+ -Amapstruct.defaultComponentModel=spring
+
+
+
+
+
+
+
+
+
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/ComputerApplication.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/ComputerApplication.java
new file mode 100644
index 0000000..670a9e0
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/ComputerApplication.java
@@ -0,0 +1,14 @@
+package ru.computer;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@SpringBootApplication
+@EnableFeignClients
+public class ComputerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ComputerApplication.class, args);
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerController.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerController.java
new file mode 100644
index 0000000..ed2d971
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerController.java
@@ -0,0 +1,64 @@
+package ru.computer.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.computer.domain.CreateComputerRequest;
+import ru.computer.domain.ComputerEntity;
+import ru.computer.domain.ComputerResponse;
+
+import java.util.List;
+import java.util.UUID;
+
+@Validated
+@Tag(name = "computer", description = "API для управления компьютерами")
+public interface ComputerController {
+
+ @Operation(summary = "Получение всех компьютеров")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Данные успешно переданы"),
+ })
+ @GetMapping(value = "/api/v1/computer")
+ List getComputers();
+
+ @Operation(summary = "Создание компьютера")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Компьютер успешно создана"),
+ })
+ @PostMapping(value = "/api/v1/computer")
+ ComputerEntity createComputer(@RequestBody @NotNull CreateComputerRequest request);
+
+ @Operation(summary = "Получение информации о компьютере по id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Данные успешно переданы"),
+ @ApiResponse(responseCode = "404", description = "Компьютер не найден")
+ })
+ @GetMapping(value = "/api/v1/computer/{computerId}")
+ ComputerResponse getComputer(@PathVariable UUID computerId);
+
+ @Operation(summary = "Редактирование компьютера")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Данные компьютер успешно изменен"),
+ @ApiResponse(responseCode = "404", description = "Компьютер не найден")
+ })
+ @PutMapping(value = "/api/v1/computer/{computerId}")
+ ComputerEntity updateComputer(@PathVariable UUID computerId,
+ @RequestBody @NotNull CreateComputerRequest request);
+
+ @Operation(summary = "Удалении компьютера по id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Компьютер успешно удален"),
+ @ApiResponse(responseCode = "404", description = "Компьютер не найден")
+ })
+ @DeleteMapping(value = "/api/v1/computer/{computerId}")
+ void deleteComputer(@PathVariable UUID computerId);
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerControllerImpl.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerControllerImpl.java
new file mode 100644
index 0000000..09eff04
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerControllerImpl.java
@@ -0,0 +1,45 @@
+package ru.computer.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RestController;
+import ru.computer.domain.CreateComputerRequest;
+import ru.computer.domain.ComputerEntity;
+import ru.computer.domain.ComputerResponse;
+import ru.computer.service.ComputerService;
+
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+public class ComputerControllerImpl implements ComputerController {
+
+ private final ComputerService computerService;
+
+ @Override
+ public List getComputers() {
+ return computerService.getComputers();
+ }
+
+ @Override
+ public ComputerEntity createComputer(CreateComputerRequest request) {
+ return computerService.createComputer(request);
+ }
+
+ @Override
+ public ComputerResponse getComputer(UUID computerId) {
+ return computerService.getComputer(computerId);
+ }
+
+ @Override
+ public ComputerEntity updateComputer(UUID computerId, CreateComputerRequest request) {
+ return computerService.updateComputer(computerId, request);
+ }
+
+ @Override
+ public void deleteComputer(UUID computerId) {
+ computerService.deleteComputer(computerId);
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerEntity.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerEntity.java
new file mode 100644
index 0000000..4d11df1
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerEntity.java
@@ -0,0 +1,35 @@
+package ru.computer.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 = "computer")
+@AllArgsConstructor
+@NoArgsConstructor
+public class ComputerEntity {
+
+ @Id
+ @GeneratedValue
+ private UUID id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description", nullable = true)
+ private String description;
+
+ @Column(name = "room_id", nullable = false)
+ private UUID roomId;
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerResponse.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerResponse.java
new file mode 100644
index 0000000..c7565d8
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerResponse.java
@@ -0,0 +1,21 @@
+package ru.computer.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 ComputerResponse {
+
+ private UUID id;
+
+ private String name;
+
+ private String description;
+
+ private RoomResponse room;
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/CreateComputerRequest.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/CreateComputerRequest.java
new file mode 100644
index 0000000..deeed0c
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/CreateComputerRequest.java
@@ -0,0 +1,17 @@
+package ru.computer.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.UUID;
+
+@Data
+@AllArgsConstructor
+public class CreateComputerRequest {
+
+ private String name;
+
+ private String description;
+
+ private UUID roomId;
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/RoomResponse.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/RoomResponse.java
new file mode 100644
index 0000000..8c9fefe
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/RoomResponse.java
@@ -0,0 +1,17 @@
+package ru.computer.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.UUID;
+
+@Data
+@AllArgsConstructor
+public class RoomResponse {
+
+ private UUID id;
+
+ private String name;
+
+ private String address;
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/AbstractWebExceptionHandler.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/AbstractWebExceptionHandler.java
new file mode 100644
index 0000000..ca8ce56
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/AbstractWebExceptionHandler.java
@@ -0,0 +1,41 @@
+package ru.computer.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 buildResponse(ErrorCode errorCode, HttpStatus status, String msg,
+ boolean details, Throwable ex) {
+ return buildResponse(errorCode.name(), status, msg, details, ex, LocalDateTime.now());
+ }
+
+ public static ResponseEntity 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());
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/BasicError.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/BasicError.java
new file mode 100644
index 0000000..849bb82
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/BasicError.java
@@ -0,0 +1,5 @@
+package ru.computer.exception;
+
+public enum BasicError implements ErrorCode {
+ NOT_FOUND
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ErrorCode.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ErrorCode.java
new file mode 100644
index 0000000..02629a4
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ErrorCode.java
@@ -0,0 +1,9 @@
+package ru.computer.exception;
+
+import java.io.Serializable;
+
+public interface ErrorCode extends Serializable {
+
+ String name();
+
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResourceNotFoundException.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResourceNotFoundException.java
new file mode 100644
index 0000000..641e86e
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResourceNotFoundException.java
@@ -0,0 +1,28 @@
+package ru.computer.exception;
+
+import org.springframework.http.HttpStatus;
+
+import static ru.computer.exception.BasicError.NOT_FOUND;
+
+public class ResourceNotFoundException extends RoomAppRuntimeException {
+
+ 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);
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResponseError.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResponseError.java
new file mode 100644
index 0000000..eefdf99
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResponseError.java
@@ -0,0 +1,24 @@
+package ru.computer.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;
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/RoomAppRuntimeException.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/RoomAppRuntimeException.java
new file mode 100644
index 0000000..d05330d
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/RoomAppRuntimeException.java
@@ -0,0 +1,27 @@
+package ru.computer.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+public class RoomAppRuntimeException extends RuntimeException {
+ private final ErrorCode errorCode;
+ private final HttpStatus status;
+ private final boolean details;
+
+ public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message) {
+ this(errorCode, status, message, null);
+ }
+
+ public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, Throwable error) {
+ this(errorCode, status, message, true, error);
+ }
+
+ public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, boolean details,
+ Throwable error) {
+ super(message, error);
+ this.errorCode = errorCode;
+ this.status = status;
+ this.details = details;
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/WebExceptionHandler.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/WebExceptionHandler.java
new file mode 100644
index 0000000..d188922
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/WebExceptionHandler.java
@@ -0,0 +1,22 @@
+package ru.computer.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(RoomAppRuntimeException.class)
+ public ResponseEntity handleException(RoomAppRuntimeException ex) {
+ return buildResponse(ex.getErrorCode(), ex.getStatus(), ex.getMessage(), ex.isDetails(), ex);
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/external/RoomClient.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/external/RoomClient.java
new file mode 100644
index 0000000..81e45d1
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/external/RoomClient.java
@@ -0,0 +1,15 @@
+package ru.computer.external;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import ru.computer.domain.RoomResponse;
+
+import java.util.UUID;
+
+@FeignClient(name = "room", url = "${app.feign.room-url}")
+public interface RoomClient {
+
+ @GetMapping(value = "/api/v1/room/{roomId}")
+ RoomResponse getRoom(@PathVariable UUID roomId);
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/mapper/ComputerMapper.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/mapper/ComputerMapper.java
new file mode 100644
index 0000000..439edf6
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/mapper/ComputerMapper.java
@@ -0,0 +1,24 @@
+package ru.computer.mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import ru.computer.domain.RoomResponse;
+import ru.computer.domain.CreateComputerRequest;
+import ru.computer.domain.ComputerEntity;
+import ru.computer.domain.ComputerResponse;
+
+import java.util.UUID;
+
+@Mapper(imports = UUID.class)
+public interface ComputerMapper {
+
+ @Mapping(target = "room", source = "room")
+ @Mapping(target = "name", source = "entity.name")
+ @Mapping(target = "id", source = "entity.id")
+ ComputerResponse map(ComputerEntity entity, RoomResponse room);
+
+ ComputerEntity map(CreateComputerRequest request);
+
+ ComputerEntity map(@MappingTarget ComputerEntity entity, CreateComputerRequest request);
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/repository/ComputerRepository.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/repository/ComputerRepository.java
new file mode 100644
index 0000000..7f1e3d3
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/repository/ComputerRepository.java
@@ -0,0 +1,11 @@
+package ru.computer.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import ru.computer.domain.ComputerEntity;
+
+import java.util.UUID;
+
+@Repository
+public interface ComputerRepository extends JpaRepository {
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerService.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerService.java
new file mode 100644
index 0000000..dbf6136
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerService.java
@@ -0,0 +1,21 @@
+package ru.computer.service;
+
+import ru.computer.domain.CreateComputerRequest;
+import ru.computer.domain.ComputerEntity;
+import ru.computer.domain.ComputerResponse;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface ComputerService {
+
+ List getComputers();
+
+ ComputerEntity createComputer(CreateComputerRequest request);
+
+ ComputerEntity updateComputer(UUID computerId, CreateComputerRequest request);
+
+ void deleteComputer(UUID computerId);
+
+ ComputerResponse getComputer(UUID computerId);
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerServiceImpl.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerServiceImpl.java
new file mode 100644
index 0000000..7ae682c
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerServiceImpl.java
@@ -0,0 +1,68 @@
+package ru.computer.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import ru.computer.domain.CreateComputerRequest;
+import ru.computer.domain.ComputerEntity;
+import ru.computer.domain.ComputerResponse;
+import ru.computer.mapper.ComputerMapper;
+import ru.computer.repository.ComputerRepository;
+import ru.computer.service.adapter.RoomAdapter;
+
+import java.util.List;
+import java.util.UUID;
+
+import static ru.computer.exception.ResourceNotFoundException.notFound;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ComputerServiceImpl implements ComputerService {
+
+ private final ComputerRepository computerRepository;
+ private final RoomAdapter roomAdapter;
+ private final ComputerMapper mapper;
+
+ @Override
+ public List getComputers() {
+ return computerRepository.findAll();
+ }
+
+ @Override
+ public ComputerEntity createComputer(CreateComputerRequest request) {
+ var entity = mapper.map(request);
+ return computerRepository.save(entity);
+ }
+
+ @Override
+ public ComputerEntity updateComputer(UUID computerId, CreateComputerRequest request) {
+ var entity = getById(computerId);
+ entity = mapper.map(entity, request);
+
+ return computerRepository.save(entity);
+ }
+
+ @Override
+ public void deleteComputer(UUID computerId) {
+ var entity = getById(computerId);
+ computerRepository.delete(entity);
+ }
+
+ @Override
+ public ComputerResponse getComputer(UUID computerId) {
+ var entity = getById(computerId);
+ var room = roomAdapter.getRoomById(entity.getRoomId());
+
+ return mapper.map(entity, room);
+ }
+
+ private ComputerEntity getById(UUID computerId) {
+ var entity = computerRepository.findById(computerId);
+ if (entity.isEmpty()) {
+ log.warn("The computer with id '{}' was not found", computerId);
+ throw notFound("Компьютер не найден");
+ }
+ return entity.get();
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapter.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapter.java
new file mode 100644
index 0000000..7c60dd5
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapter.java
@@ -0,0 +1,10 @@
+package ru.computer.service.adapter;
+
+import ru.computer.domain.RoomResponse;
+
+import java.util.UUID;
+
+public interface RoomAdapter {
+
+ RoomResponse getRoomById(UUID companyId);
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapterImpl.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapterImpl.java
new file mode 100644
index 0000000..b38afd9
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapterImpl.java
@@ -0,0 +1,20 @@
+package ru.computer.service.adapter;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.computer.domain.RoomResponse;
+import ru.computer.external.RoomClient;
+
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class RoomAdapterImpl implements RoomAdapter {
+
+ private final RoomClient roomClient;
+
+ @Override
+ public RoomResponse getRoomById(UUID roomId) {
+ return roomClient.getRoom(roomId);
+ }
+}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/resources/application.yml b/kalyshev_yan_lab_3/computer-service/src/main/resources/application.yml
new file mode 100644
index 0000000..1ed9851
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/resources/application.yml
@@ -0,0 +1,32 @@
+server:
+ port: ${SERVER_PORT:8080}
+spring:
+ application:
+ name: computer-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/computer}
+ 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:
+ room-url: ${ROOM_URL:http://localhost:8081}
diff --git a/kalyshev_yan_lab_3/computer-service/src/main/resources/db/migration/V000001__init.sql b/kalyshev_yan_lab_3/computer-service/src/main/resources/db/migration/V000001__init.sql
new file mode 100644
index 0000000..15c1141
--- /dev/null
+++ b/kalyshev_yan_lab_3/computer-service/src/main/resources/db/migration/V000001__init.sql
@@ -0,0 +1,7 @@
+CREATE TABLE computer (
+ id UUID
+ CONSTRAINT computer_pk PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ description VARCHAR(1000),
+ room_id UUID NOT NULL
+);
diff --git a/kalyshev_yan_lab_3/docker-compose.yml b/kalyshev_yan_lab_3/docker-compose.yml
new file mode 100644
index 0000000..59f53bd
--- /dev/null
+++ b/kalyshev_yan_lab_3/docker-compose.yml
@@ -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
+ room:
+ build: ./room-service
+ container_name: room
+ depends_on:
+ - postgres
+ environment:
+ SERVER_PORT: 8080
+ DB_URL: jdbc:postgresql://postgres:5432/room
+ DB_USERNAME: postgres
+ DB_PASSWORD: postgres
+ computer:
+ build: ./computer-service
+ container_name: computer
+ depends_on:
+ - postgres
+ environment:
+ SERVER_PORT: 8080
+ DB_URL: jdbc:postgresql://postgres:5432/computer
+ DB_USERNAME: postgres
+ DB_PASSWORD: postgres
+ ROOM_URL: http://nginx/
+ nginx:
+ image: nginx
+ depends_on:
+ - computer
+ - room
+ volumes:
+ - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
+ ports:
+ - 80:80
diff --git a/kalyshev_yan_lab_3/init-database.sh b/kalyshev_yan_lab_3/init-database.sh
new file mode 100644
index 0000000..f9c3afb
--- /dev/null
+++ b/kalyshev_yan_lab_3/init-database.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+
+# Создаем БД
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
+ CREATE DATABASE computer;
+ CREATE DATABASE room;
+EOSQL
diff --git a/kalyshev_yan_lab_3/nginx/nginx.conf b/kalyshev_yan_lab_3/nginx/nginx.conf
new file mode 100644
index 0000000..786e529
--- /dev/null
+++ b/kalyshev_yan_lab_3/nginx/nginx.conf
@@ -0,0 +1,21 @@
+server {
+ listen 80;
+ listen [::]:80;
+ server_name localhost;
+
+ location /api/v1/room {
+ proxy_pass http://room: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/computer {
+ proxy_pass http://computer: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;
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/.gitignore b/kalyshev_yan_lab_3/room-service/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/.gitignore
@@ -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
\ No newline at end of file
diff --git a/kalyshev_yan_lab_3/room-service/Dockerfile b/kalyshev_yan_lab_3/room-service/Dockerfile
new file mode 100644
index 0000000..cc673eb
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/Dockerfile
@@ -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"]
diff --git a/kalyshev_yan_lab_3/room-service/pom.xml b/kalyshev_yan_lab_3/room-service/pom.xml
new file mode 100644
index 0000000..425e89f
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ ru.room
+ room-service
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.3
+
+
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.postgresql
+ postgresql
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.2.1
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.3.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/RoomApplication.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/RoomApplication.java
new file mode 100644
index 0000000..b8ae38f
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/RoomApplication.java
@@ -0,0 +1,12 @@
+package ru.room;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class RoomApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RoomApplication.class, args);
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomController.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomController.java
new file mode 100644
index 0000000..a617862
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomController.java
@@ -0,0 +1,43 @@
+package ru.room.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.room.domain.RoomEntity;
+import ru.room.domain.CreateRoomRequest;
+
+import java.util.List;
+import java.util.UUID;
+
+@Validated
+@Tag(name = "room", description = "API для управления компаниями")
+public interface RoomController {
+
+ @Operation(summary = "Получение всех компаний")
+ @GetMapping(value = "/api/v1/room")
+ List getCompanies();
+
+ @Operation(summary = "Создание компании")
+ @PostMapping(value = "/api/v1/room")
+ RoomEntity createRoom(@RequestBody @NotNull CreateRoomRequest roomRequest);
+
+ @Operation(summary = "Получение информации о компании по id")
+ @GetMapping(value = "/api/v1/room/{roomId}")
+ RoomEntity getRoom(@PathVariable UUID roomId);
+
+ @Operation(summary = "Редактирование компании")
+ @PutMapping(value = "/api/v1/room/{roomId}")
+ RoomEntity updateRoom(@PathVariable UUID roomId,
+ @RequestBody @NotNull CreateRoomRequest roomRequest);
+
+ @Operation(summary = "Удалении компании по id")
+ @DeleteMapping(value = "/api/v1/room/{roomId}")
+ void deleteRoom(@PathVariable UUID roomId);
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomControllerImpl.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomControllerImpl.java
new file mode 100644
index 0000000..e1d5f8a
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomControllerImpl.java
@@ -0,0 +1,42 @@
+package ru.room.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.RestController;
+import ru.room.domain.RoomEntity;
+import ru.room.domain.CreateRoomRequest;
+import ru.room.service.RoomService;
+
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequiredArgsConstructor
+public class RoomControllerImpl implements RoomController {
+
+ private final RoomService roomService;
+
+ @Override
+ public List getCompanies() {
+ return roomService.getCompanies();
+ }
+
+ @Override
+ public RoomEntity createRoom(CreateRoomRequest roomRequest) {
+ return roomService.createRoom(roomRequest);
+ }
+
+ @Override
+ public RoomEntity getRoom(UUID roomId) {
+ return roomService.getRoom(roomId);
+ }
+
+ @Override
+ public RoomEntity updateRoom(UUID roomId, CreateRoomRequest roomRequest) {
+ return roomService.updateRoom(roomId, roomRequest);
+ }
+
+ @Override
+ public void deleteRoom(UUID roomId) {
+ roomService.deleteRoom(roomId);
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/CreateRoomRequest.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/CreateRoomRequest.java
new file mode 100644
index 0000000..00bcfae
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/CreateRoomRequest.java
@@ -0,0 +1,13 @@
+package ru.room.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class CreateRoomRequest {
+
+ private String name;
+
+ private String address;
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/RoomEntity.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/RoomEntity.java
new file mode 100644
index 0000000..54d5de1
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/RoomEntity.java
@@ -0,0 +1,32 @@
+package ru.room.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 = "room")
+@AllArgsConstructor
+@NoArgsConstructor
+public class RoomEntity {
+
+ @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;
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/AbstractWebExceptionHandler.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/AbstractWebExceptionHandler.java
new file mode 100644
index 0000000..3623f56
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/AbstractWebExceptionHandler.java
@@ -0,0 +1,41 @@
+package ru.room.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 buildResponse(ErrorCode errorCode, HttpStatus status, String msg,
+ boolean details, Throwable ex) {
+ return buildResponse(errorCode.name(), status, msg, details, ex, LocalDateTime.now());
+ }
+
+ public static ResponseEntity 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());
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/BasicError.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/BasicError.java
new file mode 100644
index 0000000..bb1034a
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/BasicError.java
@@ -0,0 +1,5 @@
+package ru.room.exception;
+
+public enum BasicError implements ErrorCode {
+ NOT_FOUND
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ErrorCode.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ErrorCode.java
new file mode 100644
index 0000000..3d25b35
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ErrorCode.java
@@ -0,0 +1,9 @@
+package ru.room.exception;
+
+import java.io.Serializable;
+
+public interface ErrorCode extends Serializable {
+
+ String name();
+
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResourceNotFoundException.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResourceNotFoundException.java
new file mode 100644
index 0000000..c55bc35
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResourceNotFoundException.java
@@ -0,0 +1,28 @@
+package ru.room.exception;
+
+import org.springframework.http.HttpStatus;
+
+import static ru.room.exception.BasicError.NOT_FOUND;
+
+public class ResourceNotFoundException extends RoomAppRuntimeException {
+
+ 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);
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResponseError.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResponseError.java
new file mode 100644
index 0000000..d60ee4d
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResponseError.java
@@ -0,0 +1,24 @@
+package ru.room.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;
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/RoomAppRuntimeException.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/RoomAppRuntimeException.java
new file mode 100644
index 0000000..a16520d
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/RoomAppRuntimeException.java
@@ -0,0 +1,27 @@
+package ru.room.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+public class RoomAppRuntimeException extends RuntimeException {
+ private final ErrorCode errorCode;
+ private final HttpStatus status;
+ private final boolean details;
+
+ public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message) {
+ this(errorCode, status, message, null);
+ }
+
+ public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, Throwable error) {
+ this(errorCode, status, message, true, error);
+ }
+
+ public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, boolean details,
+ Throwable error) {
+ super(message, error);
+ this.errorCode = errorCode;
+ this.status = status;
+ this.details = details;
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/WebExceptionHandler.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/WebExceptionHandler.java
new file mode 100644
index 0000000..a9a6f90
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/WebExceptionHandler.java
@@ -0,0 +1,22 @@
+package ru.room.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(RoomAppRuntimeException.class)
+ public ResponseEntity handleException(RoomAppRuntimeException ex) {
+ return buildResponse(ex.getErrorCode(), ex.getStatus(), ex.getMessage(), ex.isDetails(), ex);
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/repository/RoomRepository.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/repository/RoomRepository.java
new file mode 100644
index 0000000..e4870a9
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/repository/RoomRepository.java
@@ -0,0 +1,9 @@
+package ru.room.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.room.domain.RoomEntity;
+
+import java.util.UUID;
+
+public interface RoomRepository extends JpaRepository {
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomService.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomService.java
new file mode 100644
index 0000000..3881f4e
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomService.java
@@ -0,0 +1,20 @@
+package ru.room.service;
+
+import ru.room.domain.RoomEntity;
+import ru.room.domain.CreateRoomRequest;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface RoomService {
+
+ List getCompanies();
+
+ RoomEntity createRoom(CreateRoomRequest roomRequest);
+
+ RoomEntity updateRoom(UUID roomId, CreateRoomRequest roomRequest);
+
+ void deleteRoom(UUID roomId);
+
+ RoomEntity getRoom(UUID roomId);
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomServiceImpl.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomServiceImpl.java
new file mode 100644
index 0000000..cab5adf
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomServiceImpl.java
@@ -0,0 +1,57 @@
+package ru.room.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.room.domain.RoomEntity;
+import ru.room.domain.CreateRoomRequest;
+import ru.room.repository.RoomRepository;
+
+import java.util.List;
+import java.util.UUID;
+
+import static ru.room.exception.ResourceNotFoundException.notFound;
+
+@Service
+@RequiredArgsConstructor
+public class RoomServiceImpl implements RoomService {
+
+ private final RoomRepository roomRepository;
+
+ @Override
+ public List getCompanies() {
+ return roomRepository.findAll();
+ }
+
+ @Override
+ public RoomEntity createRoom(CreateRoomRequest roomRequest) {
+ var entity = RoomEntity.builder()
+ .name(roomRequest.getName())
+ .address(roomRequest.getAddress())
+ .build();
+ return roomRepository.save(entity);
+ }
+
+ @Override
+ public RoomEntity updateRoom(UUID roomId, CreateRoomRequest roomRequest) {
+ var room = getById(roomId);
+ room.setName(roomRequest.getName());
+ room.setAddress(roomRequest.getAddress());
+ return roomRepository.save(room);
+ }
+
+ @Override
+ public void deleteRoom(UUID roomId) {
+ var room = getById(roomId);
+ roomRepository.delete(room);
+ }
+
+ @Override
+ public RoomEntity getRoom(UUID roomId) {
+ return getById(roomId);
+ }
+
+ private RoomEntity getById(UUID roomId) {
+ return roomRepository.findById(roomId)
+ .orElseThrow(() -> notFound("Компания не найдена"));
+ }
+}
diff --git a/kalyshev_yan_lab_3/room-service/src/main/resources/application.yml b/kalyshev_yan_lab_3/room-service/src/main/resources/application.yml
new file mode 100644
index 0000000..e98b0f6
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/resources/application.yml
@@ -0,0 +1,28 @@
+server:
+ port: ${SERVER_PORT:8080}
+spring:
+ application:
+ name: room-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/room}
+ 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/
diff --git a/kalyshev_yan_lab_3/room-service/src/main/resources/db/migration/V000001__init.sql b/kalyshev_yan_lab_3/room-service/src/main/resources/db/migration/V000001__init.sql
new file mode 100644
index 0000000..ea25073
--- /dev/null
+++ b/kalyshev_yan_lab_3/room-service/src/main/resources/db/migration/V000001__init.sql
@@ -0,0 +1,6 @@
+CREATE TABLE room (
+ id UUID
+ CONSTRAINT room_pk PRIMARY KEY,
+ name VARCHAR(255) UNIQUE NOT NULL,
+ address VARCHAR(255) NOT NULL
+);