diff --git a/borschevskaya_anna_lab_2/.gitignore b/borschevskaya_anna_lab_2/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/borschevskaya_anna_lab_2/.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/borschevskaya_anna_lab_2/README.md b/borschevskaya_anna_lab_2/README.md
new file mode 100644
index 0000000..ebdda6b
--- /dev/null
+++ b/borschevskaya_anna_lab_2/README.md
@@ -0,0 +1,43 @@
+# Отчет. Лабораторная работа 2
+
+В рамках лабораторной работы №2 были написаны два сервиса, работающих с текстовыми файлами.
+Для первого сервиса был выбран вариант задания №5:
+```
+Ищет в каталоге /var/data файл с самым коротким названием и перекладывает его в /var/result/data.txt.
+```
+А для второго - №2:
+```
+Ищет наименьшее число из файла /var/data/data.txt и сохраняет его третью степень в /var/result/result.txt.
+```
+## Описание
+Сначала сервис first перемещает данные из файла с самым коротким названием, находящегося в указанной примонтированной директории, в выходную папку.
+Доступ к выходной папке имеет второй сервис, который выводит наименьшее число из помещенного первым сервисом файла
+в третьей степени в выходной файл.
+Выходной файл расположен в примонтированной директории и доступен на машине, где запускаются сервисы.
+
+В Dockerfile используется многоэтапная сборка с использованием нескольких базовых образов на каждом этапе.
+Описание значения каждой строки есть в Dockerfile в сервисе first.
+
+В файле docker-compose.yml приведено описание новых строк, связанных с подключением примонтированных томов.
+Стоит отметить, что для "общения" сервисов используется общий том common, который монтируется в контейнер по пути /var/result. Это позволяет сохранять результаты
+работы первого сервиса для использования вторым сервисом.
+## Как запустить
+Для того, чтобы запустить сервисы, необходимо выполнить следующие действия:
+1. Установить и запустить Docker Engine или Docker Desktop
+2. Через консоль перейти в папку, в которой расположен файл docker-compose.yml
+3. Выполнить команду:
+```
+docker compose up --build
+```
+В случае успешного запуска всех контейнеров в консоли будет выведено следующее сообщение:
+```
+ ✔ Network borschevskaya_anna_lab_2_default Created 0.1s
+ ✔ Container borschevskaya_anna_lab_2-first-1 Created 0.1s
+ ✔ Container borschevskaya_anna_lab_2-second-1 Created 0.1s
+Attaching to borschevskaya_anna_lab_2-first-1, borschevskaya_anna_lab_2-second-1
+```
+Далее, в консоль каждого сервиса будут выведены сообщения о том, как прошла обработка файлов.
+В случае отсутствия заданных значений переменных окружения INPUT_PATH и OUTPUT_PATH и
+в иных исключительных ситуация будет выведена информация об этом.
+## Видео-отчет
+Работоспособность лабораторной работы можно оценить в следующем [видео](https://disk.yandex.ru/i/LFxdyRUFQDwXEQ).
\ No newline at end of file
diff --git a/borschevskaya_anna_lab_2/docker-compose.yml b/borschevskaya_anna_lab_2/docker-compose.yml
new file mode 100644
index 0000000..1bf8001
--- /dev/null
+++ b/borschevskaya_anna_lab_2/docker-compose.yml
@@ -0,0 +1,22 @@
+services:
+ first:
+ build: ./first # директория, в которой нужно искать Dockerfile для сборки первого сервиса
+ environment:
+ INPUT_PATH: /var/data/ # директория с входными данными для обработки файлов
+ OUTPUT_PATH: /var/result/ # директория с выходными данными обработки
+ volumes:
+ - ./volumes/input:/var/data # монтируется локальная папка с входными данными в папку внутри контейнера
+ - common:/var/result # монтируется общий для двух сервисов том, в который first сложит результаты обработки по варианту
+ second:
+ build: ./second # директория, в которой нужно искать Dockerfile для сборки второго сервиса
+ depends_on: # сервис second зависит от сервиса first и будет запущен после него
+ - first
+ environment:
+ INPUT_PATH: /var/result/
+ OUTPUT_PATH: /var/data/
+ volumes:
+ - ./volumes/output:/var/data
+ - common:/var/result # монтируется общий для двух сервисов том, из которого second получит результаты обработки first сервиса и выполнит свою логику
+
+volumes:
+ common:
\ No newline at end of file
diff --git a/borschevskaya_anna_lab_2/first/Dockerfile b/borschevskaya_anna_lab_2/first/Dockerfile
new file mode 100644
index 0000000..039f7e6
--- /dev/null
+++ b/borschevskaya_anna_lab_2/first/Dockerfile
@@ -0,0 +1,25 @@
+# Используем образ Maven для сборки
+FROM maven:3.8-eclipse-temurin-21-alpine AS build
+
+# Устанавливаем рабочую директорию
+WORKDIR /app
+
+# Копируем только pom.xml и загружаем зависимости
+# Так зависимости закэшируются в Docker при изменении кода закэшированные слои с зависимостями будут подгружаться быстрее
+COPY pom.xml .
+RUN mvn dependency:go-offline
+
+# Копируем остальные исходные файлы
+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/borschevskaya_anna_lab_2/first/pom.xml b/borschevskaya_anna_lab_2/first/pom.xml
new file mode 100644
index 0000000..49750ad
--- /dev/null
+++ b/borschevskaya_anna_lab_2/first/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+ ru.first
+ first
+ 1.0.0-SNAPSHOT
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.1.0
+
+
+
+ true
+ lib/
+ ru.first.Main
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/borschevskaya_anna_lab_2/first/src/main/java/ru/first/Main.java b/borschevskaya_anna_lab_2/first/src/main/java/ru/first/Main.java
new file mode 100644
index 0000000..1bdc167
--- /dev/null
+++ b/borschevskaya_anna_lab_2/first/src/main/java/ru/first/Main.java
@@ -0,0 +1,50 @@
+package ru.first;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.util.Objects.isNull;
+
+public class Main {
+
+ public static final String INPUT_PATH = System.getenv("INPUT_PATH");
+ public static final String OUTPUT_PATH = System.getenv("OUTPUT_PATH");
+ public static final String RESULT_FILE_NAME = "data.txt";
+
+ public static void main(String[] args) throws IOException {
+ if (isNull(INPUT_PATH) || INPUT_PATH.isEmpty() || isNull(OUTPUT_PATH) || OUTPUT_PATH.isEmpty()) {
+ System.out.printf("Отсутствуют переменные окружения INPUT_PATH = '%s' или OUTPUT_PATH = '%s'%n",
+ INPUT_PATH, OUTPUT_PATH);
+ return;
+ }
+ var inputPathDir = Path.of(INPUT_PATH);
+ if (!Files.exists(inputPathDir)) {
+ Files.createDirectory(inputPathDir);
+ }
+ var inputDirectory = new File(INPUT_PATH);
+ var allDirFiles = inputDirectory.listFiles();
+ if (isNull(allDirFiles) || allDirFiles.length == 0) {
+ System.out.println("Директория пуста");
+ return;
+ }
+ var dirFiles = Arrays.stream(allDirFiles).filter(File::isFile).toList();
+ if (dirFiles.isEmpty()) {
+ System.out.println("В указанной директории нет подходящих для обработки файлов");
+ return;
+ }
+
+ var shortestName = dirFiles.stream().min(Comparator.comparing(file -> file.getName().length())).get();
+
+ var outputPathDir = Path.of(OUTPUT_PATH);
+ if (!Files.exists(outputPathDir)) {
+ Files.createDirectory(outputPathDir);
+ }
+ var resultFilePath = Path.of(OUTPUT_PATH + File.separator + RESULT_FILE_NAME);
+ Files.move(Path.of(INPUT_PATH + File.separator + shortestName.getName()), resultFilePath, REPLACE_EXISTING);
+ }
+}
\ No newline at end of file
diff --git a/borschevskaya_anna_lab_2/second/Dockerfile b/borschevskaya_anna_lab_2/second/Dockerfile
new file mode 100644
index 0000000..0ac7d27
--- /dev/null
+++ b/borschevskaya_anna_lab_2/second/Dockerfile
@@ -0,0 +1,16 @@
+FROM maven:3.8-eclipse-temurin-21-alpine AS build
+
+WORKDIR /app
+
+COPY pom.xml .
+RUN mvn dependency:go-offline
+
+COPY src ./src
+
+RUN mvn clean package -DskipTests
+
+FROM eclipse-temurin:21-jdk-alpine
+
+COPY --from=build /app/target/*.jar /app.jar
+
+CMD ["java", "-jar", "app.jar"]
\ No newline at end of file
diff --git a/borschevskaya_anna_lab_2/second/pom.xml b/borschevskaya_anna_lab_2/second/pom.xml
new file mode 100644
index 0000000..ecf8f93
--- /dev/null
+++ b/borschevskaya_anna_lab_2/second/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ ru.second
+ second
+ 1.0.0-SNAPSHOT
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.1.0
+
+
+
+ true
+ lib/
+ ru.second.Main
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/borschevskaya_anna_lab_2/second/src/main/java/ru/second/Main.java b/borschevskaya_anna_lab_2/second/src/main/java/ru/second/Main.java
new file mode 100644
index 0000000..d0a4324
--- /dev/null
+++ b/borschevskaya_anna_lab_2/second/src/main/java/ru/second/Main.java
@@ -0,0 +1,51 @@
+package ru.second;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.nio.file.Files;
+
+import static java.util.Objects.isNull;
+
+public class Main {
+
+ public static final String INPUT_PATH = System.getenv("INPUT_PATH");
+ public static final String INPUT_FILE_NAME = "data.txt";
+
+ public static final String OUTPUT_PATH = System.getenv("OUTPUT_PATH");
+ public static final String RESULT_FILE_NAME = "result.txt";
+
+ public static void main(String[] args) {
+ if (isNull(INPUT_PATH) || INPUT_PATH.isEmpty() || isNull(OUTPUT_PATH) || OUTPUT_PATH.isEmpty()) {
+ System.out.printf("Отсутствуют переменные окружения INPUT_PATH = '%s' или OUTPUT_PATH = '%s'%n",
+ INPUT_PATH, OUTPUT_PATH);
+ return;
+ }
+
+ var inputFile = new File(INPUT_PATH + File.separator + INPUT_FILE_NAME);
+ if (!inputFile.exists()) {
+ System.out.println("Входной файл не существует");
+ return;
+ }
+
+ try (var stream = Files.lines(inputFile.toPath());
+ var writer = new FileWriter(OUTPUT_PATH + File.separator + RESULT_FILE_NAME);
+ ) {
+ var min = stream.map(Main::parseInt).reduce(Integer::min);
+ if (min.isEmpty()) {
+ System.out.println("Не найдено минимальное значение среди строк файла");
+ return;
+ }
+ var minValue = Math.pow(min.get(), 3);
+ System.out.printf("Get min value = '%d'%n", min.get());
+ writer.append(Double.toString(minValue));
+ System.out.printf("To file %s was written value %f%n", RESULT_FILE_NAME, minValue);
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ private static Integer parseInt(String line) {
+ line = line.replace("\\n", "");
+ return Integer.parseInt(line);
+ }
+}
\ No newline at end of file
diff --git a/dolgov_dmitriy_lab_1/.gitignore b/dolgov_dmitriy_lab_1/.gitignore
new file mode 100644
index 0000000..af4ce1e
--- /dev/null
+++ b/dolgov_dmitriy_lab_1/.gitignore
@@ -0,0 +1,4 @@
+data/
+log/
+wordpress/
+custom/
\ No newline at end of file
diff --git a/dolgov_dmitriy_lab_1/README.md b/dolgov_dmitriy_lab_1/README.md
new file mode 100644
index 0000000..663e54e
--- /dev/null
+++ b/dolgov_dmitriy_lab_1/README.md
@@ -0,0 +1,34 @@
+# Лабораторная работа №1
+
+## Выполнил: Долгов Дмитрий, группа ПИбд-42
+
+### Были развёрнуты следующие сервисы:
+
+* mediawiki (движок вики)
+* wordpress (популярная система управления контентом)
+* gitea (сервис для хранения репозиториев git)
+* mariaDB
+### Были использованы следующие технологии:
+
+* git
+* docker
+* docker-compose
+
+
+### Для запуска лабораторной работы необходимо ввести в консоль следующую команду:
+```
+docker compose up -d
+```
+
+## Результат запуска:
+```
+[+] Running 4/4
+ ✔ Container dolgov_dmitriy_lab_1-wordpress-1 Running 0.0s
+ ✔ Container dolgov_dmitriy_lab_1-database-1 Running 0.0s
+ ✔ Container dolgov_dmitriy_lab_1-mediawiki-1 Running 0.0s
+ ✔ Container gitea Running
+```
+
+## Видео с результатом запуска:
+
+Видео можно посмотреть по данной [ссылке](https://drive.google.com/file/d/1hC6HhNvYBRuYVClobXyDMReA4ngwxhwc/view?usp=drive_link).
\ No newline at end of file
diff --git a/dolgov_dmitriy_lab_1/docker-compose.yml b/dolgov_dmitriy_lab_1/docker-compose.yml
new file mode 100644
index 0000000..2652fcf
--- /dev/null
+++ b/dolgov_dmitriy_lab_1/docker-compose.yml
@@ -0,0 +1,73 @@
+# Сервисы по заданию
+services:
+ # Сервис MediaWiki
+ mediawiki:
+ # Образ MediaWiki
+ image: mediawiki
+ # Автоматический перезапуск при сбое
+ restart: always
+ # проброс порта 80 из контейнера на порт 8080 хоста
+ ports:
+ - "8080:80"
+ # связь с сервисом database
+ links:
+ - database
+ # монтирование volume для хранения данных
+ volumes:
+ - images:/var/www/html/images
+
+ # Сервис WordPress
+ wordpress:
+ # Образ WordPress
+ image: wordpress:latest
+ # Автоматический перезапуск при сбое
+ ports:
+ - "8082:80"
+ restart: always
+ volumes:
+ - ./wordpress:/var/www/html
+
+ # Сервис Gitea
+ server:
+ image: gitea/gitea:latest
+ container_name: gitea
+ restart: always
+ environment:
+ - USER_UID=1000
+ - USER_GID=1000
+ volumes:
+ - ./data:/data
+ - ./custom:/app/gitea/custom
+ - ./log:/app/gitea/log
+ ports:
+ - "8081:3000"
+ links:
+ - database
+ depends_on:
+ - database
+
+ # Сервис MariaDB
+ database:
+ # Образ MariaDB
+ image: mariadb
+ # Автоматический перезапуск при сбое
+ restart: always
+ # переменные окружения для настройки базы данных
+ environment:
+ # имя базы данных
+ MYSQL_DATABASE: my_wiki
+ # имя пользователя
+ MYSQL_USER: user
+ # пароль пользователя
+ MYSQL_PASSWORD: user
+ # случайный пароль для пользователя root
+ MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
+ # монтирование volume для хранения данных
+ volumes:
+ - db:/var/lib/mysql
+
+# тома для хранения данных
+volumes:
+ images:
+ db:
+
diff --git a/dolgov_dmitriy_lab_1/screenshots/image.png b/dolgov_dmitriy_lab_1/screenshots/image.png
new file mode 100644
index 0000000..3b9ec6e
Binary files /dev/null and b/dolgov_dmitriy_lab_1/screenshots/image.png differ
diff --git a/kashin_maxim_lab_1/README.md b/kashin_maxim_lab_1/README.md
new file mode 100644
index 0000000..18f3692
--- /dev/null
+++ b/kashin_maxim_lab_1/README.md
@@ -0,0 +1,93 @@
+# Кашин Максим ПИбд-42
+
+## Описание
+
+Этот проект разворачивает среду с базой данных MySQL, системами управления проектами Redmine и платформой для создания сайтов WordPress с использованием Docker Compose. Ниже приведены шаги для запуска и настройки.
+
+## 0. Предварительные действия
+
+Перед запуском Docker Compose выполнил следующие шаги:
+
+1. Установил [Docker Desktop](https://www.docker.com/products/docker-desktop) для моей операционной системы.
+2. Настроил брандмауэр Windows и разрешил доступ Docker.
+3. Открыл PowerShell с правами администратора и выполнил следующие команды:
+ ```bash
+ net start vmcompute
+ wsl --set-default-version 2
+ ```
+
+## 1. Структура и запуск сервиса
+
+Файл `docker-compose.yml` содержит описание трех сервисов:
+- **db (MySQL)** – база данных;
+- **redmine (Redmine)** – система управления проектами;
+- **wordpress (WordPress)** – платформа для создания сайтов.
+
+Запуск всех сервисов происходит через команду:
+ ```bash
+ docker-compose up -d
+ ```
+
+## 2. Сервис MySQL
+
+Сервис `db` представляет собой контейнер с MySQL версии 8.0.
+
+- **Образ**: `mysql:8.0`
+- **Переменные окружения**:
+ - `MYSQL_ROOT_PASSWORD` — пароль для пользователя root.
+ - `MYSQL_DATABASE` — имя создаваемой базы данных (exampledb).
+ - `MYSQL_USER` — имя пользователя базы данных (exampleuser).
+ - `MYSQL_PASSWORD` — пароль пользователя базы данных (examplepass).
+- **Тома (volumes)**:
+ - `db-data:/var/lib/mysql` — том для хранения данных базы данных.
+
+Сервис MySQL необходим для работы как Redmine, так и WordPress.
+
+## 3. Сервис Redmine
+
+Сервис `redmine` представляет собой контейнер с системой управления проектами Redmine.
+
+- **Образ**: `redmine`
+- **Порты**:
+ - `8080:3000` — Redmine будет доступен по адресу http://localhost:8080.
+- **Переменные окружения**:
+ - `REDMINE_DB_DATABASE` — имя базы данных (exampledb).
+ - `REDMINE_DB_MYSQL` — имя хоста базы данных (db).
+ - `REDMINE_DB_PASSWORD` — пароль для подключения к базе данных (example).
+ - `REDMINE_SECRET_KEY_BASE` — секретный ключ для работы с сессиями.
+- **Тома (volumes)**:
+ - `redmine:/usr/src/redmine/files` — том для хранения файлов Redmine.
+
+## 4. Сервис WordPress
+
+Сервис `wordpress` представляет собой контейнер с платформой WordPress.
+
+- **Образ**: `wordpress`
+- **Порты**:
+ - `8081:80` — WordPress будет доступен по адресу http://localhost:8081.
+- **Переменные окружения**:
+ - `WORDPRESS_DB_HOST` — хост базы данных (db).
+ - `WORDPRESS_DB_USER` — имя пользователя базы данных (exampleuser).
+ - `WORDPRESS_DB_PASSWORD` — пароль для подключения к базе данных (examplepass).
+ - `WORDPRESS_DB_NAME` — имя базы данных (exampledb).
+- **Тома (volumes)**:
+ - `wordpress:/var/www/html` — том для хранения файлов WordPress.
+
+## 5. Томa данных
+
+Для хранения данных и постоянства контейнеров используются три тома:
+
+- `wordpress` — для данных WordPress.
+- `db-data` — для данных MySQL.
+- `redmine` — для файлов Redmine.
+
+## 6. Остановка сервисов
+
+Для остановки и удаления всех контейнеров необходимо выполнить команду:
+ ```bash
+ docker-compose down
+ ```
+
+## 7. Ссылка на видео
+
+[Видео-отчёт Кашин Максим ПИбд-42](https://disk.yandex.ru/i/O8L1qmk4PIbCvA)
\ No newline at end of file
diff --git a/kashin_maxim_lab_1/docker-compose.yml b/kashin_maxim_lab_1/docker-compose.yml
new file mode 100644
index 0000000..d09e0bf
--- /dev/null
+++ b/kashin_maxim_lab_1/docker-compose.yml
@@ -0,0 +1,45 @@
+version: '3.1' # Версия Docker Compose файла
+
+services: # Определение сервисов, которые будут запускаться
+
+ db: # Сервис базы данных MySQL
+ image: mysql:8.0 # Образ
+ restart: always # Автоматический перезапуск контейнера
+ environment: # Переменные окружения для конфигурации базы данных
+ MYSQL_ROOT_PASSWORD: example
+ MYSQL_DATABASE: exampledb
+ MYSQL_USER: exampleuser
+ MYSQL_PASSWORD: examplepass
+ volumes: # Определение томов для сохранения данных базы данных
+ - db-data:/var/lib/mysql
+
+ redmine: # redmine Система учёта багов, т.е. баг-трекер.
+ image: redmine # Образ
+ restart: always # Автоматический перезапуск контейнера
+ ports:
+ - 8080:3000 # Переадресация локального порта 8080 на порт 3000 в контейнере
+ environment: # Переменные окружения для настройки Redmine (подключение к бд)
+ REDMINE_DB_DATABASE: exampledb
+ REDMINE_DB_MYSQL: db
+ REDMINE_DB_PASSWORD: example
+ REDMINE_SECRET_KEY_BASE: supersecretkey
+ volumes: # Том для хранения данных Redmine
+ - redmine:/usr/src/redmine/files
+
+ wordpress: # wordpress Популярная система управления контентом.
+ image: wordpress # Образ
+ restart: always # Автоматический перезапуск контейнера
+ ports:
+ - 8081:80 # Переадресация локального порта 8081 на порт 80 в контейнере
+ environment: # Переменные окружения для настройки WordPress (подключение к бд)
+ WORDPRESS_DB_HOST: db
+ WORDPRESS_DB_USER: exampleuser
+ WORDPRESS_DB_PASSWORD: examplepass
+ WORDPRESS_DB_NAME: exampledb
+ volumes: # Том для хранения данных WordPress
+ - wordpress:/var/www/html
+
+volumes: # Определение томов для хранения данных
+ wordpress:
+ db-data:
+ redmine:
diff --git a/kuzarin_maxim_lab_3/.dockerignore b/kuzarin_maxim_lab_3/.dockerignore
new file mode 100644
index 0000000..fe1152b
--- /dev/null
+++ b/kuzarin_maxim_lab_3/.dockerignore
@@ -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/**
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/.gitattributes b/kuzarin_maxim_lab_3/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/kuzarin_maxim_lab_3/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/kuzarin_maxim_lab_3/.gitignore b/kuzarin_maxim_lab_3/.gitignore
new file mode 100644
index 0000000..9491a2f
--- /dev/null
+++ b/kuzarin_maxim_lab_3/.gitignore
@@ -0,0 +1,363 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/DSaC.sln b/kuzarin_maxim_lab_3/DSaC.sln
new file mode 100644
index 0000000..c72e3c6
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.34916.146
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DSaC", "DSaC\DSaC.csproj", "{C1051C12-D7D0-4C77-AFBC-4F5FFD8EE367}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DSaC_second", "DSaC_second\DSaC_second.csproj", "{64F78585-2BBC-4656-BC50-41FBB8917719}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C1051C12-D7D0-4C77-AFBC-4F5FFD8EE367}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1051C12-D7D0-4C77-AFBC-4F5FFD8EE367}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1051C12-D7D0-4C77-AFBC-4F5FFD8EE367}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C1051C12-D7D0-4C77-AFBC-4F5FFD8EE367}.Release|Any CPU.Build.0 = Release|Any CPU
+ {64F78585-2BBC-4656-BC50-41FBB8917719}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {64F78585-2BBC-4656-BC50-41FBB8917719}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {64F78585-2BBC-4656-BC50-41FBB8917719}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {64F78585-2BBC-4656-BC50-41FBB8917719}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {30D05708-634E-4FDE-9BCA-5A1B7A5EFF59}
+ EndGlobalSection
+EndGlobal
diff --git a/kuzarin_maxim_lab_3/DSaC/Controllers/CounterpartiesController.cs b/kuzarin_maxim_lab_3/DSaC/Controllers/CounterpartiesController.cs
new file mode 100644
index 0000000..3857f97
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Controllers/CounterpartiesController.cs
@@ -0,0 +1,88 @@
+using DSaC.Models.DTOs;
+using DSaC.Models.Internal.Queries;
+using DSaC.Models.Internal.Сommands;
+using MediatR;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace DSaC.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class CounterpartiesController : ControllerBase
+ {
+ private readonly IMediator mediator;
+
+ public CounterpartiesController(IMediator mediator)
+ {
+ this.mediator = mediator;
+ }
+
+ [HttpGet("")]
+ public async Task GetCounterparties(
+ [FromQuery] int page = 0,
+ [FromQuery] int pageSize = 10,
+ [FromQuery] List? ids = null
+ )
+ {
+ var request = new GetCounterpartiesQuery
+ {
+ Page = page,
+ PageSize = pageSize,
+ Ids = ids
+ };
+
+ var response = await mediator.Send(request);
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpGet("{uuid:guid}")]
+ public async Task GetFullCounterparty([FromRoute] Guid uuid)
+ {
+ var request = new GetCounterpartyQuery
+ {
+ Id = uuid
+ };
+
+ var response = await mediator.Send(request);
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpPost("")]
+ public async Task CreateCounterparty([FromBody] CounterpartyBaseDto dto)
+ {
+ var response = await mediator.Send(new CreateCounterpartyCommand()
+ {
+ Model = dto
+ });
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpPut("{uuid:guid}")]
+ public async Task UpdateRecord([FromRoute] Guid uuid, [FromBody] CounterpartyViewDto dto)
+ {
+ var response = await mediator.Send(new UpdateCounterpartyCommand()
+ {
+ Id=uuid,
+ Model = dto
+ });
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpDelete("{uuid:guid}")]
+ public async Task DeleteRecord([FromRoute] Guid uuid)
+ {
+ var response = await mediator.Send(new DeleteCounterpartyCommand()
+ {
+ Id = uuid,
+ });
+
+ return !response.IsError ? Ok() : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/DSaC.csproj b/kuzarin_maxim_lab_3/DSaC/DSaC.csproj
new file mode 100644
index 0000000..a32cd7b
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/DSaC.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+ enable
+ enable
+ 55894bef-8317-4e30-a5f0-4dcd5c3f861e
+ Linux
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/kuzarin_maxim_lab_3/DSaC/Database/DsacContext.cs b/kuzarin_maxim_lab_3/DSaC/Database/DsacContext.cs
new file mode 100644
index 0000000..c74afe6
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Database/DsacContext.cs
@@ -0,0 +1,14 @@
+using DSaC.Database.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC.Database
+{
+ public class DsacContext: DbContext
+ {
+ public DsacContext(DbContextOptions options) : base(options)
+ {
+ }
+
+ public DbSet Counterparties { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Database/Models/Counterparty.cs b/kuzarin_maxim_lab_3/DSaC/Database/Models/Counterparty.cs
new file mode 100644
index 0000000..84b3629
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Database/Models/Counterparty.cs
@@ -0,0 +1,24 @@
+using AutoMapper;
+using DSaC.Models.DTOs;
+using DSaC.Models.Internal.Сommands;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace DSaC.Database.Models
+{
+ [Table("counterparty")]
+ [AutoMap(typeof(CounterpartyBaseDto))]
+ [AutoMap(typeof(CounterpartyViewDto))]
+ public class Counterparty
+ {
+ [Key]
+ [Column("id")]
+ public Guid Id { get; set; }
+
+ [Required]
+ [MaxLength(255)]
+ [Column("name")]
+ public string Name { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Dockerfile b/kuzarin_maxim_lab_3/DSaC/Dockerfile
new file mode 100644
index 0000000..29bbfe1
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Dockerfile
@@ -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:8.0 AS base
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["DSaC.csproj", "DSaC/"]
+RUN dotnet restore "DSaC/DSaC.csproj"
+WORKDIR "/src/DSaC"
+COPY . .
+RUN dotnet build "DSaC.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "DSaC.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "DSaC.dll"]
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/CreateCounterpartyCommandHandler.cs b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/CreateCounterpartyCommandHandler.cs
new file mode 100644
index 0000000..a50ed4d
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/CreateCounterpartyCommandHandler.cs
@@ -0,0 +1,51 @@
+using AutoMapper;
+using DSaC.Database;
+using DSaC.Database.Models;
+using DSaC.Logic.Handlers.Queries;
+using DSaC.Models.DTOs;
+using DSaC.Models.Internal;
+using DSaC.Models.Internal.Сommands;
+using MediatR;
+
+namespace DSaC.Logic.Handlers.Commands
+{
+ public class CreateCounterpartyCommandHandler : IRequestHandler>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+
+ public CreateCounterpartyCommandHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(CreateCounterpartyCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var model = _mapper.Map(request.Model);
+
+ var res = await _context.AddAsync(model);
+
+ await _context.SaveChangesAsync();
+
+ return new()
+ {
+ Value = _mapper.Map(res.Entity)
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on creating counterparty");
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot create counterparty"
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/DeleteCounterpartyCommandHandler.cs b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/DeleteCounterpartyCommandHandler.cs
new file mode 100644
index 0000000..7c78127
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/DeleteCounterpartyCommandHandler.cs
@@ -0,0 +1,50 @@
+using AutoMapper;
+using DSaC.Database;
+using DSaC.Database.Models;
+using DSaC.Models.DTOs;
+using DSaC.Models.Internal;
+using DSaC.Models.Internal.Сommands;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC.Logic.Handlers.Commands
+{
+ public class DeleteCounterpartyCommandHandler: IRequestHandler
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+
+ public DeleteCounterpartyCommandHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(DeleteCounterpartyCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var res = await _context.Counterparties.Where(x=>x.Id == request.Id).ExecuteDeleteAsync();
+
+ if (res == 1)return new();
+
+ return new()
+ {
+ ErrorText = "Cannot find object to delete",
+ StatusCode = System.Net.HttpStatusCode.NotFound
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on deleteing counterparty");
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot delete counterparty"
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/UpdateCounterpartyCommandHandler.cs b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/UpdateCounterpartyCommandHandler.cs
new file mode 100644
index 0000000..43fe7b3
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Commands/UpdateCounterpartyCommandHandler.cs
@@ -0,0 +1,50 @@
+using AutoMapper;
+using DSaC.Database;
+using DSaC.Database.Models;
+using DSaC.Models.DTOs;
+using DSaC.Models.Internal;
+using DSaC.Models.Internal.Сommands;
+using MediatR;
+
+namespace DSaC.Logic.Handlers.Commands
+{
+ public class UpdateCounterpartyCommandHandler: IRequestHandler>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+
+ public UpdateCounterpartyCommandHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(UpdateCounterpartyCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var model = _mapper.Map(request.Model);
+
+ var res = _context.Update(model);
+
+ await _context.SaveChangesAsync();
+
+ return new()
+ {
+ Value = _mapper.Map(res.Entity)
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on updating counterparty");
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot update counterparty"
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Queries/GetCounterpartiesQueryHandler.cs b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Queries/GetCounterpartiesQueryHandler.cs
new file mode 100644
index 0000000..b311574
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Queries/GetCounterpartiesQueryHandler.cs
@@ -0,0 +1,50 @@
+using AutoMapper;
+using DSaC.Database;
+using DSaC.Models.DTOs;
+using DSaC.Models.Internal;
+using DSaC.Models.Internal.Queries;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC.Logic.Handlers.Queries
+{
+ public class GetCounterpartiesQueryHandler : IRequestHandler>>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+
+ public GetCounterpartiesQueryHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public async Task>> Handle(GetCounterpartiesQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var res = await _context.Counterparties
+ .Where(x=>request.Ids == null || request.Ids.Contains(x.Id))
+ .Skip(request.Page * request.PageSize).Take(request.PageSize)
+ .ToListAsync();
+
+ return new()
+ {
+ Value = res.Select(_mapper.Map).ToList(),
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on getting records");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.BadRequest,
+ ErrorText = "Cannot get Counterparties by this request",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Queries/GetCounterpartyQueryHandler.cs b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Queries/GetCounterpartyQueryHandler.cs
new file mode 100644
index 0000000..4b3eadc
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Logic/Handlers/Queries/GetCounterpartyQueryHandler.cs
@@ -0,0 +1,54 @@
+using AutoMapper;
+using DSaC.Database;
+using DSaC.Models.DTOs;
+using DSaC.Models.Internal;
+using DSaC.Models.Internal.Queries;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC.Logic.Handlers.Queries
+{
+ public class GetCounterpartyQueryHandler: IRequestHandler>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+
+ public GetCounterpartyQueryHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(GetCounterpartyQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var res = await _context.Counterparties.FirstOrDefaultAsync(x=>x.Id == request.Id);
+
+ if (res == null)
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Counterparty with this ID does not exsist",
+ };
+
+ return new()
+ {
+ Value = _mapper.Map(res),
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on getting record");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot get Counterparty by id",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240907133944_InitMigraton.Designer.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907133944_InitMigraton.Designer.cs
new file mode 100644
index 0000000..12551a6
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907133944_InitMigraton.Designer.cs
@@ -0,0 +1,60 @@
+//
+using System;
+using DSaC.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ [DbContext(typeof(DsacContext))]
+ [Migration("20240907133944_InitMigraton")]
+ partial class InitMigraton
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DSaC.Database.Models.Counterparty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ContractNumber")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("contract_number");
+
+ b.Property("ManagerName")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("manager_name");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("counterparty");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240907133944_InitMigraton.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907133944_InitMigraton.cs
new file mode 100644
index 0000000..79eabc6
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907133944_InitMigraton.cs
@@ -0,0 +1,36 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ ///
+ public partial class InitMigraton : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "counterparty",
+ columns: table => new
+ {
+ id = table.Column(type: "uuid", nullable: false),
+ name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false),
+ contract_number = table.Column(type: "character varying(50)", maxLength: 50, nullable: false),
+ manager_name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_counterparty", x => x.id);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "counterparty");
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240907140843_ChangingContractInfo.Designer.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907140843_ChangingContractInfo.Designer.cs
new file mode 100644
index 0000000..750edea
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907140843_ChangingContractInfo.Designer.cs
@@ -0,0 +1,64 @@
+//
+using System;
+using DSaC.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ [DbContext(typeof(DsacContext))]
+ [Migration("20240907140843_ChangingContractInfo")]
+ partial class ChangingContractInfo
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DSaC.Database.Models.Counterparty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ContractNumber")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("contract_number");
+
+ b.Property("IsContractClosed")
+ .HasColumnType("boolean")
+ .HasColumnName("is_contract_closed");
+
+ b.Property("ManagerName")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("manager_name");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("counterparty");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240907140843_ChangingContractInfo.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907140843_ChangingContractInfo.cs
new file mode 100644
index 0000000..1b48b00
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907140843_ChangingContractInfo.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ ///
+ public partial class ChangingContractInfo : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "is_contract_closed",
+ table: "counterparty",
+ type: "boolean",
+ nullable: false,
+ defaultValue: false);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "is_contract_closed",
+ table: "counterparty");
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240907141343_SmallHotfix.Designer.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907141343_SmallHotfix.Designer.cs
new file mode 100644
index 0000000..996c2cf
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907141343_SmallHotfix.Designer.cs
@@ -0,0 +1,63 @@
+//
+using System;
+using DSaC.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ [DbContext(typeof(DsacContext))]
+ [Migration("20240907141343_SmallHotfix")]
+ partial class SmallHotfix
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DSaC.Database.Models.Counterparty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ContractNumber")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("contract_number");
+
+ b.Property("IsContractClosed")
+ .HasColumnType("boolean")
+ .HasColumnName("is_contract_closed");
+
+ b.Property("ManagerName")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("manager_name");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("counterparty");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240907141343_SmallHotfix.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907141343_SmallHotfix.cs
new file mode 100644
index 0000000..c82024a
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240907141343_SmallHotfix.cs
@@ -0,0 +1,40 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ ///
+ public partial class SmallHotfix : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn(
+ name: "manager_name",
+ table: "counterparty",
+ type: "character varying(255)",
+ maxLength: 255,
+ nullable: true,
+ oldClrType: typeof(string),
+ oldType: "character varying(255)",
+ oldMaxLength: 255);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn(
+ name: "manager_name",
+ table: "counterparty",
+ type: "character varying(255)",
+ maxLength: 255,
+ nullable: false,
+ defaultValue: "",
+ oldClrType: typeof(string),
+ oldType: "character varying(255)",
+ oldMaxLength: 255,
+ oldNullable: true);
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240914065929_after-reconstruction.Designer.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240914065929_after-reconstruction.Designer.cs
new file mode 100644
index 0000000..2a38398
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240914065929_after-reconstruction.Designer.cs
@@ -0,0 +1,48 @@
+//
+using System;
+using DSaC.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ [DbContext(typeof(DsacContext))]
+ [Migration("20240914065929_after-reconstruction")]
+ partial class afterreconstruction
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DSaC.Database.Models.Counterparty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("counterparty");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/20240914065929_after-reconstruction.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/20240914065929_after-reconstruction.cs
new file mode 100644
index 0000000..3abc7b3
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/20240914065929_after-reconstruction.cs
@@ -0,0 +1,52 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ ///
+ public partial class afterreconstruction : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "contract_number",
+ table: "counterparty");
+
+ migrationBuilder.DropColumn(
+ name: "is_contract_closed",
+ table: "counterparty");
+
+ migrationBuilder.DropColumn(
+ name: "manager_name",
+ table: "counterparty");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "contract_number",
+ table: "counterparty",
+ type: "character varying(50)",
+ maxLength: 50,
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "is_contract_closed",
+ table: "counterparty",
+ type: "boolean",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "manager_name",
+ table: "counterparty",
+ type: "character varying(255)",
+ maxLength: 255,
+ nullable: true);
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Migrations/DsacContextModelSnapshot.cs b/kuzarin_maxim_lab_3/DSaC/Migrations/DsacContextModelSnapshot.cs
new file mode 100644
index 0000000..153ecb1
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Migrations/DsacContextModelSnapshot.cs
@@ -0,0 +1,45 @@
+//
+using System;
+using DSaC.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DSaC.Migrations
+{
+ [DbContext(typeof(DsacContext))]
+ partial class DsacContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DSaC.Database.Models.Counterparty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("counterparty");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/DTOs/ContractViewDto.cs b/kuzarin_maxim_lab_3/DSaC/Models/DTOs/ContractViewDto.cs
new file mode 100644
index 0000000..8790566
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/DTOs/ContractViewDto.cs
@@ -0,0 +1,17 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace DSaC.Models.DTOs
+{
+ public class ContractViewDto
+ {
+ [Required(ErrorMessage = "Contract number must be present")]
+ public string ContractNumber { get; set; } = null!;
+
+ [Required(ErrorMessage = "Manager name must be present")]
+ public string ManagerName { get; set; } = null!;
+
+ public DateTime CreatinTime { get; set; }
+
+ public bool IsClosed { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/DTOs/CounterpartyBaseDto.cs b/kuzarin_maxim_lab_3/DSaC/Models/DTOs/CounterpartyBaseDto.cs
new file mode 100644
index 0000000..c1dad6d
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/DTOs/CounterpartyBaseDto.cs
@@ -0,0 +1,13 @@
+
+using AutoMapper;
+using DSaC.Database.Models;
+using System.ComponentModel.DataAnnotations;
+
+namespace DSaC.Models.DTOs
+{
+ public class CounterpartyBaseDto
+ {
+ [Required(ErrorMessage = "Cpty name must be present")]
+ public string Name { get; set; } = null!;
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/DTOs/CounterpartyViewDto.cs b/kuzarin_maxim_lab_3/DSaC/Models/DTOs/CounterpartyViewDto.cs
new file mode 100644
index 0000000..97df697
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/DTOs/CounterpartyViewDto.cs
@@ -0,0 +1,11 @@
+using AutoMapper;
+using DSaC.Database.Models;
+
+namespace DSaC.Models.DTOs
+{
+ [AutoMap(typeof(Counterparty))]
+ public class CounterpartyViewDto: CounterpartyBaseDto
+ {
+ public Guid Id { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/Internal/Queries/GetCounterpartiesQuery.cs b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Queries/GetCounterpartiesQuery.cs
new file mode 100644
index 0000000..ad17f69
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Queries/GetCounterpartiesQuery.cs
@@ -0,0 +1,14 @@
+using DSaC.Models.DTOs;
+using MediatR;
+
+namespace DSaC.Models.Internal.Queries
+{
+ public class GetCounterpartiesQuery: IRequest>>
+ {
+ public List? Ids { get; set; }
+
+ public int Page { get; set; }
+
+ public int PageSize { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/Internal/Queries/GetCounterpartyQuery.cs b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Queries/GetCounterpartyQuery.cs
new file mode 100644
index 0000000..254f082
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Queries/GetCounterpartyQuery.cs
@@ -0,0 +1,10 @@
+using DSaC.Models.DTOs;
+using MediatR;
+
+namespace DSaC.Models.Internal.Queries
+{
+ public class GetCounterpartyQuery: IRequest>
+ {
+ public Guid Id { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/Internal/ResponseModel.cs b/kuzarin_maxim_lab_3/DSaC/Models/Internal/ResponseModel.cs
new file mode 100644
index 0000000..f832eaf
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/Internal/ResponseModel.cs
@@ -0,0 +1,30 @@
+using System.Net;
+
+namespace DSaC.Models.Internal
+{
+ public class ResponseModel
+ {
+ public string? ErrorText;
+
+ public bool IsError => !string.IsNullOrEmpty(ErrorText);
+
+ private int? _errorCode = 200;
+
+ public int? ErrorCode
+ {
+ get => _errorCode;
+ set => _errorCode = value;
+ }
+
+ public HttpStatusCode? StatusCode
+ {
+ get => (HttpStatusCode?)_errorCode;
+ set => _errorCode = (int?)value;
+ }
+ }
+
+ public class ResponseModel : ResponseModel
+ {
+ public T? Value { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/CreateCounterpartyCommand.cs b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/CreateCounterpartyCommand.cs
new file mode 100644
index 0000000..6084a23
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/CreateCounterpartyCommand.cs
@@ -0,0 +1,10 @@
+using DSaC.Models.DTOs;
+using MediatR;
+
+namespace DSaC.Models.Internal.Сommands
+{
+ public class CreateCounterpartyCommand : IRequest>
+ {
+ public CounterpartyBaseDto Model { get; set; } = null!;
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/DeleteCounterpartyCommand.cs b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/DeleteCounterpartyCommand.cs
new file mode 100644
index 0000000..b968e14
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/DeleteCounterpartyCommand.cs
@@ -0,0 +1,9 @@
+using MediatR;
+
+namespace DSaC.Models.Internal.Сommands
+{
+ public class DeleteCounterpartyCommand: IRequest
+ {
+ public Guid Id { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/UpdateCounterpartyCommand.cs b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/UpdateCounterpartyCommand.cs
new file mode 100644
index 0000000..58e8b55
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Models/Internal/Сommands/UpdateCounterpartyCommand.cs
@@ -0,0 +1,14 @@
+using DSaC.Models.DTOs;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace DSaC.Models.Internal.Сommands
+{
+ public class UpdateCounterpartyCommand: IRequest>
+ {
+ public Guid Id { get; set; }
+
+ [Required(ErrorMessage = "UpdateMessageMust be present")]
+ public CounterpartyViewDto Model { get; set; } = null!;
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/Program.cs b/kuzarin_maxim_lab_3/DSaC/Program.cs
new file mode 100644
index 0000000..c60c692
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Program.cs
@@ -0,0 +1,128 @@
+using DSaC.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.OpenApi.Models;
+using NLog.Config;
+using NLog.Extensions.Logging;
+using NLog.Targets;
+using PIHelperSh.Configuration;
+using PIHelperSh.Configuration.Attributes;
+using System.Reflection;
+using LogLevel = NLog.LogLevel;
+
+[TrackedType]
+public class Program
+{
+ private static WebApplication? app;
+
+ [Constant(BlockName = "Database")]
+ private static string ConnectionString;
+
+ [Constant(BlockName = "GatewaySettings")]
+ private static string AppPrefix;
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ ConfigureLogger(builder);
+
+ ConfigureServices(builder);
+
+ ConfigureDatabase(builder);
+
+ app = builder.Build();
+
+ var t = MigrateDatabase();
+
+ app.UseSwagger(c =>
+ {
+ if (!string.IsNullOrEmpty(AppPrefix))
+ {
+ //c.RouteTemplate = AppPrefix + "/swagger/{documentName}/swagger.json";
+
+ c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
+ {
+ swaggerDoc.Servers = new List { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/{AppPrefix}" } };
+ });
+ }
+ });
+ app.UseSwaggerUI(c =>
+ {
+ //if (!string.IsNullOrEmpty(AppPrefix))
+ //{
+ // c.SwaggerEndpoint($"/{AppPrefix}/swagger/v1/swagger.json", $"APP API");
+ // c.RoutePrefix = $"{AppPrefix}/swagger";
+ //}
+ });
+
+ app.UseHttpsRedirection();
+
+ app.UseAuthorization();
+
+ app.MapControllers();
+
+ t.Wait();
+
+ app.Run();
+ }
+
+ private static void ConfigureLogger(WebApplicationBuilder builder)
+ {
+ var nLogConfig = new LoggingConfiguration();
+ var logConsole = new ConsoleTarget();
+ var blackhole = new NullTarget();
+
+ var logFile = new FileTarget()
+ {
+ FileName = "${basedir}/logs/${shortdate}_logs.log"
+ };
+
+ nLogConfig.AddRule(LogLevel.Trace, LogLevel.Trace, blackhole, "Microsoft.AspNetCore.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Warn, logFile, "Microsoft.EntityFrameworkCore.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Warn, logFile, "Microsoft.AspNetCore.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Warn, logFile, "System.Net.Http.HttpClient.Refit.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Error, logConsole);
+ nLogConfig.AddRule(LogLevel.Debug, LogLevel.Error, logFile);
+
+ builder.Logging.ClearProviders();
+ builder.Services.AddLogging(m => m.AddNLog(nLogConfig));
+ }
+
+ private static void ConfigureServices(WebApplicationBuilder builder)
+ {
+ builder.Services.AddConfigurations(builder.Configuration);
+ builder.Configuration.AddConstants();
+ builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
+
+ builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining());
+
+ builder.Services.AddControllers().AddNewtonsoftJson();
+
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new OpenApiInfo
+ {
+ Title = "Distributed computing and applications",
+ Version = "v1",
+ Description = ""
+ });
+
+ //c.EnableAnnotations();
+ });
+ }
+
+ private static void ConfigureDatabase(WebApplicationBuilder builder)
+ {
+ builder.Services.AddDbContext(options =>
+ {
+ options.UseNpgsql(ConnectionString);
+ });
+ }
+
+ private static async Task MigrateDatabase()
+ {
+ var context = app?.Services.CreateScope().ServiceProvider.GetService();
+ if(context != null)
+ await context.Database.MigrateAsync();
+ }
+}
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/DSaC/Properties/launchSettings.json b/kuzarin_maxim_lab_3/DSaC/Properties/launchSettings.json
new file mode 100644
index 0000000..6349710
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/Properties/launchSettings.json
@@ -0,0 +1,52 @@
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5062"
+ },
+ "https": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://localhost:7219;http://localhost:5062"
+ },
+ "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_HTTPS_PORTS": "8081",
+ "ASPNETCORE_HTTP_PORTS": "8080"
+ },
+ "publishAllPorts": true,
+ "useSSL": true
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:9154",
+ "sslPort": 44381
+ }
+ }
+}
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/DSaC/appsettings.Development.json b/kuzarin_maxim_lab_3/DSaC/appsettings.Development.json
new file mode 100644
index 0000000..3711bea
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/appsettings.Development.json
@@ -0,0 +1,14 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "Database": {
+ "ConnectionString": "Host=db.dev-moio.online;Port=31153;Database=dsac_maxim;Username=postgres;Password=postgres_password"
+ },
+ "GatewaySettings": {
+ "AppPrefix": ""
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC/appsettings.json b/kuzarin_maxim_lab_3/DSaC/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Controllers/ContractController.cs b/kuzarin_maxim_lab_3/DSaC_second/Controllers/ContractController.cs
new file mode 100644
index 0000000..1fc805f
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Controllers/ContractController.cs
@@ -0,0 +1,88 @@
+using DSaC_second.Models.DTOs;
+using DSaC_second.Models.Internal.Queries;
+using DSaC_second.Models.Internal.Сommands;
+using MediatR;
+using Microsoft.AspNetCore.Components.Forms;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace DSaC_second.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class ContractController : ControllerBase
+ {
+ private readonly IMediator _mediator;
+
+ public ContractController(IMediator mediator)
+ {
+ _mediator = mediator;
+ }
+
+ [HttpGet("")]
+ public async Task GetContracts(
+ [FromQuery] int page = 0,
+ [FromQuery] int pageSize = 10,
+ [FromQuery] List? ids = null
+ )
+ {
+ var request = new GetContractsQuery
+ {
+ Page = page,
+ PageSize = pageSize,
+ Ids = ids
+ };
+
+ var response = await _mediator.Send(request);
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpGet("{uuid:guid}")]
+ public async Task GetFullContract([FromRoute] Guid uuid)
+ {
+ var request = new GetContractQuery
+ {
+ Id = uuid
+ };
+
+ var response = await _mediator.Send(request);
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpPost("")]
+ public async Task CreateContract([FromBody] ContractBaseDto dto)
+ {
+ var response = await _mediator.Send(new CreateContractCommand()
+ {
+ Model = dto
+ });
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpPut("{uuid:guid}")]
+ public async Task UpdateContract([FromRoute] Guid uuid, [FromBody] ContractViewDto dto)
+ {
+ var response = await _mediator.Send(new UpdateContractCommand()
+ {
+ Id = uuid,
+ Model = dto
+ });
+
+ return !response.IsError ? Ok(response.Value) : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+
+ [HttpDelete("{uuid:guid}")]
+ public async Task DeleteContract([FromRoute] Guid uuid)
+ {
+ var response = await _mediator.Send(new DeleteContractCommand()
+ {
+ Id = uuid,
+ });
+
+ return !response.IsError ? Ok() : StatusCode(response.ErrorCode!.Value, response.ErrorText);
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/DSaC_second.csproj b/kuzarin_maxim_lab_3/DSaC_second/DSaC_second.csproj
new file mode 100644
index 0000000..9205aad
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/DSaC_second.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+ 39d29416-63e9-4884-9c5f-9d6ff461995f
+ Linux
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Database/DsacContext.cs b/kuzarin_maxim_lab_3/DSaC_second/Database/DsacContext.cs
new file mode 100644
index 0000000..3341751
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Database/DsacContext.cs
@@ -0,0 +1,15 @@
+using DSaC_second.Database.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC_second.Database
+{
+ public class DsacContext : DbContext
+ {
+ public DsacContext(DbContextOptions options) : base(options)
+ {
+ AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
+ }
+
+ public DbSet Contracts { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Database/Models/Contract.cs b/kuzarin_maxim_lab_3/DSaC_second/Database/Models/Contract.cs
new file mode 100644
index 0000000..b83b77d
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Database/Models/Contract.cs
@@ -0,0 +1,38 @@
+using AutoMapper;
+using DSaC_second.Models.DTOs;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace DSaC_second.Database.Models
+{
+ [Table("contract")]
+ [AutoMap(typeof(ContractBaseDto))]
+ [AutoMap(typeof(ContractViewDto))]
+ public class Contract
+ {
+ [Key]
+ [Column("id")]
+ public Guid Id { get; set; }
+
+ [Column("contract_number")]
+ [Required]
+ public string ContractNumber { get; set; }
+
+ [Column("manager_name")]
+ [Required]
+ public string ManagerName { get; set; }
+
+ [Column("counterparty_id")]
+ [Required]
+ public Guid CounterpartyId { get; set; }
+
+ [Column("creation_time")]
+ [Required]
+ public DateTime CreatinTime { get; set; } = DateTime.Now;
+
+ [Column("is_closed")]
+ [DefaultValue(false)]
+ public bool IsClosed { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Dockerfile b/kuzarin_maxim_lab_3/DSaC_second/Dockerfile
new file mode 100644
index 0000000..d0dad02
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Dockerfile
@@ -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:8.0 AS base
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["DSaC_second.csproj", "DSaC_second/"]
+RUN dotnet restore "DSaC_second/DSaC_second.csproj"
+WORKDIR "/src/DSaC_second"
+COPY . .
+RUN dotnet build "DSaC_second.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "DSaC_second.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "DSaC_second.dll"]
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/CreateContractCommandHandler.cs b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/CreateContractCommandHandler.cs
new file mode 100644
index 0000000..e885bbc
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/CreateContractCommandHandler.cs
@@ -0,0 +1,76 @@
+using AutoMapper;
+using DSaC_second.Database;
+using DSaC_second.Database.Models;
+using DSaC_second.Logic.Handlers.Queries;
+using DSaC_second.Models.DTOs;
+using DSaC_second.Models.Internal;
+using DSaC_second.Models.Internal.Queries;
+using DSaC_second.Models.Internal.Сommands;
+using MediatR;
+
+namespace DSaC_second.Logic.Handlers.Commands
+{
+ public class CreateContractCommandHandler : IRequestHandler>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+ private readonly IMediator _mediator;
+
+ public CreateContractCommandHandler(ILogger logger, DsacContext context, IMapper mapper, IMediator mediator)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ _mediator = mediator;
+ }
+
+ public async Task> Handle(CreateContractCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var counterparty = await _mediator.Send(new GetConunterpartyQuery()
+ {
+ Id = request.Model.CounterpartyId,
+ }, cancellationToken: cancellationToken);
+
+ if (counterparty.IsError)
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = counterparty.ErrorText,
+ };
+ if (counterparty.Value == null)
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cpty with this id not found",
+ };
+
+ var model = _mapper.Map(request.Model);
+
+ var outModel = await _context.Contracts.AddAsync(model, cancellationToken: cancellationToken);
+
+ await _context.SaveChangesAsync(cancellationToken);
+
+ var res = _mapper.Map(model);
+ res.Counterparty = counterparty.Value!;
+
+ return new()
+ {
+ Value = res,
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on creating record");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot create contract",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/DeleteContractCommandHandler.cs b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/DeleteContractCommandHandler.cs
new file mode 100644
index 0000000..fd3a811
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/DeleteContractCommandHandler.cs
@@ -0,0 +1,43 @@
+using AutoMapper;
+using DSaC_second.Database;
+using DSaC_second.Models.Internal;
+using DSaC_second.Models.Internal.Сommands;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC_second.Logic.Handlers.Commands
+{
+ public class DeleteContractCommandHandler: IRequestHandler
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+
+ public DeleteContractCommandHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(DeleteContractCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await _context.Contracts.Where(x => x.Id == request.Id).ExecuteDeleteAsync(cancellationToken);
+
+ return new();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on deleting record");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot delete contract by id",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/UpdateContractCommandHandler.cs b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/UpdateContractCommandHandler.cs
new file mode 100644
index 0000000..4574634
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Commands/UpdateContractCommandHandler.cs
@@ -0,0 +1,78 @@
+using AutoMapper;
+using DSaC_second.Database;
+using DSaC_second.Database.Models;
+using DSaC_second.Models.DTOs;
+using DSaC_second.Models.Internal;
+using DSaC_second.Models.Internal.Queries;
+using DSaC_second.Models.Internal.Сommands;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC_second.Logic.Handlers.Commands
+{
+ public class UpdateContractCommandHandler : IRequestHandler>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+ private readonly IMediator _mediator;
+
+ public UpdateContractCommandHandler(ILogger logger, DsacContext context, IMapper mapper, IMediator mediator)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ _mediator = mediator;
+ }
+
+ public async Task> Handle(UpdateContractCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var counterparty = await _mediator.Send(new GetConunterpartyQuery()
+ {
+ Id = request.Model.CounterpartyId,
+ }, cancellationToken: cancellationToken);
+
+ if (counterparty.IsError)
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = counterparty.ErrorText,
+ };
+ if(counterparty.Value == null)
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cpty with this id not found",
+ };
+
+ var model = _mapper.Map(request.Model);
+
+ _context.Contracts.Update(model);
+
+ await _context.SaveChangesAsync(cancellationToken: cancellationToken);
+
+ var res = _mapper.Map(model);
+
+ res.Counterparty = counterparty.Value!;
+
+ return new()
+ {
+ Value = res
+ };
+
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on updating record");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot update contract by id",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetContractQueryHandler.cs b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetContractQueryHandler.cs
new file mode 100644
index 0000000..001f9ea
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetContractQueryHandler.cs
@@ -0,0 +1,71 @@
+using AutoMapper;
+using DSaC_second.Database;
+using DSaC_second.Models.DTOs;
+using DSaC_second.Models.Internal;
+using DSaC_second.Models.Internal.Queries;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC_second.Logic.Handlers.Queries
+{
+ public class GetContractQueryHandler : IRequestHandler>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+ private readonly IMediator _mediator;
+
+ public GetContractQueryHandler(ILogger logger, DsacContext context, IMapper mapper, IMediator mediator)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ _mediator = mediator;
+ }
+
+ public async Task> Handle(GetContractQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var res = await _context.Contracts.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken: cancellationToken);
+
+ if (res == null)
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Contract with whit Id is not exist"
+ };
+ var counterparty = await _mediator.Send(new GetConunterpartyQuery()
+ {
+ Id = res.CounterpartyId
+ }, cancellationToken: cancellationToken);
+
+ if (counterparty.IsError)
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = counterparty.ErrorText
+ };
+
+ var model = _mapper.Map(res);
+ model.Counterparty = counterparty.Value!;
+
+ return new()
+ {
+ Value = model,
+ };
+
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on getting record");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot get contract by id",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetContractsQueryHandler.cs b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetContractsQueryHandler.cs
new file mode 100644
index 0000000..74fc87f
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetContractsQueryHandler.cs
@@ -0,0 +1,50 @@
+using AutoMapper;
+using DSaC_second.Database;
+using DSaC_second.Models.DTOs;
+using DSaC_second.Models.Internal;
+using DSaC_second.Models.Internal.Queries;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace DSaC_second.Logic.Handlers.Queries
+{
+ public class GetContractsQueryHandler : IRequestHandler>>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+
+ public GetContractsQueryHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public async Task>> Handle(GetContractsQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var res = await _context.Contracts
+ .Where(x=>request.Ids == null || request.Ids.Contains(x.Id))
+ .Skip(request.Page * request.PageSize).Take(request.PageSize)
+ .ToListAsync(cancellationToken: cancellationToken);
+
+ return new()
+ {
+ Value = res.Select(_mapper.Map).ToList(),
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on getting records");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot get contracts",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetConunterpartyQueryHandler.cs b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetConunterpartyQueryHandler.cs
new file mode 100644
index 0000000..0510b86
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Logic/Handlers/Queries/GetConunterpartyQueryHandler.cs
@@ -0,0 +1,72 @@
+using AutoMapper;
+using DSaC_second.Database;
+using DSaC_second.Logic.Handlers.Commands;
+using DSaC_second.Models.DTOs;
+using DSaC_second.Models.Internal;
+using DSaC_second.Models.Internal.Queries;
+using MediatR;
+using PIHelperSh.Configuration.Attributes;
+using RestSharp;
+
+namespace DSaC_second.Logic.Handlers.Queries
+{
+ [TrackedType]
+ public class GetConunterpartyQueryHandler : IRequestHandler>
+ {
+ private readonly ILogger _logger;
+ private readonly DsacContext _context;
+ private readonly IMapper _mapper;
+ private readonly RestClient _restClient;
+
+ [Constant(BlockName = "FirstService")]
+ private static string BaseUrl;
+
+ [Constant(BlockName = "FirstService")]
+ private static string GetCounterpartyPostfix;
+
+ public GetConunterpartyQueryHandler(ILogger logger, DsacContext context, IMapper mapper)
+ {
+ _logger = logger;
+ _context = context;
+ _mapper = mapper;
+ _restClient = new RestClient(BaseUrl);
+ }
+
+ ~GetConunterpartyQueryHandler()
+ {
+ _restClient.Dispose();
+ }
+
+ public async Task> Handle(GetConunterpartyQuery request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var rq = new RestRequest($"{GetCounterpartyPostfix}/{request.Id.ToString()}");
+
+ var res = await _restClient.ExecuteAsync(rq, cancellationToken:cancellationToken);
+
+ if (res.IsSuccessful)
+ return new()
+ {
+ Value = res.Data
+ };
+
+ return new()
+ {
+ StatusCode = res.StatusCode,
+ ErrorText = res.ErrorMessage,
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error on getting record from first service");
+
+ return new()
+ {
+ StatusCode = System.Net.HttpStatusCode.NotFound,
+ ErrorText = "Cannot get cpty from first service",
+ };
+ }
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Migrations/20240914152154_initial.Designer.cs b/kuzarin_maxim_lab_3/DSaC_second/Migrations/20240914152154_initial.Designer.cs
new file mode 100644
index 0000000..d16d35a
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Migrations/20240914152154_initial.Designer.cs
@@ -0,0 +1,64 @@
+//
+using System;
+using DSaC_second.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DSaC_second.Migrations
+{
+ [DbContext(typeof(DsacContext))]
+ [Migration("20240914152154_initial")]
+ partial class initial
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DSaC_second.Database.Models.Contract", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ContractNumber")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("contract_number");
+
+ b.Property("CounterpartyId")
+ .HasColumnType("uuid")
+ .HasColumnName("counterparty_id");
+
+ b.Property("CreatinTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("creation_time");
+
+ b.Property("IsClosed")
+ .HasColumnType("boolean")
+ .HasColumnName("is_closed");
+
+ b.Property("ManagerName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("manager_name");
+
+ b.HasKey("Id");
+
+ b.ToTable("contract");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Migrations/20240914152154_initial.cs b/kuzarin_maxim_lab_3/DSaC_second/Migrations/20240914152154_initial.cs
new file mode 100644
index 0000000..dabac45
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Migrations/20240914152154_initial.cs
@@ -0,0 +1,38 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DSaC_second.Migrations
+{
+ ///
+ public partial class initial : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "contract",
+ columns: table => new
+ {
+ id = table.Column(type: "uuid", nullable: false),
+ contract_number = table.Column(type: "text", nullable: false),
+ manager_name = table.Column(type: "text", nullable: false),
+ counterparty_id = table.Column(type: "uuid", nullable: false),
+ creation_time = table.Column(type: "timestamp with time zone", nullable: false),
+ is_closed = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_contract", x => x.id);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "contract");
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Migrations/DsacContextModelSnapshot.cs b/kuzarin_maxim_lab_3/DSaC_second/Migrations/DsacContextModelSnapshot.cs
new file mode 100644
index 0000000..d3b6a2e
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Migrations/DsacContextModelSnapshot.cs
@@ -0,0 +1,61 @@
+//
+using System;
+using DSaC_second.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DSaC_second.Migrations
+{
+ [DbContext(typeof(DsacContext))]
+ partial class DsacContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DSaC_second.Database.Models.Contract", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ContractNumber")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("contract_number");
+
+ b.Property("CounterpartyId")
+ .HasColumnType("uuid")
+ .HasColumnName("counterparty_id");
+
+ b.Property("CreatinTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("creation_time");
+
+ b.Property("IsClosed")
+ .HasColumnType("boolean")
+ .HasColumnName("is_closed");
+
+ b.Property("ManagerName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("manager_name");
+
+ b.HasKey("Id");
+
+ b.ToTable("contract");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractBaseDto.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractBaseDto.cs
new file mode 100644
index 0000000..f21ceea
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractBaseDto.cs
@@ -0,0 +1,17 @@
+using AutoMapper;
+using System.ComponentModel.DataAnnotations;
+
+namespace DSaC_second.Models.DTOs
+{
+ public class ContractBaseDto
+ {
+ [Required(ErrorMessage = "Contract number must be present")]
+ public string ContractNumber { get; set; } = null!;
+
+ [Required(ErrorMessage = "Manager name must be present")]
+ public string ManagerName { get; set; } = null!;
+
+ [Required(ErrorMessage = "Counterparty id must be present")]
+ public Guid CounterpartyId { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractFullDto.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractFullDto.cs
new file mode 100644
index 0000000..85b8372
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractFullDto.cs
@@ -0,0 +1,11 @@
+using AutoMapper;
+using DSaC_second.Database.Models;
+
+namespace DSaC_second.Models.DTOs
+{
+ [AutoMap(typeof(Contract))]
+ public class ContractFullDto: ContractViewDto
+ {
+ public CounterpartyViewDto Counterparty { get; set; } = new();
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractViewDto.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractViewDto.cs
new file mode 100644
index 0000000..7010a9a
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/ContractViewDto.cs
@@ -0,0 +1,15 @@
+using AutoMapper;
+using DSaC_second.Database.Models;
+
+namespace DSaC_second.Models.DTOs
+{
+ [AutoMap(typeof(Contract))]
+ public class ContractViewDto: ContractBaseDto
+ {
+ public Guid Id { get; set; }
+
+ public DateTime CreatinTime { get; set; }
+
+ public bool IsClosed { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/CounterpartyViewDto.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/CounterpartyViewDto.cs
new file mode 100644
index 0000000..645f649
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/DTOs/CounterpartyViewDto.cs
@@ -0,0 +1,12 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace DSaC_second.Models.DTOs
+{
+ public class CounterpartyViewDto
+ {
+ public Guid Id { get; set; }
+
+ [Required(ErrorMessage = "Cpty name must be present")]
+ public string Name { get; set; } = null!;
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetContractQuery.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetContractQuery.cs
new file mode 100644
index 0000000..f75bfc1
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetContractQuery.cs
@@ -0,0 +1,10 @@
+using DSaC_second.Models.DTOs;
+using MediatR;
+
+namespace DSaC_second.Models.Internal.Queries
+{
+ public class GetContractQuery : IRequest>
+ {
+ public Guid Id { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetContractsQuery.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetContractsQuery.cs
new file mode 100644
index 0000000..94aa470
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetContractsQuery.cs
@@ -0,0 +1,14 @@
+using DSaC_second.Models.DTOs;
+using MediatR;
+
+namespace DSaC_second.Models.Internal.Queries
+{
+ public class GetContractsQuery : IRequest>>
+ {
+ public List? Ids { get; set; }
+
+ public int Page { get; set; }
+
+ public int PageSize { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetConunterpartyQuery.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetConunterpartyQuery.cs
new file mode 100644
index 0000000..ebd0f71
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Queries/GetConunterpartyQuery.cs
@@ -0,0 +1,10 @@
+using DSaC_second.Models.DTOs;
+using MediatR;
+
+namespace DSaC_second.Models.Internal.Queries
+{
+ public class GetConunterpartyQuery: IRequest>
+ {
+ public Guid Id { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/ResponseModel.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/ResponseModel.cs
new file mode 100644
index 0000000..83c6b40
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/ResponseModel.cs
@@ -0,0 +1,30 @@
+using System.Net;
+
+namespace DSaC_second.Models.Internal
+{
+ public class ResponseModel
+ {
+ public string? ErrorText;
+
+ public bool IsError => !string.IsNullOrEmpty(ErrorText);
+
+ private int? _errorCode = 200;
+
+ public int? ErrorCode
+ {
+ get => _errorCode;
+ set => _errorCode = value;
+ }
+
+ public HttpStatusCode? StatusCode
+ {
+ get => (HttpStatusCode?)_errorCode;
+ set => _errorCode = (int?)value;
+ }
+ }
+
+ public class ResponseModel : ResponseModel
+ {
+ public T? Value { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/CreateContractCommand.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/CreateContractCommand.cs
new file mode 100644
index 0000000..3b8c241
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/CreateContractCommand.cs
@@ -0,0 +1,10 @@
+using DSaC_second.Models.DTOs;
+using MediatR;
+
+namespace DSaC_second.Models.Internal.Сommands
+{
+ public class CreateContractCommand : IRequest>
+ {
+ public ContractBaseDto Model { get; set; } = null!;
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/DeleteContractCommand.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/DeleteContractCommand.cs
new file mode 100644
index 0000000..c144c1c
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/DeleteContractCommand.cs
@@ -0,0 +1,9 @@
+using MediatR;
+
+namespace DSaC_second.Models.Internal.Сommands
+{
+ public class DeleteContractCommand : IRequest
+ {
+ public Guid Id { get; set; }
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/UpdateContractCommand.cs b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/UpdateContractCommand.cs
new file mode 100644
index 0000000..10fc2b9
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Models/Internal/Сommands/UpdateContractCommand.cs
@@ -0,0 +1,14 @@
+using DSaC_second.Models.DTOs;
+using MediatR;
+using System.ComponentModel.DataAnnotations;
+
+namespace DSaC_second.Models.Internal.Сommands
+{
+ public class UpdateContractCommand : IRequest>
+ {
+ public Guid Id { get; set; }
+
+ [Required(ErrorMessage = "Update model msust be present")]
+ public ContractViewDto Model { get; set; } = null!;
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Program.cs b/kuzarin_maxim_lab_3/DSaC_second/Program.cs
new file mode 100644
index 0000000..25eb501
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Program.cs
@@ -0,0 +1,130 @@
+using DSaC_second.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.OpenApi.Models;
+using NLog.Config;
+using NLog.Extensions.Logging;
+using NLog.Targets;
+using PIHelperSh.Configuration;
+using PIHelperSh.Configuration.Attributes;
+using System;
+using System.Reflection;
+using LogLevel = NLog.LogLevel;
+
+[TrackedType]
+public class Program
+{
+ private static WebApplication? app;
+
+ [Constant(BlockName = "Database")]
+ private static string ConnectionString;
+
+ [Constant(BlockName = "GatewaySettings")]
+ private static string AppPrefix;
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ ConfigureLogger(builder);
+
+ ConfigureServices(builder);
+
+ ConfigureDatabase(builder);
+
+ app = builder.Build();
+
+ var t = MigrateDatabase();
+
+ app.UseSwagger(c =>
+ {
+ if (!string.IsNullOrEmpty(AppPrefix))
+ {
+ //c.RouteTemplate = AppPrefix + "/swagger/{documentName}/swagger.json";
+
+ c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
+ {
+ swaggerDoc.Servers = new List { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/{AppPrefix}" } };
+ });
+ }
+
+ });
+ app.UseSwaggerUI(c =>
+ {
+ //if (!string.IsNullOrEmpty(AppPrefix))
+ //{
+ // c.SwaggerEndpoint($"/{AppPrefix}/swagger/v1/swagger.json", $"APP API");
+ // c.RoutePrefix = $"{AppPrefix}/swagger";
+ //}
+ });
+
+ app.UseHttpsRedirection();
+
+ app.UseAuthorization();
+
+ app.MapControllers();
+
+ t.Wait();
+
+ app.Run();
+ }
+
+ private static void ConfigureLogger(WebApplicationBuilder builder)
+ {
+ var nLogConfig = new LoggingConfiguration();
+ var logConsole = new ConsoleTarget();
+ var blackhole = new NullTarget();
+
+ var logFile = new FileTarget()
+ {
+ FileName = "${basedir}/logs/${shortdate}_logs.log"
+ };
+
+ nLogConfig.AddRule(LogLevel.Trace, LogLevel.Trace, blackhole, "Microsoft.AspNetCore.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Warn, logFile, "Microsoft.EntityFrameworkCore.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Warn, logFile, "Microsoft.AspNetCore.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Warn, logFile, "System.Net.Http.HttpClient.Refit.*", true);
+ nLogConfig.AddRule(LogLevel.Info, LogLevel.Error, logConsole);
+ nLogConfig.AddRule(LogLevel.Debug, LogLevel.Error, logFile);
+
+ builder.Logging.ClearProviders();
+ builder.Services.AddLogging(m => m.AddNLog(nLogConfig));
+ }
+
+ private static void ConfigureServices(WebApplicationBuilder builder)
+ {
+ builder.Services.AddConfigurations(builder.Configuration);
+ builder.Configuration.AddConstants();
+ builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
+
+ builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining());
+
+ builder.Services.AddControllers().AddNewtonsoftJson();
+
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new OpenApiInfo
+ {
+ Title = "Distributed computing and applications",
+ Version = "v1",
+ Description = ""
+ });
+
+ //c.EnableAnnotations();
+ });
+ }
+
+ private static void ConfigureDatabase(WebApplicationBuilder builder)
+ {
+ builder.Services.AddDbContext(options =>
+ {
+ options.UseNpgsql(ConnectionString);
+ });
+ }
+
+ private static async Task MigrateDatabase()
+ {
+ var context = app?.Services.CreateScope().ServiceProvider.GetService();
+ if(context != null)
+ await context.Database.MigrateAsync();
+ }
+}
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/DSaC_second/Properties/launchSettings.json b/kuzarin_maxim_lab_3/DSaC_second/Properties/launchSettings.json
new file mode 100644
index 0000000..8f8496f
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/Properties/launchSettings.json
@@ -0,0 +1,53 @@
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "GATEWAY_SETTINGS_APP_PREFIX": "first"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5246"
+ },
+ "https": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://localhost:7239;http://localhost:5246"
+ },
+ "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_HTTPS_PORTS": "8081",
+ "ASPNETCORE_HTTP_PORTS": "8080"
+ },
+ "publishAllPorts": true,
+ "useSSL": true
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:56866",
+ "sslPort": 44308
+ }
+ }
+}
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/DSaC_second/appsettings.Development.json b/kuzarin_maxim_lab_3/DSaC_second/appsettings.Development.json
new file mode 100644
index 0000000..d4d8f8b
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/appsettings.Development.json
@@ -0,0 +1,18 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "Database": {
+ "ConnectionString": "Host=db.dev-moio.online;Port=31153;Database=dsac_maxim;Username=postgres;Password=postgres_password"
+ },
+ "FirstService": {
+ "BaseUrl": "http://localhost:5062/api",
+ "GetCounterpartyPostfix": "/Counterparties"
+ },
+ "GatewaySettings": {
+ "AppPrefix": ""
+ }
+}
diff --git a/kuzarin_maxim_lab_3/DSaC_second/appsettings.json b/kuzarin_maxim_lab_3/DSaC_second/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/kuzarin_maxim_lab_3/DSaC_second/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/kuzarin_maxim_lab_3/README.md b/kuzarin_maxim_lab_3/README.md
new file mode 100644
index 0000000..f85e69a
--- /dev/null
+++ b/kuzarin_maxim_lab_3/README.md
@@ -0,0 +1,18 @@
+# Лабораторная работа 3
+Данная работа посвящена теме синхронного обмена сообщениями между сервисами, а так же шлюзам, для взаимодействия с внешним миром.
+## Описание
+В качестве предметной области использовался пример 18-го варианта. Имеются 2 сущность: контрагент(только имя) и договор(номер, ФИО менеджера, дата создания и статус).
+Для реализации была выбрана платформа ASP.net. Информация хранится в СУБД Postgres, которая так же поднимается в контейнере(вместе с остальными сервисами)
+Система позволяет проводить с сущностями базовые CRUD операции. В случае договора, имеется возможность получения его в связке с информацией по контрагенту(не просто Id, но и имя). В этот момент сервисы и взаимодействуют друг с другом. Для этого используется шлюз(напрямую сервисы друг о друге могут и не знать, общение как с внешним миром так и между собой только через nginx шлюз)
+В качестве интерфейса имеется Swagger, который можно использовать для отправки запросов. Он и будет показан в видеодеомнстрации
+## Запуск
+Для запуска лабораторной работы необходимо иметь запущенный движок Docker на устройстве.
+Необходимо перейти в папку, где располагается данный файл. Далее открыть терминал и ввести команду:
+```
+docker compose up -d --build
+```
+Важно, чтобы в этот момент на компьютере был свободен порт 80.
+В результате, после сборки вся система запустится и Swagger-ы будут доступны по путям http://localhost/first/swagger и http://localhost/second/swagger
+## Видеодемонстрация
+Был записан видеоролик, демонстрирующий процесс запуска и работы системы, а так же всех её основных функйи. Он расположен по [адресу](https://drive.google.com/file/d/17OwZ6kEJ-AaW0uMTIfNtolyp-oyy9OfT/view?usp=sharing)
+
diff --git a/kuzarin_maxim_lab_3/compose.yaml b/kuzarin_maxim_lab_3/compose.yaml
new file mode 100644
index 0000000..8e4df01
--- /dev/null
+++ b/kuzarin_maxim_lab_3/compose.yaml
@@ -0,0 +1,37 @@
+services:
+ postgres:
+ image: postgres
+ ports:
+ - 5432:5432
+ environment:
+ POSTGRES_PASSWORD: "password"
+
+ nginx:
+ image: nginx
+ depends_on:
+ - second
+ - first
+ volumes:
+ - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
+ ports:
+ - 80:80
+
+ first:
+ build: .\DSaC
+ depends_on:
+ - postgres
+ environment:
+ DATABASE_CONNECTION_STRING: "Host=postgres;Database=dsac_maxim_1;Username=postgres;Password=password"
+ GATEWAY_SETTINGS_APP_PREFIX: "first" # Нужно, чтобы запросы из Swagger работали нормально
+
+ second:
+ build: .\DSaC_second
+ depends_on:
+ - first
+ environment:
+ DATABASE_CONNECTION_STRING: "Host=postgres;Database=dsac_maxim_2;Username=postgres;Password=password"
+ FIRST_SERVICE_BASE_URL: "http://nginx/first/api"
+ FIRST_SERVICE_GET_COUNTERPARTY_POSTFIX: "/Counterparties"
+ GATEWAY_SETTINGS_APP_PREFIX: "second"
+
+
\ No newline at end of file
diff --git a/kuzarin_maxim_lab_3/nginx/nginx.conf b/kuzarin_maxim_lab_3/nginx/nginx.conf
new file mode 100644
index 0000000..63e5ddc
--- /dev/null
+++ b/kuzarin_maxim_lab_3/nginx/nginx.conf
@@ -0,0 +1,22 @@
+
+server {
+ listen 80;
+ listen [::]:80;
+ server_name localhost;
+
+ location /first/ {
+ proxy_pass http://first: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-Prefix /test;
+ }
+
+ location /second/ {
+ proxy_pass http://second: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-Prefix /admin;
+ }
+}
diff --git a/minhasapov_ruslan_lab_1/README.md b/minhasapov_ruslan_lab_1/README.md
new file mode 100644
index 0000000..0dd3760
--- /dev/null
+++ b/minhasapov_ruslan_lab_1/README.md
@@ -0,0 +1,64 @@
+## Лабораторная работа №1: Знакомство с docker и docker-compose
+
+---
+
+**Цель:** изучение современных технологий контейнеризации.
+
+**Задачи:**
+
+1. Установить средство контейнеризации docker.
+2. Изучить применение и принципы docker.
+3. Изучить утилиту docker-compose и структуру файла docker-compose.yml.
+4. Развернуть не менее 3х различных сервисов при помощи docker-compose.
+5. Оформить отчёт в формате Markdown и создать Pull Request в git-репозитории.
+
+---
+
+**Сервисы:**
+
+* **Gitea:** Легковесная и самодостаточная система управления версиями кода с веб-интерфейсом.
+* **Postgres:** Мощная система управления реляционными базами данных с открытым исходным кодом.
+* **Redis:** Высокопроизводительное хранилище данных типа "ключ-значение", используемое для кэширования, сессий и других задач.
+
+---
+
+**Инструкция по запуску:**
+
+1. **Установка Docker:** Необходимо убедиться, установлен ли Docker на системе, в ином случае установить его.
+2. **Навигация:** Перейти в директорию с файлом `docker-compose.yml`.
+3. **Запуск:** Необходимо выполнить следующую команду в терминале:
+ ```bash
+ docker-compose up --build --remove-orphans
+ ```
+ Данная команда создаст и запустит все необходимые контейнеры, описанные в `docker-compose.yml`.
+
+**Проверка:**
+
+Для проверки успешного запуска нужно выполнить команду:
+
+```bash
+docker ps -a
+```
+
+В случае успеха мы можем увидеть список запущенных контейнеров (gitea, postgres и redis):
+```bash
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
+6ea2e28126a8 gitea/gitea:1.22.2 "/usr/bin/entrypoint…" About a minute ago Up 9 seconds 0.0.0.0:3000->3000/tcp, 0.0.0.0:222->22/tcp minhasapov_ruslan_lab_1-gitea-1
+2490f332bc90 postgres:14 "docker-entrypoint.s…" About a minute ago Up 9 seconds 0.0.0.0:5432->5432/tcp minhasapov_ruslan_lab_1-postgres-1
+c5580d8fe1d7 redis:latest "docker-entrypoint.s…" About a minute ago Up 9 seconds 0.0.0.0:6379->6379/tcp minhasapov_ruslan_lab_1-redis-1
+```
+**Примечание:**
+
+*Файл `docker-compose.yml` содержит подробную информацию о конфигурации каждого сервиса, включая порты, тома и переменные окружения.*
+
+**Важно:**
+
+После первого запуска **Gitea**, необходимо зайти на сервис *(по адресу `http://localhost:3000`)*, выполнить первоначальную настройку и создать суперпользователя.
+
+---
+
+**Видеодемонстрация работы:**
+
+Видео доступно по *[ссылке](https://disk.yandex.ru/i/6Efipp3Gxs9-Jw)*
+
+---
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_1/docker-compose.yml b/minhasapov_ruslan_lab_1/docker-compose.yml
new file mode 100644
index 0000000..b1ce000
--- /dev/null
+++ b/minhasapov_ruslan_lab_1/docker-compose.yml
@@ -0,0 +1,45 @@
+services: # начало объявления сервисов, которые будут запущены
+ postgres: # указываем название сервиса
+ image: postgres:14 # указываем название и/или версии образа Docker для Postgres
+ restart: always # автоматический перезапуск контейнера, если он упал
+ ports:
+ - "5432:5432" # пробрасываем порт 5432 из контейнера на хост-машину
+ # (слева порт хостовой машины, справа порт контейнера)
+ volumes: # указываются монтированные тома, папки, файлы
+ - postgres_data:/var/lib/postgresql/data # монтирует том postgres_data к /var/lib/postgresql/data,
+ # чтобы данные Postgres сохранялись после перезапуска контейнера
+ environment: # задаем переменные окружения для Postgres, такие как:
+ POSTGRES_USER: postgres_user # имя пользователя
+ POSTGRES_PASSWORD: postgres_password # пароль пользователя
+ POSTGRES_DB: postgres_db # имя БД
+
+ gitea:
+ image: gitea/gitea:1.22.2
+ restart: always
+ ports:
+ - "3000:3000" # пробрасывает порты 3000 (HTTP, веб-интерфейс)
+ - "222:22" # и 22 (SSH) из контейнера на хост-машину
+ volumes:
+ - gitea_data:/data
+ environment:
+ GITEA__database__DB_TYPE: postgres
+ GITEA__database__HOST: postgres:5432 # хост бд, название сервиса выступает в роли доменного имени и внутри сети разрешается в ip нужного контейнера
+ GITEA__database__NAME: postgres_db
+ GITEA__database__USER: postgres_user
+ GITEA__database__PASSWD: postgres_password
+ depends_on: # указывается зависимость запуска контейнера от другого сервиса
+ - postgres # Gitea зависит от Postgres, поэтому Postgres будет запущен первым
+ - redis
+
+ redis:
+ image: redis:latest
+ restart: always
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis_data:/data
+
+volumes: # определяем тома, которые будут использоваться сервисами
+ postgres_data:
+ gitea_data:
+ redis_data:
diff --git a/presnyakova_victoria_lab_1/docker-compose.yml b/presnyakova_victoria_lab_1/docker-compose.yml
new file mode 100644
index 0000000..4f5c57d
--- /dev/null
+++ b/presnyakova_victoria_lab_1/docker-compose.yml
@@ -0,0 +1,29 @@
+services:
+ rabbitmq:
+ image: rabbitmq:3.12.8-management
+ environment:
+ RABBITMQ_DEFAULT_USER: admin
+ RABBITMQ_DEFAULT_PASS: admin
+ ports:
+ - 15672:15672
+ volumes:
+ - rabbitmq-data:/var/lib/rabbitmq
+
+ mediawiki:
+ image: mediawiki
+ ports:
+ - 8081:80
+ volumes:
+ - mediawiki-data:/var/files/mediawiki
+
+ wordpress:
+ image: wordpress
+ ports:
+ - 8082:80
+ volumes:
+ - wordpress-data:/var/files/wordpress
+
+volumes:
+ rabbitmq-data:
+ mediawiki-data:
+ wordpress-data:
\ No newline at end of file
diff --git a/presnyakova_victoria_lab_1/readme.md b/presnyakova_victoria_lab_1/readme.md
new file mode 100644
index 0000000..b413a9e
--- /dev/null
+++ b/presnyakova_victoria_lab_1/readme.md
@@ -0,0 +1,26 @@
+# Docker Compose: RabbitMQ, Mediawiki, Wordpress
+
+## Описание проекта
+
+Этот проект разворачивает три сервиса с помощью Docker Compose:
+1. **RabbitMQ** — брокер сообщений.
+2. **Mediawiki** — движок вики.
+3. **Wordpress** — популярная система управления контентом.
+
+
+## Команды для запуска
+
+Из директории с файлом docker-compose.yml запустить сервисы docker-compose up --build
+
+## Сервисы и порты
+1. **RabbitMQ:**
+ Доступ по адресу http://localhost:15672/ (логин: admin, пароль: admin).
+
+2. **Mediawiki:**
+ Доступ по адресу http://localhost:8081/.
+
+
+3. **Wordpress:**
+ Доступ по адресу http://localhost:8082/.
+
+## Видео https://drive.google.com/file/d/1NvsMFoMU2ecsQ17EouqB_ZaLBskonHv0/view?usp=sharing
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_1/README.md b/yakovleva_yulia_lab_1/README.md
new file mode 100644
index 0000000..62ae9ad
--- /dev/null
+++ b/yakovleva_yulia_lab_1/README.md
@@ -0,0 +1,50 @@
+# Отчет по лабораторной работе 1
+
+## Описание
+Docker Compose используется для одновременного управления несколькими контейнерами, входящими в состав приложения. Он позволяет хранить все настройки проекта в одном контейнере, что позволяет потом беспроблемно использовать те же настройки проекта на другом сервере. docker-compose.yml позволяет настраивать и документировать все зависимости служб приложения. В нем указываются образ, порт(который можно пробросить), окрущающие среды, томы и зависимости (если нужно).
+
+## Описание docker-compose.yml
+Были выбраны следующие сервисы:
+
+1. Postgres
+2. Redmine
+3. Gitea
+
+В файле у каждого сервиса пробрасывается порт. Каждый сервис содержит том, который используются для хранения данных. Запуск Redmine и Gitea имеют зависимость от Postgres, чтобы они имели возможность накатить миграции и заполнить таблицы после создания базы. У каждого сервиса заполнен паарметр environment, где указываются данные для БД.
+
+## Инструкция по запуску
+
+1. Запустить приложение Docker Hub
+2. Открыть консоль с помощью команды cd перейти к папке, где находится файл docker-compose.yml
+3. Ввести команду для поднятия контейнеров
+```
+docker compose up -d
+```
+Дожидаемся запуска сервисов:
+```
+[+] Running 3/3
+ ✔ Container yakovleva_julia_lab_1-postgres-1 Started 0.5s
+ ✔ Container yakovleva_julia_lab_1-gitea-1 Started 1.2s
+ ✔ Container yakovleva_julia_lab_1-redmine-1 Started 1.1s
+```
+
+4. Убедиться, что сервисы успешно запущены можно по команде:
+```
+docker compose ps
+```
+Отобразится таблица с запущенными сервисами:
+```
+NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
+yakovleva_julia_lab_1-gitea-1 gitea/gitea:1.22.2 "/usr/bin/entrypoint…" gitea 19 minutes ago Up 6 seconds 22/tcp, 0.0.0.0:3002->3000/tcp
+yakovleva_julia_lab_1-postgres-1 postgres:16.4 "docker-entrypoint.s…" postgres 19 minutes ago Up 6 seconds 0.0.0.0:5433->5432/tcp
+yakovleva_julia_lab_1-redmine-1 redmine:latest "/docker-entrypoint.…" redmine 19 minutes ago Up 6 seconds 0.0.0.0:3003->3000/tcp
+```
+
+Также можно посомтреть логи каждого севриса при вводе команды:
+```
+docker compose logs -ft (название сервиса)
+```
+-ft необходим для того, чтобы читать логи сервиса в реальном времени
+
+## Видео
+[Видео](https://disk.yandex.ru/i/Ds2f4xO8vQ8XPA) работоспособности.
diff --git a/yakovleva_yulia_lab_1/docker-compose.yml b/yakovleva_yulia_lab_1/docker-compose.yml
new file mode 100644
index 0000000..fb4ae1d
--- /dev/null
+++ b/yakovleva_yulia_lab_1/docker-compose.yml
@@ -0,0 +1,45 @@
+services:
+ postgres:
+ image: postgres:16.4
+ ports:
+ - "5433:5432"
+ environment:
+ POSTGRES_USER: redmine
+ POSTGRES_PASSWORD: redminepass
+ POSTGRES_DB: redmine
+ volumes:
+ - ./postgres_data:/var/lib/postgresql/data # Volume для хранения данных PostgreSQL
+ restart: always
+
+ redmine:
+ image: redmine:latest
+ ports:
+ - "3003:3000"
+ volumes:
+ - ./redmine_data:/usr/src/redmine/files # Volume для хранения данных Redmine
+ environment:
+ REDMINE_DB_POSTGRES: postgres # Имя сервиса базы данных
+ REDMINE_DB_USERNAME: redmine
+ REDMINE_DB_PASSWORD: redminepass
+ REDMINE_DB_DATABASE: redmine
+ depends_on:
+ - postgres
+ restart: always
+
+ gitea:
+ image: gitea/gitea:1.22.2
+ ports:
+ - "3002:3000" # Проброс порта для доступа к Gitea
+ volumes:
+ - ./gitea_data:/data # Volume для хранения данных Gitea
+ environment:
+ - USER_UID=1000
+ - USER_GID=1000
+ - GITEA__database__DB_TYPE=postgres
+ - GITEA__database__HOST=postgres:5432
+ - GITEA__database__NAME=gitea
+ - GITEA__database__USER=gitea
+ - GITEA__database__PASSWD=gitea_password
+ depends_on:
+ - postgres
+ restart: always
diff --git a/yakovleva_yulia_lab_2/.dockerignore b/yakovleva_yulia_lab_2/.dockerignore
new file mode 100644
index 0000000..4d72b4f
--- /dev/null
+++ b/yakovleva_yulia_lab_2/.dockerignore
@@ -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/**
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_2/.gitignore b/yakovleva_yulia_lab_2/.gitignore
new file mode 100644
index 0000000..5e57f18
--- /dev/null
+++ b/yakovleva_yulia_lab_2/.gitignore
@@ -0,0 +1,484 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
diff --git a/yakovleva_yulia_lab_2/App1/App1.csproj b/yakovleva_yulia_lab_2/App1/App1.csproj
new file mode 100644
index 0000000..c1a8aa3
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App1/App1.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ Linux
+
+
+
+
+
+
+
+
+
diff --git a/yakovleva_yulia_lab_2/App1/Dockerfile b/yakovleva_yulia_lab_2/App1/Dockerfile
new file mode 100644
index 0000000..855ad62
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App1/Dockerfile
@@ -0,0 +1,20 @@
+FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["App1.csproj", "App1/"]
+RUN dotnet restore "App1/App1.csproj"
+WORKDIR "/src/App1"
+COPY . .
+RUN dotnet build "App1.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "App1.csproj" -c $BUILD_CONFIGURATION -o /app/publish --self-contained false --no-restore
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "App1.dll"]
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_2/App1/Program.cs b/yakovleva_yulia_lab_2/App1/Program.cs
new file mode 100644
index 0000000..4bdd5b7
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App1/Program.cs
@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+
+class Program
+{
+ static void Main()
+ {
+ string inputPath = Environment.GetEnvironmentVariable("DATA_PATH") ?? ".\\data";
+ string outputPath = Environment.GetEnvironmentVariable("RESULT_PATH") ?? ".\\result";
+
+ try
+ {
+ string[] files = Directory.GetFiles(inputPath);
+
+ using (StreamWriter writer = new StreamWriter(Path.Combine(outputPath, "data.txt")))
+ {
+ foreach (string file in files)
+ {
+ int lineCount = File.ReadAllLines(file).Length;
+ writer.WriteLine(lineCount);
+ }
+ }
+
+ Console.WriteLine("Файл data.txt успешно создан");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Произошла ошибка: " + ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_2/App1/Properties/launchSettings.json b/yakovleva_yulia_lab_2/App1/Properties/launchSettings.json
new file mode 100644
index 0000000..6cd0db9
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App1/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "App1": {
+ "commandName": "Project"
+ },
+ "Container (Dockerfile)": {
+ "commandName": "Docker"
+ }
+ }
+}
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_2/App2/App2.csproj b/yakovleva_yulia_lab_2/App2/App2.csproj
new file mode 100644
index 0000000..983a07e
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App2/App2.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ Linux
+
+
+
+
+
+
+
diff --git a/yakovleva_yulia_lab_2/App2/Dockerfile b/yakovleva_yulia_lab_2/App2/Dockerfile
new file mode 100644
index 0000000..97eeb20
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App2/Dockerfile
@@ -0,0 +1,20 @@
+FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["App2.csproj", "App2/"]
+RUN dotnet restore "App2/App2.csproj"
+WORKDIR "/src/App2"
+COPY . .
+RUN dotnet build "App2.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "App2.csproj" -c $BUILD_CONFIGURATION -o /app/publish --self-contained false --no-restore
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "App2.dll"]
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_2/App2/Program.cs b/yakovleva_yulia_lab_2/App2/Program.cs
new file mode 100644
index 0000000..701f885
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App2/Program.cs
@@ -0,0 +1,35 @@
+using System;
+using System.IO;
+using System.Linq;
+
+class Program
+{
+ static void Main()
+ {
+ string inputPath = Environment.GetEnvironmentVariable("DATA_PATH") ?? ".\\data";
+ string outputPath = Environment.GetEnvironmentVariable("RESULT_PATH") ?? ".\\result";
+
+ try
+ {
+ string dataFilePath = Path.Combine(inputPath, "data.txt");
+
+ // Чтение всех строк из файла
+ string[] lines = File.ReadAllLines(dataFilePath);
+ int[] numbers = lines.Select(int.Parse).ToArray();
+
+ // Поиск наибольшего числа
+ int maxNumber = numbers.Max();
+
+ // Подсчет количества таких чисел
+ int maxCount = numbers.Count(n => n == maxNumber);
+
+ File.WriteAllText(Path.Combine(outputPath, "result.txt"), maxCount.ToString());
+
+ Console.WriteLine($"Количество наибольших чисел: {maxCount}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Произошла ошибка: " + ex.Message);
+ }
+ }
+}
diff --git a/yakovleva_yulia_lab_2/App2/Properties/launchSettings.json b/yakovleva_yulia_lab_2/App2/Properties/launchSettings.json
new file mode 100644
index 0000000..4371dae
--- /dev/null
+++ b/yakovleva_yulia_lab_2/App2/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "App2": {
+ "commandName": "Project"
+ },
+ "Container (Dockerfile)": {
+ "commandName": "Docker"
+ }
+ }
+}
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_2/Distributed computing and applications.sln b/yakovleva_yulia_lab_2/Distributed computing and applications.sln
new file mode 100644
index 0000000..9d192f7
--- /dev/null
+++ b/yakovleva_yulia_lab_2/Distributed computing and applications.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34728.123
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App2", "App2\App2.csproj", "{D03073B1-0CB3-4CA9-A9D8-F1FB804F92D1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App1", "App1\App1.csproj", "{729837A1-27A7-4D48-BBB2-21F68E5F7766}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileGeneration", "FileGeneration\FileGeneration.csproj", "{E7AD8A8C-C957-41FF-B92A-924465D73DDB}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D03073B1-0CB3-4CA9-A9D8-F1FB804F92D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D03073B1-0CB3-4CA9-A9D8-F1FB804F92D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D03073B1-0CB3-4CA9-A9D8-F1FB804F92D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D03073B1-0CB3-4CA9-A9D8-F1FB804F92D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {729837A1-27A7-4D48-BBB2-21F68E5F7766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {729837A1-27A7-4D48-BBB2-21F68E5F7766}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {729837A1-27A7-4D48-BBB2-21F68E5F7766}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {729837A1-27A7-4D48-BBB2-21F68E5F7766}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E7AD8A8C-C957-41FF-B92A-924465D73DDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E7AD8A8C-C957-41FF-B92A-924465D73DDB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E7AD8A8C-C957-41FF-B92A-924465D73DDB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E7AD8A8C-C957-41FF-B92A-924465D73DDB}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {36392E9A-B3A7-4CFF-B577-B2A3146B3007}
+ EndGlobalSection
+EndGlobal
diff --git a/yakovleva_yulia_lab_2/FileGeneration/Dockerfile b/yakovleva_yulia_lab_2/FileGeneration/Dockerfile
new file mode 100644
index 0000000..18b2cf0
--- /dev/null
+++ b/yakovleva_yulia_lab_2/FileGeneration/Dockerfile
@@ -0,0 +1,22 @@
+#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/runtime:8.0 AS base
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["FileGeneration.csproj", "FileGeneration/"]
+RUN dotnet restore "FileGeneration/FileGeneration.csproj"
+WORKDIR "/src/FileGeneration"
+COPY . .
+RUN dotnet build "FileGeneration.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "FileGeneration.csproj" -c $BUILD_CONFIGURATION -o /app/publish --self-contained false --no-restore
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "FileGeneration.dll"]
diff --git a/yakovleva_yulia_lab_2/FileGeneration/FileGeneration.csproj b/yakovleva_yulia_lab_2/FileGeneration/FileGeneration.csproj
new file mode 100644
index 0000000..983a07e
--- /dev/null
+++ b/yakovleva_yulia_lab_2/FileGeneration/FileGeneration.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ Linux
+
+
+
+
+
+
+
diff --git a/yakovleva_yulia_lab_2/FileGeneration/Program.cs b/yakovleva_yulia_lab_2/FileGeneration/Program.cs
new file mode 100644
index 0000000..dca4532
--- /dev/null
+++ b/yakovleva_yulia_lab_2/FileGeneration/Program.cs
@@ -0,0 +1,41 @@
+using System;
+using System.IO;
+
+class Program
+{
+ static void Main()
+ {
+ string directoryPath = Environment.GetEnvironmentVariable("DATA_PATH") ?? ".\\data";
+
+ Random random = new Random();
+
+ try
+ {
+ if (!Directory.Exists(directoryPath))
+ {
+ Directory.CreateDirectory(directoryPath);
+ }
+
+ for (int i = 1; i <= 100; i++)
+ {
+ int lineCount = random.Next(1, 101);
+
+ string filePath = Path.Combine(directoryPath, $"file_{i}.txt");
+
+ using (StreamWriter writer = new StreamWriter(filePath))
+ {
+ for (int j = 0; j < lineCount; j++)
+ {
+ writer.WriteLine($"{j + 1}");
+ }
+ }
+
+ Console.WriteLine($"Файл {filePath} успешно создан с {lineCount} строками.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Произошла ошибка: " + ex.Message);
+ }
+ }
+}
diff --git a/yakovleva_yulia_lab_2/FileGeneration/Properties/launchSettings.json b/yakovleva_yulia_lab_2/FileGeneration/Properties/launchSettings.json
new file mode 100644
index 0000000..bb4e510
--- /dev/null
+++ b/yakovleva_yulia_lab_2/FileGeneration/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "FileGeneration": {
+ "commandName": "Project"
+ },
+ "Container (Dockerfile)": {
+ "commandName": "Docker"
+ }
+ }
+}
\ No newline at end of file
diff --git a/yakovleva_yulia_lab_2/README.md b/yakovleva_yulia_lab_2/README.md
new file mode 100644
index 0000000..22d4d87
--- /dev/null
+++ b/yakovleva_yulia_lab_2/README.md
@@ -0,0 +1,34 @@
+# Лабораторная работа 2
+
+## Описание
+В данной лабораторной работе были изучены файлы сборки образов и создание их для приложений. Сборки образов необходимы для контейниризации созданных приложений. В файле сборки мы указываем папку из которой копируем в контейнер и собираем проект.
+
+## Описание работы
+Были выбраны варианты для первого приложения - 3 вариант и для второго 3 вариант.
+
+В первом приложении мы ищем все файлы из папки output и записываем количество строк каждого файла в файл data.txt. Затем второе приложение ищет в data.txt наибольшее значения и записывает количество наибольших значений в файл result.txt.
+
+## Запуск
+1. Для начала необходимо запустить Docker
+2. Открыть консоль, перейти в папку с docker-compose.yml
+3. Ввести команду:
+```
+docker compose up --build
+```
+`--build` необходим для принудительной сборки контейнеров, даже если они уже запущены.
+
+## Результат
+В результате в логах видим, что приложения успешно отработали:
+```
+app2-1 | Количество наибольших чисел: 1
+app1-1 exited with code 0
+app2-1 exited with code 0
+```
+И также видим в папке Files две созданные папки: data и result. В data находятся сгенерированные файлы, а в result - файл с результатом.
+
+## Описание docker-compose.yml
+В docker-compose.yml запускаются 3 сервиса: первый генерирует файлы с рандомным набором строк, второй сервис записывает количество строк каждого файла в data.txt, а третий сервис определяет количество наибольших чисел из файла data.txt и записывает результат в файл result.txt. Также используются зависимости `depends_on`, второй сервис зависит от запуска первого, и третий севрис зависит от запуска второго.
+С помощью `volumes` создаем буферную область, которая позволяет использовать двум севрисам одну папку. `build` позволяет использовать только что собранный образ в момент запуска.
+
+## Видеоотчёт
+[Видеоотчёт](https://disk.yandex.ru/i/Ds2f4xO8vQ8XPA)
diff --git a/yakovleva_yulia_lab_2/docker-compose.yml b/yakovleva_yulia_lab_2/docker-compose.yml
new file mode 100644
index 0000000..b96f359
--- /dev/null
+++ b/yakovleva_yulia_lab_2/docker-compose.yml
@@ -0,0 +1,33 @@
+services:
+ generatefiles:
+ build: ./FileGeneration/
+ volumes:
+ - ./Files/data:/var/data
+ - buffer:/var/result
+ environment:
+ DATA_PATH: "/var/data"
+ RESULT_PATH: "/var/result"
+
+ app1:
+ build: ./App1/
+ depends_on:
+ - generatefiles
+ volumes:
+ - ./Files/data:/var/data
+ - buffer:/var/result
+ environment:
+ DATA_PATH: "/var/data"
+ RESULT_PATH: "/var/result"
+
+ app2:
+ build: ./App2/
+ depends_on:
+ - app1
+ volumes:
+ - buffer:/var/data
+ - ./Files/result:/var/result
+ environment:
+ DATA_PATH: "/var/data"
+ RESULT_PATH: "/var/result"
+volumes:
+ buffer:
\ No newline at end of file
diff --git a/zhimolostnova_anna_lab_1/.env b/zhimolostnova_anna_lab_1/.env
new file mode 100644
index 0000000..2f3051d
--- /dev/null
+++ b/zhimolostnova_anna_lab_1/.env
@@ -0,0 +1,15 @@
+# Moodle
+MARIADB_USER=moodle_user
+MARIADB_ROOT_PASSWORD=moodle_root_password
+MARIADB_PASSWORD=moodle_password
+MARIADB_DATABASE=moodle_db
+
+# WordPress
+MYSQL_ROOT_PASSWORD=wordpress_root_password
+MYSQL_DATABASE=wordpress_db
+MYSQL_USER=wordpress_user
+MYSQL_PASSWORD=wordpress_password
+
+# Ports
+MOODLE_PORT=80
+WORDPRESS_PORT=8081
diff --git a/zhimolostnova_anna_lab_1/README.md b/zhimolostnova_anna_lab_1/README.md
new file mode 100644
index 0000000..cd25da8
--- /dev/null
+++ b/zhimolostnova_anna_lab_1/README.md
@@ -0,0 +1,70 @@
+# Отчет по лабораторной работе №1
+
+## Поставленные задачи
+
+1. Установить средство контейнеризации **docker**.
+2. Изучить применение и принципы **docker**.
+3. Изучить утилиту **docker-compose** и структуру файла `docker-compose.yml`.
+4. Развернуть не менее 3х различных сервисов при помощи **docker-compose**.
+5. Оформить отчёт в формате **Markdown** и создать **Pull Request** в git-репозитории.
+
+## Сервисы
+
+1. **Moodle** — платформа для дистанционного обучения (LMS).
+2. **WordPress** — популярная система управления контентом (CMS).
+3. **MariaDB** — реляционная база данных для хранения данных.
+
+## Запуск работы
+
+1. Убедиться, что установлены необходимые технологии:
+ - Docker: Платформа для контейнеризации приложений.
+ - Docker Compose: Инструмент для запуска многоконтейнерных приложений на основе `docker-compose.yaml`. Обычно поставляется вместе с Docker. Чтобы проверить, установлена ли утилита, нужно запустить команду:
+```bash
+ docker-compose --version
+```
+
+2. В директории, где находится файл `docker-compose.yaml`, выполнить следующую команду для запуска всех сервисов:
+```bash
+ docker compose up -d
+```
+Эта команда запустит контейнеры в фоновом режиме. Для отслеживания процесса можно использовать команду без флага `-d`, чтобы контейнеры запускались в режиме вывода логов.
+
+3. После успешного запуска можно перейти в браузер и проверить:
+ - Moodle доступен по адресу: [http://localhost:80](http://localhost:80) (или по порту, указанному в переменной `MOODLE_PORT`).
+ - WordPress доступен по адресу: [http://localhost:8081](http://localhost:8081) (или по порту, указанному в переменной `WORDPRESS_PORT`).
+
+## Другие возможности
+
+1. Чтобы остановить контейнеры, нужно выполнить команду:
+```bash
+ docker compose down
+```
+Эта команда остановит все контейнеры и удалит их, но сохранит данные в volume-ах.
+
+2. Чтобы поставить контейнеры на паузу (без их остановки), нужно выполнить команду:
+```bash
+ docker compose pause
+```
+Контейнеры останутся в памяти, но их процессы будут приостановлены.
+
+3. Чтобы снять паузу, нужно выполнить команду:
+```bash
+ docker compose unpause
+```
+
+## Описание работы
+
+1. **Сервис mariadb:**
+ Этот сервис поднимает контейнер с MariaDB (образ от Bitnami), который хранит данные для Moodle. Здесь используются переменные окружения для конфигурации имени базы данных, паролей и пользователя. Данные базы хранятся в volume moodle_db_data, чтобы они сохранялись даже после перезапуска контейнера.
+2. **Сервис wordpress_db:**
+ Аналогично mariadb, этот сервис поднимает контейнер MariaDB для базы данных WordPress. Данные хранятся в отдельном volume wordpress_db_data.
+3. **Сервис moodle:**
+ Это контейнер с приложением Moodle, который зависит от настроенного контейнера базы данных mariadb. Moodle использует переменные окружения для подключения к своей базе данных. Также используются volumes для хранения файлов Moodle и данных, загружаемых пользователями, что обеспечивает их сохранение между перезапусками.
+4. **Сервис wordpress:**
+ Этот контейнер поднимает приложение WordPress, которое зависит от сервиса базы данных wordpress_db. Volumes используются для хранения файлов WordPress (включая темы, плагины и медиафайлы), что гарантирует их сохранность между перезапусками.
+
+Построчное описание docker-compose.yaml выполнено в самом файле.
+
+## Демонстрационное видео
+
+Видеозапись доступна по адресу: [https://vk.com/video193898050_456240868?list=ln-FRUcWLBDSsFz3YNUEk](https://vk.com/video193898050_456240868?list=ln-FRUcWLBDSsFz3YNUEk)
\ No newline at end of file
diff --git a/zhimolostnova_anna_lab_1/docker-compose.yaml b/zhimolostnova_anna_lab_1/docker-compose.yaml
new file mode 100644
index 0000000..dd7a95e
--- /dev/null
+++ b/zhimolostnova_anna_lab_1/docker-compose.yaml
@@ -0,0 +1,62 @@
+services:
+ mariadb: # Определение сервиса
+ image: bitnami/mariadb:11.4 # Имя образа docker и его версия (предпочтиельно указывать версию явно, вместо использования latest)
+ container_name: mariadb # Имя контейнера
+ environment: # Переменные окружения
+ - MARIADB_USER=${MARIADB_USER} # Имя пользователя
+ - MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD} # Пароль для пользователя root
+ - MARIADB_PASSWORD=${MARIADB_PASSWORD} # Пароль для пользователя
+ - MARIADB_DATABASE=${MARIADB_DATABASE} # Имя базы данных
+ volumes: # Настройка volumes для хранения данных
+ - moodle_db_data:/bitnami/mariadb # Хранение данных в volume moodle_db_data
+
+ # Далее комментарии идентичны
+ wordpress_db:
+ image: mariadb:11.4
+ container_name: wordpress_db
+ environment:
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
+ MYSQL_DATABASE: ${MYSQL_DATABASE}
+ MYSQL_USER: ${MYSQL_USER}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD}
+ volumes:
+ - wordpress_db_data:/var/lib/mysql # Хранение данных в volume wordpress_db_data
+
+ moodle:
+ image: bitnami/moodle:4.4
+ container_name: moodle
+ environment:
+ - MOODLE_DATABASE_HOST=mariadb # Указание хоста базы данных
+ - MOODLE_DATABASE_PORT_NUMBER=3306
+ - MOODLE_DATABASE_NAME=${MARIADB_DATABASE}
+ - MOODLE_DATABASE_USER=${MARIADB_USER}
+ - MOODLE_DATABASE_PASSWORD=${MARIADB_PASSWORD}
+ volumes:
+ - moodle_data:/bitnami/moodle # Хранение данных в volume moodle_data
+ - moodledata_data:/bitnami/moodledata # Хранение данных (moodledata) в отдельном volume
+ depends_on: # Зависимость от сервиса mariadb. Данный сервис не запустится, пока не запустится mariadb
+ - mariadb
+ ports: # Проброс порта для доступа
+ - "${MOODLE_PORT}:8080"
+
+ wordpress:
+ image: wordpress:6
+ container_name: wordpress
+ environment:
+ WORDPRESS_DB_HOST: wordpress_db
+ WORDPRESS_DB_USER: ${MYSQL_USER}
+ WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
+ WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
+ volumes:
+ - wordpress_data:/var/www/html # Хранение данных в volume wordpress_data
+ depends_on: # Зависимость от сервиса wordpress_db, Данный сервис не запустится, пока не запустится wordpress_db
+ - wordpress_db
+ ports:
+ - "${WORDPRESS_PORT}:80"
+
+volumes: # Определение volumes для хранения данных
+ moodle_db_data:
+ wordpress_db_data:
+ moodle_data:
+ moodledata_data:
+ wordpress_data:
diff --git a/zhimolostnova_anna_lab_2/.gitignore b/zhimolostnova_anna_lab_2/.gitignore
new file mode 100644
index 0000000..cc114d2
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/.gitignore
@@ -0,0 +1,2 @@
+result/
+data/
\ No newline at end of file
diff --git a/zhimolostnova_anna_lab_2/README.md b/zhimolostnova_anna_lab_2/README.md
new file mode 100644
index 0000000..7e25ae0
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/README.md
@@ -0,0 +1,50 @@
+# Отчет по лабораторной работе №2
+
+## Поставленные задачи
+
+1. Согласно вашему варианту (выбирайте любой) разработать два приложения такие, что результат первого является исходными данными для второго.
+2. Изучить файлы сборки образов docker и разработать их для созданных приложений.
+3. Собрать файл docker-compose.yml для запуска приложений. Разобраться с монтированием каталогов из хост-системы.
+4. Правильно закоммитить результат без лишних файлов.
+5. Оформить pull request по правилам и отправить его на проверку.
+
+## Варианты заданий
+
+1. **Программа 1 под вариантом 5:** Ищет в каталоге /var/data файл с самым коротким названием и перекладывает его в /var/result/data.txt.
+2. **Программа 2 под вариантом 2:** Ищет наименьшее число из файла /var/data/data.txt и сохраняет его третью степень в /var/result/result.txt.
+
+## Запуск работы
+
+1. Убедиться, что установлены необходимые технологии:
+ - Docker: Платформа для контейнеризации приложений.
+ - Docker Compose: Инструмент для запуска многоконтейнерных приложений на основе `docker-compose.yaml`. Обычно поставляется вместе с Docker. Чтобы проверить, установлена ли утилита, нужно запустить команду:
+```bash
+ docker-compose --version
+```
+
+2. В директории, где находится файл `docker-compose.yaml`, выполнить следующую команду для запуска всех сервисов:
+```bash
+ docker-compose up --build
+```
+Эта команда сначала выполнит сборку, а затем запустит контейнеры.
+
+3. После успешного запуска можно перейти в каталог проекта и увидеть папку data со сгенерированными файлами и папку result, в которой два текстовых файла:
+ - data.txt - файл, полученный после выполнения первой программы.
+ - result.txt -файл, полученный после выполнения второй программы.
+
+## Ход работы
+
+1. **Реализация генератора файлов**
+ Написание скрипта generate_files.py, который генерирует случайные файлы с числами и сохраняет их в папке data. Создание Dockerfile для генератора, который устанавливает Python и копирует скрипт в контейнер. (ПОстрочное описание скрипта и Dockerfile представлено в папке generator)
+2. **Реализация первого приложения**
+ Создание первой программы в app.py, которая ищет файл с самым коротким именем в папке data и копирует его содержимое в result/data.txt. Создание Dockerfile для первого приложения с аналогичной для Dockerfile из generate структурой.
+3. **Реализация второго приложения**
+ Создание второй программы в app.py, которая находит минимальное число из result/data.txt и записывает его третью степень в result/result.txt. Создание Dockerfile для второго приложения с аналогичной для Dockerfile из generate структурой.
+4. **Создание файла docker-compose.yml**
+ Создание файла docker-compose.yml, в котором описываются все три сервиса (генератор, первое приложение, второе приложение). Необходимо настроить их зависимости и монтирование папок.
+
+Построчное описание docker-compose.yaml и других файлов выполнено в самих файлах.
+
+## Демонстрационное видео
+
+Видеозапись доступна по адресу: [https://vk.com/video193898050_456240869](https://vk.com/video193898050_456240869)
\ No newline at end of file
diff --git a/zhimolostnova_anna_lab_2/docker-compose.yml b/zhimolostnova_anna_lab_2/docker-compose.yml
new file mode 100644
index 0000000..22c6970
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/docker-compose.yml
@@ -0,0 +1,28 @@
+services:
+ # Сервис генератора файлов
+ generator:
+ build:
+ context: ./generator # Путь к директории с Dockerfile и скриптом генератора
+ volumes:
+ - ./data:/var/data # Монтирование локальной папки data в /var/data в контейнере
+ entrypoint: ["sh", "-c", "if [ -z \"$(ls -A /var/data)\" ]; then python generate_files.py; else echo '/var/data is not empty'; fi"]
+ # Запуск команды, проверяющей, пуста ли папка /var/data. Если пуста, запускается скрипт генерации данных.
+
+ # Первый сервис, который ищет файл с коротким именем
+ first_app:
+ build:
+ context: ./first_app # Путь к директории с Dockerfile для первого приложения
+ volumes:
+ - ./data:/var/data # Монтирование локальной папки data в /var/data
+ - ./result:/var/result # Монтирование локальной папки result в /var/result
+ depends_on:
+ - generator # Указывает, что первый сервис зависит от завершения работы генератора
+
+ # Второй сервис, который ищет минимальное число
+ second_app:
+ build:
+ context: ./second_app # Путь к директории с Dockerfile для второго приложения
+ volumes:
+ - ./result:/var/result # Монтирование локальной папки result в /var/result
+ depends_on:
+ - first_app # Указывает, что второй сервис зависит от завершения работы первого сервиса
diff --git a/zhimolostnova_anna_lab_2/first_app/Dockerfile b/zhimolostnova_anna_lab_2/first_app/Dockerfile
new file mode 100644
index 0000000..fa8d828
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/first_app/Dockerfile
@@ -0,0 +1,8 @@
+# См. описание Dockerfile для генератора файлов (generator)
+FROM python:3.9-slim
+
+WORKDIR /app
+
+COPY app.py /app/
+
+CMD ["python", "app.py"]
diff --git a/zhimolostnova_anna_lab_2/first_app/app.py b/zhimolostnova_anna_lab_2/first_app/app.py
new file mode 100644
index 0000000..aa22b3c
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/first_app/app.py
@@ -0,0 +1,28 @@
+import os
+
+def find_shortest_filename(source_dir, result_file):
+ # Существует ли папка result? Если нет, то создается
+ result_dir = os.path.dirname(result_file)
+ if not os.path.exists(result_dir):
+ os.makedirs(result_dir)
+ print(f"Created directory {result_dir}")
+
+ # Поиск файлов в исходной директории
+ files = [f for f in os.listdir(source_dir) if os.path.isfile(os.path.join(source_dir, f))]
+
+ if not files:
+ print(f"No files found in {source_dir}")
+ return
+
+ # Поиск файла с самым коротким именем
+ shortest_file = min(files, key=len)
+ shortest_file_path = os.path.join(source_dir, shortest_file)
+
+ # Копирование содержимого в result/data.txt. Файл будет перезаписан, если уже существует
+ with open(shortest_file_path, 'r') as f_in, open(result_file, 'w') as f_out:
+ f_out.write(f_in.read())
+
+ print(f"Moved {shortest_file} to {result_file}")
+
+if __name__ == "__main__":
+ find_shortest_filename('/var/data', '/var/result/data.txt')
diff --git a/zhimolostnova_anna_lab_2/generator/Dockerfile b/zhimolostnova_anna_lab_2/generator/Dockerfile
new file mode 100644
index 0000000..966d9c5
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/generator/Dockerfile
@@ -0,0 +1,13 @@
+# задание базового образа, на основе которого строится контейнер.
+# в данном случае slim версия, , которая содержит минимальные необходимые
+# компоненты для запуска Python, что уменьшает размер контейнера.
+FROM python:3.9-slim
+
+# задание рабочей директории внутри контейнера.
+WORKDIR /app
+
+# перемещение файлов из хоста в контейнер.
+COPY generate_files.py /app/
+
+# команда, выполняющаяся при запуске контейнера.
+CMD ["python", "generate_files.py"]
\ No newline at end of file
diff --git a/zhimolostnova_anna_lab_2/generator/generate_files.py b/zhimolostnova_anna_lab_2/generator/generate_files.py
new file mode 100644
index 0000000..2db8ac7
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/generator/generate_files.py
@@ -0,0 +1,31 @@
+import os
+import random
+import string
+
+def generate_random_filename(length):
+ return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) + '.txt'
+
+def generate_data_files(directory, num_files, min_lines, max_lines):
+ # Создание директории, если таковой не существует
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ # Проверка, пустая ли директория
+ if os.listdir(directory):
+ print(f"{directory} is not empty, skipping file generation.")
+ return
+
+ # Генерация файлов
+ for _ in range(num_files):
+ file_name = generate_random_filename(random.randint(1, 20))
+ file_path = os.path.join(directory, file_name)
+
+ with open(file_path, 'w') as f:
+ num_lines = random.randint(min_lines, max_lines)
+ for _ in range(num_lines):
+ f.write(f"{random.randint(1, 1000)}\n")
+
+ print(f"Generated file: {file_path}")
+
+if __name__ == "__main__":
+ generate_data_files('/var/data', num_files=random.randint(5, 15), min_lines=1, max_lines=50)
diff --git a/zhimolostnova_anna_lab_2/second_app/Dockerfile b/zhimolostnova_anna_lab_2/second_app/Dockerfile
new file mode 100644
index 0000000..fa8d828
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/second_app/Dockerfile
@@ -0,0 +1,8 @@
+# См. описание Dockerfile для генератора файлов (generator)
+FROM python:3.9-slim
+
+WORKDIR /app
+
+COPY app.py /app/
+
+CMD ["python", "app.py"]
diff --git a/zhimolostnova_anna_lab_2/second_app/app.py b/zhimolostnova_anna_lab_2/second_app/app.py
new file mode 100644
index 0000000..b119f8b
--- /dev/null
+++ b/zhimolostnova_anna_lab_2/second_app/app.py
@@ -0,0 +1,26 @@
+import os
+
+def find_smallest_number_and_cube(input_file, output_file):
+ # Проверка, существует ли файл data.txt
+ if not os.path.exists(input_file):
+ print(f"File {input_file} does not exist.")
+ return
+
+ # Чтение чисел из файла
+ with open(input_file, 'r') as f:
+ numbers = [int(line.strip()) for line in f.readlines()]
+
+ if numbers:
+ smallest_number = min(numbers)
+ result = smallest_number ** 3
+
+ # Запись результата в result.txt
+ with open(output_file, 'w') as f_out:
+ f_out.write(str(result))
+
+ print(f"Saved the cube of the smallest number {smallest_number} to {output_file}")
+ else:
+ print(f"No numbers found in {input_file}")
+
+if __name__ == "__main__":
+ find_smallest_number_and_cube('/var/result/data.txt', '/var/result/result.txt')