diff --git a/turner_ilya_lab_3/.gitignore b/turner_ilya_lab_3/.gitignore new file mode 100644 index 0000000..eb53ea0 --- /dev/null +++ b/turner_ilya_lab_3/.gitignore @@ -0,0 +1,2 @@ +/.venv +/.idea \ No newline at end of file diff --git a/turner_ilya_lab_3/README.md b/turner_ilya_lab_3/README.md new file mode 100644 index 0000000..9ddf100 --- /dev/null +++ b/turner_ilya_lab_3/README.md @@ -0,0 +1,36 @@ +# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами +## ПИбд-42 || Тюрнер Илья + +### Описание: +В рамках данной лабораторной работы были реализованы две сущности со связью "один-ко-многим", также были разработаны все CRUD-операции: получение списка записей, получение одной записи, создание, редактирование и удаление записи. + +У сущности факультета также реализована возможность получения записи со списком всх специальностей. В этом случае сервис взаимодействует с другим сервисом. Еще реализована проверка наличия факультета по id. + +У сущности специальности также реализованы возможности: получение списка записей с информацией о факультете, получение списка записей по id факультета и получение одной записи с информацией о факультете. + +### Цель лабораторной работы +изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API + +### Выбранные сущности: +- Факультет. Поля: Uuid, Name и Description +- Специальность. Поля: Uuid, Name, CountPlaces и Faculty_Id + +### Инструкция для работы +1. Клонирование репозитория: +``` +git clone <ссылка-на-репозиторий> +cd <папка репозитория> +cd <папка лабораторной работы> +``` + +2. Запуск контейнеров: +``` +docker compose up --build +``` + +3. Результаты: +В результате можно будет применят CRUD операции к сущностям через http запросы. Для демонстрации был выбран знакомый инструмент Postman. + +### Видео с демонстрацией работы: +Размещено на платформе VK видео +https://vk.com/video/@tyurner02?z=video303312410_456239079%2Fpl_303312410_-2 \ No newline at end of file diff --git a/turner_ilya_lab_3/docker-compose.yaml b/turner_ilya_lab_3/docker-compose.yaml new file mode 100644 index 0000000..6724b9e --- /dev/null +++ b/turner_ilya_lab_3/docker-compose.yaml @@ -0,0 +1,27 @@ +services: + + faculty_service: + container_name: faculty_service + build: + context: . + dockerfile: ./faculty_service/Dockerfile + expose: + - 10001 + + speciality_service: + container_name: speciality_service + build: + context: . + dockerfile: ./speciality_service/Dockerfile + expose: + - 10002 + + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - faculty_service + - speciality_service \ No newline at end of file diff --git a/turner_ilya_lab_3/faculty_service/Dockerfile b/turner_ilya_lab_3/faculty_service/Dockerfile new file mode 100644 index 0000000..b97a53a --- /dev/null +++ b/turner_ilya_lab_3/faculty_service/Dockerfile @@ -0,0 +1,17 @@ +#Берем базовый образ python +FROM python:3.12 + +#Устанавливаем рабочую директорию +WORKDIR /app + +#Копируем requirements.txt в контейнер +COPY requirements.txt . + +#Подтягиваем зависимости +RUN pip install --no-cache-dir -r requirements.txt + +#Копируем файлы программы в контейнер +COPY faculty_service/faculty_service.py . + +#Задаем команду для выполнения программы +CMD ["python", "faculty_service.py"] \ No newline at end of file diff --git a/turner_ilya_lab_3/faculty_service/faculty_service.py b/turner_ilya_lab_3/faculty_service/faculty_service.py new file mode 100644 index 0000000..ff89a64 --- /dev/null +++ b/turner_ilya_lab_3/faculty_service/faculty_service.py @@ -0,0 +1,129 @@ +from flask import Flask, jsonify, request +from uuid import uuid4 +import uuid +import requests + + +class Faculty: + def __init__(self, name, description, uuid_: uuid): + if uuid_ is None: + self.uuid_: uuid = uuid4() + else: + self.uuid_: uuid = uuid.UUID(uuid_) + self.name: str = name + self.description: str = description + + def to_dict(self): + return { + "uuid": self.uuid_, + "name": self.name, + "description": self.description + } + + def to_dict_with_specialities(self, specialities: list): + return { + "uuid": self.uuid_, + "name": self.name, + "description": self.description, + "specialities": specialities + } + + +app = Flask(__name__) + +faculties: list[Faculty] = [ + Faculty(name='ФИСТ', description='Факультет информационных систем и технологий', uuid_='3c87a9a1-1a3e-402d-8a53-6bbba478e644'), + Faculty(name='ГФ', description='Гуманитарный факультет', uuid_='a9e36307-02dc-4ee0-a771-e6d39cae9a75'), + Faculty(name='РТФ', description='Радиотехнический факультет', uuid_='3e7fc35d-f3bb-4657-bdcf-cf19191f3bc0'), + Faculty(name='ИЭФ', description='Инженерно-экономический факультет', uuid_='bb8571c4-f4a9-4d6e-b1f3-6efe4f54166e') +] + +specialities_url = 'http://speciality_service:10002/' + + +def list_jsonify(): + return jsonify([faculty.to_dict() for faculty in faculties]) + + +# получение списка всех факультетов +@app.route('/', methods=['GET']) +def get_all(): + return list_jsonify(), 200 + + +# получение факультета по id +@app.route('/', methods=['GET']) +def get_one(uuid_): + for faculty in faculties: + if faculty.uuid_ == uuid_: + return faculty.to_dict(), 200 + + return 'Факультет с указанным uuid не обнаружен', 404 + + +# получение факультета со списком специальностей +@app.route('/with-specialities/', methods=['GET']) +def get_one_with_specialities(uuid_): + for faculty in faculties: + if faculty.uuid_ == uuid_: + response = requests.get(specialities_url + f'by-faculty/{uuid_}') + print(response.json()) + return faculty.to_dict_with_specialities(response.json()), 200 + + return 'Факультет с указанным uuid не обнаружен', 404 + + +# проверка наличия факультета по id (для специальности) +@app.route('/check/', methods=['GET']) +def check_exist(uuid_): + for faculty in faculties: + if faculty.uuid_ == uuid_: + return '', 200 + return '', 404 + + +# создание факультета +@app.route('/', methods=['POST']) +def create(): + data = request.json + name = data.get('name', None) + description = data.get('description', None) + if name is None or description is None: + return 'Не хватает данных для создания нового факультета', 404 + + new_faculty = Faculty(name, description, None) + faculties.append(new_faculty) + return get_one(new_faculty.uuid_) + + +# изменение факультета по id +@app.route('/', methods=['PUT']) +def update_by_id(uuid_): + data = request.json + new_name = data.get('name', None) + new_description = data.get('description', None) + + for faculty in faculties: + if faculty.uuid_ == uuid_: + if new_name is not None: + faculty.name = new_name + if new_description is not None: + faculty.description = new_description + return get_one(faculty.uuid_) + + return 'Факультет с указанным uuid не обнаружен', 404 + + +# удаление факультета по id +@app.route('/', methods=['DELETE']) +def delete(uuid_): + for faculty in faculties: + if faculty.uuid_ == uuid_: + faculties.remove(faculty) + return 'Факультет был удален', 200 + + return 'Факультет с указанным uuid не обнаружен', 404 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=10001, debug=True) diff --git a/turner_ilya_lab_3/nginx.conf b/turner_ilya_lab_3/nginx.conf new file mode 100644 index 0000000..75c0926 --- /dev/null +++ b/turner_ilya_lab_3/nginx.conf @@ -0,0 +1,26 @@ + +events { worker_connections 1024; } + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /faculty_service/ { + proxy_pass http://faculty_service:10001/; + 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 /speciality_service/ { + proxy_pass http://speciality_service:10002/; + 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/turner_ilya_lab_3/requirements.txt b/turner_ilya_lab_3/requirements.txt new file mode 100644 index 0000000..41917d8 Binary files /dev/null and b/turner_ilya_lab_3/requirements.txt differ diff --git a/turner_ilya_lab_3/speciality_service/Dockerfile b/turner_ilya_lab_3/speciality_service/Dockerfile new file mode 100644 index 0000000..a84ff78 --- /dev/null +++ b/turner_ilya_lab_3/speciality_service/Dockerfile @@ -0,0 +1,17 @@ +#Берем базовый образ python +FROM python:3.12 + +#Устанавливаем рабочую директорию +WORKDIR /app + +#Копируем requirements.txt в контейнер +COPY requirements.txt . + +#Подтягиваем зависимости +RUN pip install --no-cache-dir -r requirements.txt + +#Копируем файлы программы в контейнер +COPY speciality_service/speciality_service.py . + +#Задаем команду для выполнения программы +CMD ["python", "speciality_service.py"] \ No newline at end of file diff --git a/turner_ilya_lab_3/speciality_service/speciality_service.py b/turner_ilya_lab_3/speciality_service/speciality_service.py new file mode 100644 index 0000000..2b72e80 --- /dev/null +++ b/turner_ilya_lab_3/speciality_service/speciality_service.py @@ -0,0 +1,161 @@ +from flask import Flask, jsonify, request +import requests +from uuid import uuid4 +import uuid + + +class Speciality: + def __init__(self, name, countPlaces, uuid_: uuid, faculty_id: uuid): + if uuid_ is None: + self.uuid_: uuid = uuid4() + else: + self.uuid_: uuid = uuid.UUID(uuid_) + self.name: str = name + self.countPlaces: int = countPlaces + self.faculty_id: uuid = uuid.UUID(faculty_id) + + def to_dict(self): + return { + 'name': self.name, + 'countPlaces': self.countPlaces, + 'faculty_id': self.faculty_id, + 'uuid': self.uuid_ + } + + def to_dict_for_faculties(self): + return { + 'name': self.name, + 'countPlaces': self.countPlaces, + 'uuid': self.uuid_ + } + + def to_dict_with_info(self, faculty: dict): + return { + 'name': self.name, + 'countPlaces': self.countPlaces, + 'faculty_id': self.faculty_id, + 'faculty_info': faculty, + 'uuid': self.uuid_ + } + + +app = Flask(__name__) + +specialities: list[Speciality] = [ + Speciality(name='Программная инженерия', countPlaces=39, uuid_='a6233a8e-7b54-4927-954a-cafbdbf142fe', + faculty_id='3c87a9a1-1a3e-402d-8a53-6bbba478e644'), + Speciality(name='Прикладная информатика', countPlaces=53, uuid_='c1863dee-7b27-4252-9327-9226dd10eebe', + faculty_id='3c87a9a1-1a3e-402d-8a53-6bbba478e644'), + Speciality(name='Лингвистика', countPlaces=23, uuid_='da1f1594-39c7-4407-8c54-883e9a8565ea', + faculty_id='a9e36307-02dc-4ee0-a771-e6d39cae9a75'), + Speciality(name='Радиотехника', countPlaces=46, uuid_='a7156dbf-b102-40bd-9eb9-d53c110cfe0d', + faculty_id='603e552f-2b53-4d60-b13b-700efa4dfbb9'), + Speciality(name='Управление качеством', countPlaces=17, uuid_='1a24d9b7-72d2-4918-8a86-b80608c855f6', + faculty_id='3e7fc35d-f3bb-4657-bdcf-cf19191f3bc0') +] + +facuties_url = 'http://faculty_service:10001/' + + +def list_jsonify(): + return jsonify([speciality.to_dict() for speciality in specialities]) + + +# получить список специальностей +@app.route('/', methods=['GET']) +def get_all(): + return list_jsonify(), 200 + + +# получить список специальностей с информацией о факультете +@app.route('/full', methods=['GET']) +def get_all_full(): + faculties: list[dict] = requests.get(facuties_url).json() + response = [] + for speciality in specialities: + for faculty in faculties: + if speciality.faculty_id == uuid.UUID(faculty.get('uuid')): + response.append(speciality.to_dict_with_info(faculty)) + + return response, 200 + + +# получение списка специальностей по id факультета +@app.route('/by-faculty/', methods=['GET']) +def get_by_faculty_id(faculty_uuid): + return [speciality.to_dict_for_faculties() for speciality in specialities if speciality.faculty_id == faculty_uuid], 200 + + +# получение специальности по id +@app.route('/', methods=['GET']) +def get_one(uuid_): + for speciality in specialities: + if speciality.uuid_ == uuid_: + return speciality.to_dict(), 200 + + return 'Специальность с указанным uuid не обнаружена', 404 + + +# получение специальности по id с информацией о факультете +@app.route('/full/', methods=['GET']) +def get_one_full(uuid_): + for speciality in specialities: + if speciality.uuid_ == uuid_: + response = requests.get(facuties_url + str(speciality.faculty_id)) + return speciality.to_dict_with_info(response.json()), 200 + + return 'Специальность с указанным uuid не обнаружена', 404 + + +# создание новой специальности +@app.route('/', methods=['POST']) +def create(): + data = request.json + name = data.get('name', None) + countPlaces = data.get('countPlaces', None) + faculty_id = data.get('faculty_id', None) + checking = requests.get(facuties_url + f'/check/{faculty_id}') + print(checking) + if checking.status_code == 200: + new_speciality = Speciality(name, countPlaces, None, faculty_id) + specialities.append(new_speciality) + return get_one(new_speciality.uuid_) + if checking.status_code == 404: + return 'Факультета с таким uuid не существует', 404 + + return 'Неизвестная ошибка', 500 + + +# изменение специальности по id +@app.route('/', methods=['PUT']) +def update_by_id(uuid_): + data = request.json + new_name = data.get('name', None) + new_countPlaces = data.get('countPlaces', None) + + for speciality in specialities: + print(speciality.uuid_) + + if speciality.uuid_ == uuid_: + if new_name is not None: + speciality.name = new_name + if new_countPlaces is not None: + speciality.countPlaces = new_countPlaces + return get_one(speciality.uuid_) + + return 'Специальность с указанным uuid не обнаружена', 404 + + +# удаление специальности по id +@app.route('/', methods=['DELETE']) +def delete(uuid_): + for speciality in specialities: + if speciality.uuid_ == uuid_: + specialities.remove(speciality) + return 'Специальность была удалена', 200 + + return 'Специальность с указанным uuid не обнаружена', 404 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=10002, debug=True)