diff --git a/tsukanova_irina_lab_3/.gitignore b/tsukanova_irina_lab_3/.gitignore new file mode 100644 index 0000000..eb53ea0 --- /dev/null +++ b/tsukanova_irina_lab_3/.gitignore @@ -0,0 +1,2 @@ +/.venv +/.idea \ No newline at end of file diff --git a/tsukanova_irina_lab_3/README.md b/tsukanova_irina_lab_3/README.md new file mode 100644 index 0000000..4ef85d9 --- /dev/null +++ b/tsukanova_irina_lab_3/README.md @@ -0,0 +1,27 @@ +# Цуканова Ирина ПИбд-32 +# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами + +### Язык разработки приложений: Python + +## Выбранные сущности: + +- Автор. Содержит Uuid, Name, Surname +- Книга. Содержит Uuid, Title, Year, Author_id + + +## Описание: +Для каждой сущности были реализованы стандартные CRUD-операции: получение всех записей, получение конкретной записи, +создание записи, изменение записи, удаление записи. + +Дополнительно для сущности Автор прописаны: +1. Получение автора со списком его книг(с полной информацией). При этом сервис взаимодействует с другим сервисом, +чтобы получить список книг по идентификатору автора. +2. Операция проверка наличия автора по идентификатору. Это нужно для сервиса книг. + +Дополнительно для сущности Книга прописаны: +1. Получение списка записей с полной информацией об авторе (не только идентификатор). +2. Получение списка записей по идентификатору автора. Это нужно для сервиса авторов. +3. Получение конкретной записи с полной информацией об авторе. + + +## [Видео](https://drive.google.com/file/d/10qXA-jS9QIX5DnYjSAHlEhLx0OyKEk0W/view?usp=sharing) \ No newline at end of file diff --git a/tsukanova_irina_lab_3/author_service/Dockerfile b/tsukanova_irina_lab_3/author_service/Dockerfile new file mode 100644 index 0000000..cd6721c --- /dev/null +++ b/tsukanova_irina_lab_3/author_service/Dockerfile @@ -0,0 +1,17 @@ +# Использую базовый образ Python +FROM python:3.12-slim + +# Устанавливаю рабочую директорию внутри контейнера +WORKDIR /app + +# Копирую файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаю зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копирую все файлы в контейнер +COPY author_service/author_service.py . + +# Команда для запуска Python-скрипта +CMD ["python", "author_service.py"] \ No newline at end of file diff --git a/tsukanova_irina_lab_3/author_service/author_service.py b/tsukanova_irina_lab_3/author_service/author_service.py new file mode 100644 index 0000000..357eeb0 --- /dev/null +++ b/tsukanova_irina_lab_3/author_service/author_service.py @@ -0,0 +1,128 @@ +from flask import Flask, jsonify, request +from uuid import uuid4 +import uuid +import requests + + +class Author: + def __init__(self, name, surname, uuid_: uuid): + if uuid_ is None: + self.uuid_: uuid = uuid4() + else: + self.uuid_: uuid = uuid.UUID(uuid_) + self.name: str = name + self.surname: str = surname + + def to_dict(self): + return { + "uuid": self.uuid_, + "name": self.name, + "surname": self.surname + } + + def to_dict_with_books(self, books: list): + return { + "uuid": self.uuid_, + "name": self.name, + "surname": self.surname, + "books": books + } + + +app = Flask(__name__) + +authors: list[Author] = [ + Author(name='Leon', surname='Kane', uuid_='997aa4c5-ebb2-4794-ba81-e742f9f1fa30'), + Author(name='James', surname='Rasal', uuid_='694827e4-0f93-45a5-8f75-bad7ef2d21fe'), + Author(name='Tess', surname='Root', uuid_='eb815350-c7b9-4446-8434-4c0640c21995') +] + +books_url = 'http://book_service:20002/' + + +def list_jsonify(): + return jsonify([author.to_dict() for author in authors]) + + +# получение списка всех авторов +@app.route('/', methods=['GET']) +def get_all(): + return list_jsonify(), 200 + + +# получение автора по идентификатору +@app.route('/', methods=['GET']) +def get_one(uuid_): + for author in authors: + if author.uuid_ == uuid_: + return author.to_dict(), 200 + + return 'Автор с таким uuid не был найден', 404 + + +# получение автора со списком его книг +@app.route('/with-books/', methods=['GET']) +def get_one_with_books(uuid_): + for author in authors: + if author.uuid_ == uuid_: + response = requests.get(books_url + f'by-author/{uuid_}') + print(response.json()) + return author.to_dict_with_books(response.json()), 200 + + return 'Автор с таким uuid не был найден', 404 + + +# проверка наличия автора по его идентификатору (для book_service) +@app.route('/check/', methods=['GET']) +def check_exist(uuid_): + for author in authors: + if author.uuid_ == uuid_: + return '', 200 + return '', 404 + + +# создание автора +@app.route('/', methods=['POST']) +def create(): + data = request.json + name = data.get('name', None) + surname = data.get('surname', None) + if name is None or surname is None: + return 'Недостаточно полей для создания нового автора', 404 + + new_author = Author(name, surname, None) + authors.append(new_author) + return get_one(new_author.uuid_) + + +# изменение автора по идентификатору +@app.route('/', methods=['PUT']) +def update_by_id(uuid_): + data = request.json + new_name = data.get('name', None) + new_surname = data.get('surname', None) + + for author in authors: + if author.uuid_ == uuid_: + if new_name is not None: + author.name = new_name + if new_surname is not None: + author.surname = new_surname + return get_one(author.uuid_) + + return 'Автор с таким uuid не был найден', 404 + + +# удаление автора по идентификатору +@app.route('/', methods=['DELETE']) +def delete(uuid_): + for author in authors: + if author.uuid_ == uuid_: + authors.remove(author) + return 'Автор успешно удален', 200 + + return 'Автор с таким uuid не был найден', 404 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=20001, debug=True) diff --git a/tsukanova_irina_lab_3/book_service/Dockerfile b/tsukanova_irina_lab_3/book_service/Dockerfile new file mode 100644 index 0000000..ded1775 --- /dev/null +++ b/tsukanova_irina_lab_3/book_service/Dockerfile @@ -0,0 +1,17 @@ +# Использую базовый образ Python +FROM python:3.12-slim + +# Устанавливаю рабочую директорию внутри контейнера +WORKDIR /app + +# Копирую файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаю зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копирую все файлы в контейнер +COPY book_service/book_service.py . + +# Команда для запуска Python-скрипта +CMD ["python", "book_service.py"] \ No newline at end of file diff --git a/tsukanova_irina_lab_3/book_service/book_service.py b/tsukanova_irina_lab_3/book_service/book_service.py new file mode 100644 index 0000000..6ce953a --- /dev/null +++ b/tsukanova_irina_lab_3/book_service/book_service.py @@ -0,0 +1,163 @@ +from flask import Flask, jsonify, request +import requests +from uuid import uuid4 +import uuid + + +class Book: + def __init__(self, title, year, uuid_: uuid, author_id: uuid): + if uuid_ is None: + self.uuid_: uuid = uuid4() + else: + self.uuid_: uuid = uuid.UUID(uuid_) + self.title: str = title + self.year: int = year + self.author_id: uuid = uuid.UUID(author_id) + + def to_dict(self): + return { + 'title': self.title, + 'year': self.year, + 'author_id': self.author_id, + 'uuid': self.uuid_ + } + + def to_dict_for_authors(self): + return { + 'title': self.title, + 'year': self.year, + 'uuid': self.uuid_ + } + + def to_dict_with_info(self, author: dict): + return { + 'title': self.title, + 'year': self.year, + 'author_id': self.author_id, + 'author_info': author, + 'uuid': self.uuid_ + } + + +app = Flask(__name__) + +books: list[Book] = [ + Book(title='Garden', year=1977, uuid_='89fa1e7a-7e88-445e-a4d8-6d4497ea8f19', + author_id='997aa4c5-ebb2-4794-ba81-e742f9f1fa30'), + Book(title='New York', year=1989, uuid_='0351ee11-f11b-4d83-b2c8-1075b0c357dc', + author_id='694827e4-0f93-45a5-8f75-bad7ef2d21fe'), + Book(title='The story of flowers', uuid_='dfc17619-7690-47aa-ae8e-6a5068f8ddec', year=1977, + author_id='eb815350-c7b9-4446-8434-4c0640c21995'), + Book(title='Little Rock', year=1990, uuid_='3fad0e6b-cefc-40dd-99c0-adc5ec0290d2', + author_id='694827e4-0f93-45a5-8f75-bad7ef2d21fe'), + Book(title='One Piece', year=1994, uuid_='cf0cf26e-c1c6-43ea-bf59-1f8edb180e41', + author_id='694827e4-0f93-45a5-8f75-bad7ef2d21fe'), + Book(title='Black clover', year=1940, uuid_='2ac3c734-21ad-4450-884d-7de1f5452b9d', + author_id='eb815350-c7b9-4446-8434-4c0640c21995'), +] + +authors_url = 'http://author_service:20001/' + + +def list_jsonify(): + return jsonify([book.to_dict() for book in books]) + + +# получить список книг без подробной инфы об авторе +@app.route('/', methods=['GET']) +def get_all(): + return list_jsonify(), 200 + + +# получение всех книг с информацией об авторе +@app.route('/full', methods=['GET']) +def get_all_full(): + authors: list[dict] = requests.get(authors_url).json() + response = [] + for book in books: + for author in authors: + if book.author_id == uuid.UUID(author.get('uuid')): + response.append(book.to_dict_with_info(author)) + + return response, 200 + + +# получение списка книг по идентификатору автора (для author_service) +@app.route('/by-author/', methods=['GET']) +def get_by_author_id(author_uuid): + return [book.to_dict_for_authors() for book in books if book.author_id == author_uuid], 200 + + +# получение книги по идентификатору +@app.route('/', methods=['GET']) +def get_one(uuid_): + for book in books: + if book.uuid_ == uuid_: + return book.to_dict(), 200 + + return 'Книга с таким uuid не была найдена', 404 + + +# получение книги по идентификатору с инфой об авторе +@app.route('/full/', methods=['GET']) +def get_one_full(uuid_): + for book in books: + if book.uuid_ == uuid_: + response = requests.get(authors_url + str(book.author_id)) + return book.to_dict_with_info(response.json()), 200 + + return 'Книга с таким uuid не была найдена', 404 + + +# создание новой книги +@app.route('/', methods=['POST']) +def create(): + data = request.json + title = data.get('title', None) + year = data.get('year', None) + author_id = data.get('author_id', None) + checking = requests.get(authors_url + f'/check/{author_id}') + print(checking) + if checking.status_code == 200: + new_book = Book(title, year, None, author_id) + books.append(new_book) + return get_one(new_book.uuid_) + if checking.status_code == 404: + return 'Автор с таким uuid не существует', 404 + + return 'Неизвестная ошибка', 500 + + +# изменение книги по идентификатору +@app.route('/', methods=['PUT']) +def update_by_id(uuid_): + data = request.json + new_title = data.get('title', None) + new_year = data.get('year', None) + + for book in books: + print(book.uuid_) + + if book.uuid_ == uuid_: + if new_title is not None: + book.title = new_title + if new_year is not None: + book.year = new_year + return get_one(book.uuid_) + + return 'Книга с таким uuid не была найдена', 404 + + +# удаление книги по идентификатору +@app.route('/', methods=['DELETE']) +def delete(uuid_): + for book in books: + if book.uuid_ == uuid_: + books.remove(book) + return 'Книга успешно удалена', 200 + + return 'Книга с таким uuid не была найдена', 404 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=20002, debug=True) diff --git a/tsukanova_irina_lab_3/docker-compose.yaml b/tsukanova_irina_lab_3/docker-compose.yaml new file mode 100644 index 0000000..bd9f0f6 --- /dev/null +++ b/tsukanova_irina_lab_3/docker-compose.yaml @@ -0,0 +1,27 @@ +services: + + author_service: + container_name: author_service + build: + context: . + dockerfile: ./author_service/Dockerfile + expose: + - 20001 + + book_service: + container_name: book_service + build: + context: . + dockerfile: ./book_service/Dockerfile + expose: + - 20002 + + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - author_service + - book_service \ No newline at end of file diff --git a/tsukanova_irina_lab_3/nginx.conf b/tsukanova_irina_lab_3/nginx.conf new file mode 100644 index 0000000..805d451 --- /dev/null +++ b/tsukanova_irina_lab_3/nginx.conf @@ -0,0 +1,26 @@ + +events { worker_connections 1024; } + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /author_service/ { + proxy_pass http://author_service:20001/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /book_service/ { + proxy_pass http://book_service:20002/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} \ No newline at end of file diff --git a/tsukanova_irina_lab_3/requirements.txt b/tsukanova_irina_lab_3/requirements.txt new file mode 100644 index 0000000..41917d8 Binary files /dev/null and b/tsukanova_irina_lab_3/requirements.txt differ