Compare commits

..

53 Commits

Author SHA1 Message Date
85b809333b Merge pull request 'dolgov_dmitriy_lab_2' (#48) from dolgov_dmitriy_lab_2 into main
Reviewed-on: Alexey/DAS_2024_1#48
2024-10-07 23:44:23 +04:00
5e3c9c0d5b Merge pull request 'kashin_maxim_lab_3' (#47) from kashin_maxim_lab_3 into main
Reviewed-on: Alexey/DAS_2024_1#47
2024-10-07 23:39:34 +04:00
daf24d364d Merge pull request 'lab 3 complete' (#46) from zhimolostnova_anna_lab_3 into main
Reviewed-on: Alexey/DAS_2024_1#46
2024-10-07 23:36:16 +04:00
6c13deb231 Merge pull request 'vasina_ekaterina_lab_1 is ready' (#45) from vasina_ekaterina_lab_1 into main
Reviewed-on: Alexey/DAS_2024_1#45
2024-10-07 23:33:38 +04:00
543d41d9c3 Merge pull request 'tsukanova_irina_lab_3' (#44) from tsukanova_irina_lab_3 into main
Reviewed-on: Alexey/DAS_2024_1#44
2024-10-07 23:32:24 +04:00
153684c403 Merge pull request 'balakhonov_danila_lab_2' (#43) from balakhonov_danila_lab_2 into main
Reviewed-on: Alexey/DAS_2024_1#43
2024-10-07 23:29:07 +04:00
0708b01560 Merge pull request 'bazunov_andrew_lab_2' (#42) from bazunov_andrew_lab_2 into main
Reviewed-on: Alexey/DAS_2024_1#42
2024-10-07 23:28:10 +04:00
8a6932ff20 Merge pull request 'bogdanov_dmitry_lab_1' (#41) from bogdanov_dmitry_lab_1 into main
Reviewed-on: Alexey/DAS_2024_1#41
2024-10-07 23:24:48 +04:00
35cf16824d Merge pull request 'lazarev_andrey_lab_1 done' (#40) from lazarev_andrey_lab_1 into main
Reviewed-on: Alexey/DAS_2024_1#40
2024-10-07 23:09:33 +04:00
ac3dc2e566 Merge pull request 'vaksman_valeria_lab_2' (#39) from vaksman_valeria_lab_2 into main
Reviewed-on: Alexey/DAS_2024_1#39
2024-10-07 23:09:07 +04:00
2f46c05849 Merge pull request 'borschevskaya_anna_lab_3 is ready' (#38) from borschevskaya_anna_lab_3 into main
Reviewed-on: Alexey/DAS_2024_1#38
2024-10-07 23:07:51 +04:00
84cb26162c Merge pull request 'kalyshev_yan_lab_1' (#37) from kalyshev_yan_lab_1 into main
Reviewed-on: Alexey/DAS_2024_1#37
2024-10-07 23:07:09 +04:00
129b991712 Merge pull request 'tukaeva_alfiya_lab_2_fix' (#36) from tukaeva_alfiya_lab_2_fix into main
Reviewed-on: Alexey/DAS_2024_1#36
2024-10-07 23:06:38 +04:00
ffecef8fa3 Merge pull request 'dozorova_alena_lab_3' (#35) from dozorova_alena_lab_3_fix into main
Reviewed-on: Alexey/DAS_2024_1#35
2024-10-07 23:06:15 +04:00
1289d67a62 Merge pull request 'kuzarin_maxim_lab_4' (#34) from kuzarin_maxim_lab_4 into main
Reviewed-on: Alexey/DAS_2024_1#34
2024-10-07 23:01:48 +04:00
b09f3ea844 Merge pull request 'kashin_maxim_lab_2' (#31) from kashin_maxim_lab_2 into main
Reviewed-on: Alexey/DAS_2024_1#31
2024-10-07 23:00:05 +04:00
2f368ffb07 Обновить dolgov_dmitriy_lab_2/README.md 2024-10-07 15:31:54 +04:00
ead06782ad Обновить dolgov_dmitriy_lab_2/README.md 2024-10-07 14:12:39 +04:00
b2ac5eba9a Обновить dolgov_dmitriy_lab_2/README.md 2024-10-07 14:12:19 +04:00
0c0a47549a Обновить dolgov_dmitriy_lab_2/README.md 2024-10-07 14:11:37 +04:00
Аришина)
84e8cac198 Лабрадор 2024-10-07 14:09:04 +04:00
bde242318f Странный комменатрии оставленный в 5 утра... 2024-10-06 00:35:20 +04:00
761cc83ebd Пабеда... 2024-10-06 00:33:30 +04:00
4699fda797 Фух, готово. Осталось ридми. 2024-10-05 23:40:53 +04:00
940cc6757f lab 3 complete 2024-10-05 21:14:57 +03:00
1e9bdf2806 Почти сделано. Исправить связи и сделать readme.md 2024-10-05 22:02:08 +04:00
evasina2312@gmail.com
9bd14a60b4 vasina_ekaterina_lab_1 is ready 2024-10-05 22:00:12 +04:00
5aa2cae670 init 2024-10-05 01:24:02 +04:00
281d30a89e lab 3 done 2024-10-03 16:17:37 +04:00
f2093f376c небольшие правки 2024-10-02 23:00:20 +04:00
Bazunov Andrew Igorevich
eeac04be49 Complete lab 2 2024-10-02 17:58:40 +04:00
the
0a73e2d5d4 bogdanov_dmitry_lab_1 is ready 2024-10-02 15:11:26 +04:00
1565e49462 lazarev_andrey_lab_1 done 2024-10-02 15:06:10 +04:00
858ea65e71 что-то работает 2024-10-02 14:16:54 +04:00
0f898b968d tot 2024-10-02 10:47:53 +04:00
8b96102dbd еще и еще запросы 2024-10-01 23:05:44 +04:00
82ecad71f4 is ready (ура) 2024-10-01 19:42:31 +04:00
ef68b506b8 borschevskaya_anna_lab_3 is ready 2024-09-29 15:49:34 +04:00
Zyzf
8efc2422cf kalyshev_yan_lab_1 is ready 2024-09-29 12:39:22 +04:00
c3537b5abe немного изменений 2024-09-27 23:26:36 +04:00
a0ef65e0f9 сервис авторов 2024-09-27 16:53:32 +04:00
6815b2e560 tukaeva_alfiya_lab_2 is fix 2024-09-26 15:53:43 +04:00
48b7fbd900 tukaeva_alfiya_lab_2 is ready 2024-09-26 15:44:30 +04:00
080625d270 исправляем реквест 2024-09-26 11:04:52 +04:00
d9f5f75f5e Добавление комментариев и readme 2024-09-24 21:07:33 +04:00
7d9c9ec4d0 Осталось readme сделать 2024-09-24 18:13:22 +04:00
6ce78e60ad Починил генерацию файлов. 2024-09-23 16:00:41 +04:00
b13182c80e Проблема с Docker на компьютере. Сохранение процесса. 2024-09-23 15:28:34 +04:00
822467bd99 add branch + readme 2024-09-23 13:20:26 +04:00
d7faf2a1b7 Обновить kuzarin_maxim_lab_4/README.md
Вот теперь картинки будут отображаться
2024-09-22 20:23:01 +04:00
d98803227e Обновить kuzarin_maxim_lab_4/README.md 2024-09-22 20:22:20 +04:00
6f12270c73 Обновить kuzarin_maxim_lab_4/README.md 2024-09-22 20:21:18 +04:00
6e6266c228 ЛР 4 готова, но может быть нужно будет README поправить... 2024-09-22 19:19:44 +03:00
236 changed files with 9790 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
# Используем официальный образ Go в качестве базового
FROM golang:1.23
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем файлы модуля
COPY . .
# Сборка модуля
RUN go build -o /bin/FileCreator
# Запуск модуля
CMD ["/bin/FileCreator"]

View File

@@ -0,0 +1 @@
module FileCreator

View File

@@ -0,0 +1,92 @@
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"os"
"path/filepath"
)
const DIR = "/var/data"
func Exists(name string) (bool, error) {
_, err := os.Stat(name)
if os.IsNotExist(err) {
return false, nil
}
return err != nil, err
}
func CreateDirectory(dirs string) error {
if _, err := os.Stat(dirs); os.IsNotExist(err) {
err := os.MkdirAll(dirs, 0664)
if err != nil {
return err
}
}
return nil
}
func CreateFileOrOpenIfExist(name string) (*os.File, error) {
err := CreateDirectory(filepath.Dir(name))
if err != nil {
return nil, err
}
exists, err := Exists(name)
if err != nil {
return nil, err
}
if exists {
return os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0664)
}
return os.Create(name)
}
func CreateFileAndWriteData(filename string) error {
file, err := CreateFileOrOpenIfExist(filename)
if err != nil {
return err
}
lines := rand.Intn(1000) + 100
for i := 0; i < lines; i++ {
randomValueForLine := rand.Intn(1_000_000)
_, err = fmt.Fprintf(file, "%d\r\n", randomValueForLine)
if err != nil {
return err
}
}
err = file.Close()
if err != nil {
return err
}
return nil
}
func GetMD5Hash(text string) string {
hash := md5.Sum([]byte(text))
return hex.EncodeToString(hash[:])
}
func main() {
for i := 0; i < 10; i++ {
filename := fmt.Sprintf("%s/%s.txt", DIR, GetMD5Hash(fmt.Sprintf("%d", i)))
err := CreateFileAndWriteData(filename)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Created file %s\n", filename)
}
}
err := CreateFileAndWriteData(DIR + "/data.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Created file %s\n", DIR+"/data.txt")
}
}

View File

@@ -0,0 +1,14 @@
# Используем официальный образ Go в качестве базового
FROM golang:1.23
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем файлы модуля
COPY . .
# Сборка модуля
RUN go build -o /bin/FirstService
# Запуск модуля
CMD ["/bin/FirstService"]

View File

@@ -0,0 +1 @@
module RVIP2

View File

@@ -0,0 +1,94 @@
package main
import (
"fmt"
"os"
)
// Формирует файл /var/result/data.txt так,
// что каждая строка файла - количество символов в именах файлов из каталога /var/data.
const INPUT = "/var/data"
const OUTPUT = "/data/result"
func GetListFilesInDirectory(directory string) ([]string, error) {
f, err := os.Open(directory)
if err != nil {
fmt.Println(err)
return nil, err
}
files, err := f.Readdir(0)
if err != nil {
fmt.Println(err)
return nil, err
}
var fileNames []string
for _, file := range files {
fileName := file.Name()
fileNames = append(fileNames, fileName)
}
return fileNames, nil
}
func Exists(name string) (bool, error) {
_, err := os.Stat(name)
if os.IsNotExist(err) {
return false, nil
}
return err != nil, err
}
func CreateFileOrOpenIfExist(name string) (*os.File, error) {
exists, err := Exists(name)
if err != nil {
return nil, err
}
if exists {
return os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0664)
}
return os.Create(name)
}
func CreateFileAndWriteData(filename string, lines []string) error {
file, err := CreateFileOrOpenIfExist(filename)
if err != nil {
return err
}
for _, line := range lines {
_, err = fmt.Fprintf(file, line)
if err != nil {
return err
}
}
err = file.Close()
if err != nil {
return err
}
return nil
}
func main() {
filenames, err := GetListFilesInDirectory(INPUT)
if err != nil {
fmt.Println(err)
return
}
var lenghtOfFilenames []string
for _, filename := range filenames {
fmt.Println(filename)
lenghtOfFilenames = append(lenghtOfFilenames, fmt.Sprintf("%d", len(filename)))
}
err = CreateFileAndWriteData(OUTPUT+"/data.txt", filenames)
if err != nil {
return
}
fmt.Println("First Service is end.")
}

View File

@@ -0,0 +1,30 @@
# Распределенные вычисления и приложения Л2
## _Автор Базунов Андрей Игревич ПИбд-42_
Сервисы ( _порядок исполнения сервисов соблюден_ ):
- 1.FileCreator - (_Создание тестовых данных_)
- 2.FirstService - (_Выполнение 1.4 варианта задания_)
- 3.SecondService - (_Выполнение 2.2 варианта задания_)
В качестве основного языка был выбран GoLang. Для каждого сервиса был создан DOCKERFILE где были прописаны условия и действия для сборки каждого из модулей
# Docker
>Перед исполнением вполняем установку docker и проверяем версию
```sh
docker-compose --version
```
>Далее производим настройку файла docker-compose.yaml и запускаем контейнер с сборкой образов
```sh
docker-compose up -d --build
```
>Для завершения работы контейнера используем команду
```sh
docker-compose down
```
[Демонстрация работы](https://vk.com/video236673313_456239575)

View File

@@ -0,0 +1,14 @@
# Используем официальный образ Go в качестве базового
FROM golang:1.23
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем файлы модуля
COPY . .
# Сборка модуля
RUN go build -o /bin/SecondService
# Запуск модуля
CMD ["/bin/SecondService"]

View File

@@ -0,0 +1 @@
module SecondService

View File

@@ -0,0 +1,79 @@
package main
import (
"bufio"
"fmt"
"os"
)
//Ищет наименьшее число из файла /var/data/data.txt и сохраняет его третью степень в /var/result/result.txt.
const INPUT = "/var/data/data.txt"
const OUTPUT = "/var/result/result.txt"
func ReadlinesFromFile(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
var output []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
output = append(output, scanner.Text())
}
err = file.Close()
if err != nil {
return nil, err
}
return output, nil
}
func WriteIntToFile(filename string, i int) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
}
}(file)
_, err = file.WriteString(fmt.Sprintf("%d\n", i))
if err != nil {
return err
}
return nil
}
func main() {
lines, err := ReadlinesFromFile(INPUT)
if err != nil {
fmt.Println(err)
}
minValue := 0
for _, line := range lines {
if intValue, err := fmt.Sscanf(line, "%d", &minValue); err != nil {
fmt.Println(err)
} else {
if minValue >= intValue {
minValue = intValue
}
}
}
if err = WriteIntToFile(OUTPUT, minValue); err != nil {
return
} else {
fmt.Printf("Write %d to %s\n", minValue, OUTPUT)
}
fmt.Println("Second Service is end.")
}

View File

@@ -0,0 +1,27 @@
services:
file_generate:
build:
context: ./FileCreator
dockerfile: Dockerfile
volumes:
- ./data:/var/data # Монтирование локальной папки data в /var/data в контейнере
first_service:
build:
context: ./FirstService
dockerfile: Dockerfile
volumes:
- ./data:/var/data
- ./data:/var/result
depends_on:
- file_generate
second_service:
build:
context: ./SecondService
dockerfile: Dockerfile
volumes:
- ./data:/var/data
- ./data:/var/result
depends_on:
- first_service

View File

@@ -0,0 +1,35 @@
# Лабораторная работа №1
## Богданов Дмитрий ПИбд-42
### Для выполнения были развернуты следующие сервисы:
* PostgreSQL - база данных
* Mediawiki - движок вики
* Gitea - движок гита
### С использованием следующих технологий:
* git
* docker
* docker-compose
### Запуск лабораторной:
Необходимо перейти в папку с файлом docker-compose.yaml и ввести следующую команду:
```
docker-compose up -d
```
## Результат запуска:
```
[+] Running 4/4
✔ Network bogdanov_dmitry_lab_1_default Created 0.0s
✔ Container bogdanov_dmitry_lab_1-mediawiki-1 Started 0.7s
✔ Container bogdanov_dmitry_lab_1-git-1 Started 0.8s
✔ Container bogdanov_dmitry_lab_1-db-1 Started 0.7s
```
## Видео с результатом запуска:
Видео можно посмотреть по данной [ссылке](https://drive.google.com/file/d/1TES58HIeCnnKbtwWgED2oig4N7plBmol/view).

View File

@@ -0,0 +1,40 @@
services:
# PostgreSQL
db:
# Образ контейнера
image: postgres
# Перезапуск при падении
restart: always
# Порт для подключения
ports:
- 5432:5432
# Каталог с данными. Каталог компьютера:каталог контейнера
volumes:
- ./volumes/postgres:/var/lib/postgresql/data
# Переменные среды для определения хотя бы одного пользователя при запуске
environment:
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=admin
# Mediawiki
mediawiki:
# Образ
image: mediawiki
# Перезапуск при падении
restart: always
# Порт для подключения
ports:
- 8080:80
# Каталоги
volumes:
- ./volumes/mediawiki:/var/www/html/images
# Gitea
git:
image: gitea/gitea:latest
restart: always
ports:
- "3000:3000"
- "222:22"
volumes:
- ./volumes/gitea:/data
- ./volumes/timezone:/etc/timezone:ro
- ./volumes/localtime:/etc/localtime:ro

View 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.

View 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

View 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"]

View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,5 @@
package ru.somecompany.exception;
public enum BasicError implements ErrorCode {
NOT_FOUND
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
package ru.somecompany.exception;
import java.io.Serializable;
public interface ErrorCode extends Serializable {
String name();
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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> {
}

View File

@@ -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);
}

View File

@@ -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("Компания не найдена"));
}
}

View File

@@ -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/

View File

@@ -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
);

View 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

View 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

View 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;
}
}

View 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

View 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"]

View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,5 @@
package ru.somecompany.exception;
public enum BasicError implements ErrorCode {
NOT_FOUND
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
package ru.somecompany.exception;
import java.io.Serializable;
public interface ErrorCode extends Serializable {
String name();
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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> {
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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}

View File

@@ -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
);

2
dolgov_dmitriy_lab_2/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
data
result

View File

@@ -0,0 +1,36 @@
# Лабораторная работа №2
## Выполнил: Долгов Дмитрий, группа ПИбд-42
### Были выбраны следующие варианты программ:
* _Вариант первого приложения: 6._ Берёт из каталога /var/data случайный файл и перекладывает его в /var/result/data.txt.
* _Вариант второго приложения: 1._ Ищет набольшее число из файла /var/data/data.txt и сохраняет его вторую степень в /var/result/result.txt.
Кроме того, для генерации файлов был реализован сервис file_generator
### Были использованы следующие технологии:
* git
* docker
* docker-compose
* язык программирования Python
### Для запуска лабораторной работы, находясь в папке `dolgov_dmitriy_lab_2`, необходимо выполнить следующую команду:
```
docker-compose up --build
```
Таким образом, запустятся все сервисы, необходимые для выполнения лабораторной работы.
## Результат запуска:
```
app2-1 | Наибольшее числовое значение: 29.0
app2-1 | Результат: 841.0
app1-1 | Файл file_3.txt успешно перемещен в /var/result/data.txt
app2-1 exited with code 0
app1-1 exited with code 0
```
## Видео с результатом запуска:
Видео можно посмотреть по данной [ссылке](https://drive.google.com/file/d/1Iw6BR0lRvh4382QsIEfr5E68-09aKX9C/view?usp=drive_link).

View File

@@ -0,0 +1,8 @@
# Стандартный образ Python
FROM python:3.9
# Указываем рабочую директорию
WORKDIR /app
# Копируем в ней файл app1.py
COPY app1.py .
# Команда для запуска при старте контейнера
CMD ["python", "app1.py"]

View File

@@ -0,0 +1,27 @@
import os
import random
import shutil
def move_random_file():
src_dir = "/var/data"
dst_file = "/var/result/data.txt"
# Получаем список всех файлов в src_dir
files = os.listdir(src_dir)
if files:
# Выбираем случайный файл
random_file = random.choice(files)
# Проверяем, что файл существует
if os.path.exists(os.path.join(src_dir, random_file)):
# Перемещаем файл в целевое место
shutil.move(os.path.join(src_dir, random_file), dst_file)
print(f"Файл {random_file} успешно перемещен в {dst_file}")
else:
print(f"Ошибка: Файл {random_file} не найден в {src_dir}")
else:
print("Папка пуста")
if __name__ == "__main__":
move_random_file()

View File

@@ -0,0 +1,8 @@
# Стандартный образ Python
FROM python:3.9
# Указываем рабочую директорию
WORKDIR /app
# Копируем содержимое текущей директории в рабочую директорию
COPY app2.py .
# Запускаем приложение
CMD ["python", "app2.py"]

View File

@@ -0,0 +1,26 @@
import os
from math import sqrt
def find_largest_number_and_square():
input_file = "/var/data/data.txt"
output_file = "/var/result/result.txt"
try:
with open(input_file, 'r') as f:
numbers = [float(num.strip()) for num in f]
largest_num = max(numbers)
squared_num = largest_num ** 2
with open(output_file, 'w') as f:
f.write(str(squared_num))
print(f"Наибольшее числовое значение: {largest_num}")
print(f"Результат: {squared_num}")
except FileNotFoundError:
print(f"Ошибка: Файл {input_file} не найден")
except ValueError:
print("Ошибка: В файле содержатся некорректные данные")
if __name__ == "__main__":
find_largest_number_and_square()

View File

@@ -0,0 +1,42 @@
# Сервисы, которые будут запускаться
services:
# Сервис генерации файлов
file_generator:
build:
# Директория с Dockerfile и скриптом генератора
context: ./file_generator
dockerfile: Dockerfile
# Монтирование локальной папки data в /var/data в контейнере
volumes:
- ./data:/var/data
- ./result:/var/result
# Запуск команды, которая генерирует файл в папке /var/data
command: ["python", "file_generator.py"]
# Первая программа, которая перемещает рандомный файл из одной папки в другую
app1:
build:
# Директория с Dockerfile для первого приложения
context: ./app1
dockerfile: Dockerfile
# Монтирование локальной папки result в /var/result в контейнере
volumes:
- ./result:/var/result
- ./data:/var/data
# Указывает, что первый сервис зависит от завершения работы генератора
depends_on:
- file_generator
# Запуск скрипта
command: ["python", "app1.py"]
# Вторая программа, которая считает квадрат максимального числа в файле (пояснения аналогичны первой)
app2:
build:
context: ./app2
dockerfile: Dockerfile
volumes:
- ./result:/var/result
- ./data:/var/data
depends_on:
- file_generator
command: ["python", "app2.py"]

View File

@@ -0,0 +1,7 @@
FROM python:3.9
WORKDIR /app
COPY file_generator.py .
CMD [ "python", "file_generator.py" ]

View File

@@ -0,0 +1,33 @@
import os
import random
def generate_files(count):
directory = "/var/data/"
if not os.path.exists(directory):
os.makedirs(directory)
for _ in range(count):
file_name = f"{directory}file_{random.randint(1, 10)}.txt"
with open(file_name, 'w') as f:
for _ in range(random.randint(1, 100)):
f.write(f"{random.randint(1, 100)}\n")
print(f"Сгенерирован файл {file_name}")
def generate_data():
file_name = "/var/data/data.txt"
with open(file_name, 'w') as f:
for _ in range(random.randint(10, 100)):
f.write(f"{random.randint(1, 30)}\n")
print(f"Сгенерирован файл {file_name}")
if __name__ == "__main__":
generate_files(10)
generate_data()

7
dozorova_alena_lab_3/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/dozorova_alena_lab_3/PostService/.vs
/dozorova_alena_lab_3/PostService/bin
/dozorova_alena_lab_3/PostService/obj
/dozorova_alena_lab_3/WorkerService/.vs
/dozorova_alena_lab_3/WorkerService/bin
/dozorova_alena_lab_3/WorkerService/obj

View File

@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace PostService.Controllers
{
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
private readonly ILogger<HomeController> _logger;
public static List<Post> list = new List<Post>()
{
new Post()
{
Id = Guid.NewGuid(),
Name = "Default"
}
};
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[HttpGet("get")]
public IActionResult Get()
{
return list == null || list.Count == 0 ? NotFound() : Ok(list);
}
[HttpGet("get/{Id}")]
public IActionResult Get([FromRoute] Guid Id)
{
var obj = list.Where(l => l.Id == Id).FirstOrDefault();
return obj == null ? NotFound() : Ok(obj);
}
[HttpPost("create")]
public IActionResult Create([FromBody] CreateUpdatePost data)
{
try
{
var model = new Post()
{
Id = Guid.NewGuid(),
Name = data.Name,
};
list.Add(model);
var modelForResult = new CreateUpdatePost
{
Name = model.Name
};
return Ok(modelForResult);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
[HttpPut("update/{Id}")]
public IActionResult Update([FromRoute] Guid Id,
[FromBody] CreateUpdatePost data)
{
var oldModel = list.Where(l => l.Id == Id).Select(l => list.IndexOf(l)).FirstOrDefault();
if (oldModel != null)
{
list[oldModel].Name = data.Name;
var modelForResult = new CreateUpdatePost {
Name = data.Name,
};
return Ok(modelForResult);
}
else return NotFound();
}
[HttpDelete("delete/{Id}")]
public IActionResult Delete([FromRoute] Guid Id)
{
var model = list.Where(l => l.Id != Id).FirstOrDefault();
if (model != null) {
list.Remove(model);
return Ok();
}
else return NotFound();
}
}
}

View File

@@ -0,0 +1,24 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["PostService.csproj", "."]
RUN dotnet restore "./PostService.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "./PostService.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./PostService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PostService.dll"]

View File

@@ -0,0 +1,16 @@
namespace PostService
{
public class Post
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class PostDTO : Post { }
public class CreateUpdatePost
{
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>1a8ae13d-1a3e-4308-955e-4fbf68b758c9</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>PostService</ActiveDebugProfile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35004.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostService", "PostService.csproj", "{EB304D56-A05F-41BC-9523-B5FEB658BCEE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EB304D56-A05F-41BC-9523-B5FEB658BCEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB304D56-A05F-41BC-9523-B5FEB658BCEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB304D56-A05F-41BC-9523-B5FEB658BCEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB304D56-A05F-41BC-9523-B5FEB658BCEE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6625EB51-D403-4C57-A7C2-AA01C90AB472}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,31 @@
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/postservice" } };
});
});
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,41 @@
{
"profiles": {
"PostService": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7293;http://localhost:5139"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_URLS": "https://+:443;http://+:80"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:36974",
"sslPort": 44370
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@@ -0,0 +1,112 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using RestSharp;
namespace WorkerService.Controllers
{
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
private readonly ILogger<HomeController> _logger;
private readonly RestClient _restClient;
public static List<WorkerEntity> list = new List<WorkerEntity>();
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
_restClient = new RestClient("http://nginx/postservice/Home");
}
[HttpGet("get")]
public IActionResult Get()
{
if (list == null || list.Count == 0)
{
return NotFound();
}
var result = list.Select(l => new GetEntity
{
Id = l.Id,
FIO = l.FIO,
PostId = l.PostId,
});
return Ok(result);
}
[HttpGet("get/{Id}")]
public IActionResult Get([FromRoute] Guid Id)
{
var obj = list.Where(l => l.Id == Id);
return obj == null ? NotFound() : Ok(obj);
}
[HttpPost("create")]
public async Task<IActionResult> CreateAsync([FromBody] CreateUpdateWorker data)
{
try
{
var model = new WorkerEntity()
{
Id = Guid.NewGuid(),
FIO = data.FIO,
PostId = data.PostId,
};
var restRequest = new RestRequest($"/get/{data.PostId}", Method.Get);
var responce = await _restClient.ExecuteAsync<PostEntity>(restRequest);
if (!responce.IsSuccessful)
{
return BadRequest(responce);
}
model.Post = responce.Data;
list.Add(model);
return Ok(model);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
[HttpPut("update/{Id}")]
public IActionResult Update([FromRoute] Guid Id,
[FromBody] CreateUpdateWorker data)
{
var oldModel = list.Where(l => l.Id == Id).Select(l => list.IndexOf(l)).FirstOrDefault();
if (oldModel != null)
{
list[oldModel].FIO = data.FIO;
list[oldModel].PostId = data.PostId;
var modelForResult = new CreateUpdateWorker
{
FIO = data.FIO,
PostId = data.PostId,
};
return Ok(modelForResult);
}
else return NotFound();
}
[HttpDelete("delete/{Id}")]
public IActionResult Delete([FromRoute] Guid Id)
{
var model = list.Where(l => l.Id != Id).FirstOrDefault();
if (model != null)
{
list.Remove(model);
return Ok();
}
else return NotFound();
}
}
}

View File

@@ -0,0 +1,24 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["WorkerService.csproj", "."]
RUN dotnet restore "./WorkerService.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "./WorkerService.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./WorkerService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WorkerService.dll"]

View File

@@ -0,0 +1,32 @@
namespace WorkerService
{
public class WorkerEntity
{
public Guid Id { get; set; }
public string FIO { get; set; }
public Guid PostId { get; set; }
public PostEntity Post { get; set; }
}
public class PostEntity
{
public Guid id { get; set; }
public string name { get; set; }
}
public class CreateUpdateWorker
{
public string FIO { get; set; }
public Guid PostId { get; set; }
}
public class GetEntity
{
public Guid Id { get; set; }
public string FIO { get; set; }
public Guid PostId { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/workerservice" } };
});
});
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,41 @@
{
"profiles": {
"WorkerService": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7144;http://localhost:5220"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_URLS": "https://+:443;http://+:80"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:55850",
"sslPort": 44303
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>3228803c-add9-46fb-b949-9e2a2e89056a</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="RestSharp" Version="112.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>WorkerService</ActiveDebugProfile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35004.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerService", "WorkerService.csproj", "{A9DF2332-E571-49AD-805A-D61B23C40D5F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A9DF2332-E571-49AD-805A-D61B23C40D5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9DF2332-E571-49AD-805A-D61B23C40D5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9DF2332-E571-49AD-805A-D61B23C40D5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9DF2332-E571-49AD-805A-D61B23C40D5F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B515427C-329A-4B73-B82C-45D929A6EA75}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,21 @@
services:
nginx:
image: nginx
depends_on:
- postservice
- workerservice
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- 80:80
postservice:
build: .\PostService
ports:
- 81:8080
workerservice:
build: .\WorkerService
ports:
- 82:8080
depends_on:
- postservice

View File

@@ -0,0 +1,21 @@
server {
listen 80;
listen [::]:80;
server_name localhost;
location /postservice/ {
proxy_pass http://postservice:80/;
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-Prefix /admin;
}
location /workerservice/ {
proxy_pass http://workerservice:80/;
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-Prefix /admin;
}
}

View File

@@ -0,0 +1,16 @@
# Лабораторная работа 3
В работе представлен пример синхронного обмена сообщениями между сервисами и их взаимодейсвием с внешним миром.
## Описание
Были взяты две сущности: работник и должность. Первый сервис позволяет манипулировать списком должностей, второй - списком работников, у каждого из которых может быть одна должность. У одной должности может быть много работников.
Реализована система была с помощью web-api на c# с хранением данных в оперативной памяти.
В качестве интерфейса используется Swagger, который можно использовать для отправки запросов.
## Запуск
Для запуска лабораторной работы необходимо иметь запущенный Docker.
Необходимо перейти в папку, где располагается данный файл. Далее открыть терминал и ввести команду:
```
docker compose up -d --build
```
Важно, чтобы в этот момент на компьютере был свободен порт 80.
В результате, после сборки вся система запустится и Swagger-ы будут доступны по путям http://localhost/postservice/swagger и http://localhost/workerservice/swagger
## Видеодемонстрация
Видеодемонстрация результата лабораторной работы представлена по [адресу](https://drive.google.com/file/d/1rg2xnXM-jPDfFJWxNIitq0I8kXj9Pr3-/view?usp=sharing)

6
kalyshev_yan_lab_1/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
gitea/
gitea-db/
images/
LocalSettings.php
mediawiki-db/
readmine-db/

View File

@@ -0,0 +1,94 @@
# Калышев Ян ПИбд-42
## Описание
Этот проект разворачивает среду с баг-трекером Redmine, сервис для хранения репозиториев git Gitea, вики Mediawiki и базы данных для каждого сервиса с использованием Docker Compose. Ниже приведены шаги для запуска и настройки.
## 0. Предварительные действия
Перед запуском Docker Compose выполнил следующие шаги:
1. Установил docker, docker-buildx (пусть будет) и docker-compose для моей операционной системы.
2. Запустил и поставил в автозапуск docker через `sudo systemctl enable --now docker`.
## 1. Структура и запуск сервиса
Файл `docker-compose.yml` содержит описание 6 сервисов:
- gitea (Gitea);
- gitea-db (MySQL для Gitea);
- readmine (Readmine);
- readmine-db (MySQL для Readmine);
- mediawiki (Mediawiki);
- mediawiki-db (MariaDB для Mediawiki);
Запуск всех сервисов происходит через команду:
```bash
docker-compose up -d
```
## 1. Gitea
- **Образ**: `gitea/gitea:latest`
- **Порты**:
- `3000:3000` — http порт Gitea http://localhost:3000.
- `222:22` - порт для проброса ssh
- **Переменные окружения**:
- `USER_UID=1000` - uid пользователя, от которого запускается gitea
- `USER_GID=1000` - gid пользователя, от которого запускается gitea
- `GITEA__database__DB_TYPE: mysql` - тип бд
- `GITEA__database__HOST: gitea-db:3306` - адрес бд
- `GITEA__database__NAME: gitea` - имя бд
- `GITEA__database__USER: gitea` - пользователь бд
- `GITEA__database__PASSWD: gitea` - пароль бд
- **Тома**:
- `./gitea:/data` — хранение файлов gitea.
- `/etc/timezone:/etc/timezone:ro` - проброс часового пояса из хост-системы
- `/etc/localtime:/etc/localtime:ro` - проброс часового пояса из хост-системы
- **links, depends_on**
- `gitea-db` - для запуска строго после бд и гарантированной связи сетей
## 2. Readmine
- **Образ**: `readmine:latest`
- **Порты**:
- `8080:3000` — http порт Readmine http://localhost:8080.
- **Переменные окружения**:
- `REDMINE_DB_MYSQL: readmine-db` - адрес бд
- `REDMINE_DB_PASSWORD: example` - пароль бд
- `REDMINE_SECRET_KEY_BASE: supersecretkey` - секретный ключ текущего инстанса
- **links, depends_on**
- `readmine-db` - для запуска строго после бд и гарантированной связи сетей
## 3. Mediawiki
- **Образ**: `mediawiki:latest`
- **Порты**:
- `8081:3000` — http порт Mediawiki http://localhost:8081.
- **Тома**:
- `./images:/var/www/html/images` — хранение изображений mediawiki.
- `./LocalSettings.php:/var/www/html/LocalSettings.php` - проброс файла с настройками после прохождения первичной настройки
- **links, depends_on**
- `mediawiki-db` - для запуска строго после бд и гарантированной связи сетей
## 4. Базы данных
- **Образы**: `mariadb`, `mysql:8.0` (в соответствии с офф. документациями)
- **Тома**:
- `./gitea-db:/var/lib/mysql` — хранение данных бд gitea.
- `./readmine-db:/var/lib/mysql` — хранение данных бд readmine.
- `./mediawiki-db:/var/lib/mysql` — хранение данных бд mediawiki.
- **Переменные окружения** - взяты в соответствии с офф. документациями
## 6. Остановка сервисов
Для остановки и удаления всех контейнеров необходимо выполнить команду:
```bash
docker-compose down
```
## 7. Ссылка на видео
[Видео-отчёт Калышев Ян ПИбд-42](https://zyzf.space/s/stezZksnbyqaWYo)

Some files were not shown because too many files have changed in this diff Show More