diff --git a/ismailov_rovshan_lab_3/README.md b/ismailov_rovshan_lab_3/README.md new file mode 100644 index 0000000..ada00c3 --- /dev/null +++ b/ismailov_rovshan_lab_3/README.md @@ -0,0 +1,42 @@ +# Лабораторная работа №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. + +### Видео с демонстрацией работы: +https://cloud.mail.ru/public/ktx4/3dHDsNr7T \ No newline at end of file diff --git a/ismailov_rovshan_lab_3/docker-compose.yaml b/ismailov_rovshan_lab_3/docker-compose.yaml new file mode 100644 index 0000000..6724b9e --- /dev/null +++ b/ismailov_rovshan_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/ismailov_rovshan_lab_3/faculty_service/Dockerfile b/ismailov_rovshan_lab_3/faculty_service/Dockerfile new file mode 100644 index 0000000..677590a --- /dev/null +++ b/ismailov_rovshan_lab_3/faculty_service/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.12 + +WORKDIR /app + +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/ismailov_rovshan_lab_3/faculty_service/faculty_service.py b/ismailov_rovshan_lab_3/faculty_service/faculty_service.py new file mode 100644 index 0000000..9d26675 --- /dev/null +++ b/ismailov_rovshan_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/ismailov_rovshan_lab_3/gitignore b/ismailov_rovshan_lab_3/gitignore new file mode 100644 index 0000000..eb53ea0 --- /dev/null +++ b/ismailov_rovshan_lab_3/gitignore @@ -0,0 +1,2 @@ +/.venv +/.idea \ No newline at end of file diff --git a/ismailov_rovshan_lab_3/nginx.conf b/ismailov_rovshan_lab_3/nginx.conf new file mode 100644 index 0000000..75c0926 --- /dev/null +++ b/ismailov_rovshan_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/ismailov_rovshan_lab_3/requirements.txt b/ismailov_rovshan_lab_3/requirements.txt new file mode 100644 index 0000000..41917d8 Binary files /dev/null and b/ismailov_rovshan_lab_3/requirements.txt differ diff --git a/ismailov_rovshan_lab_3/speciality_service/Dockerfile b/ismailov_rovshan_lab_3/speciality_service/Dockerfile new file mode 100644 index 0000000..44b959d --- /dev/null +++ b/ismailov_rovshan_lab_3/speciality_service/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.12 + +WORKDIR /app + +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/ismailov_rovshan_lab_3/speciality_service/speciality_service.py b/ismailov_rovshan_lab_3/speciality_service/speciality_service.py new file mode 100644 index 0000000..19c1715 --- /dev/null +++ b/ismailov_rovshan_lab_3/speciality_service/speciality_service.py @@ -0,0 +1,154 @@ +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='3e7fc35d-f3bb-4657-bdcf-cf19191f3bc0'), + 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 + + +@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 + + +@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 + + +@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 + + +@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)