From a4d674af99d33172aed628c00b337a46047c5686 Mon Sep 17 00:00:00 2001 From: Pineapple Date: Fri, 20 Dec 2024 10:20:16 +0400 Subject: [PATCH] klyushenkova_ksenia_lab_3 is ready --- klyushenkova_ksenia_lab_2/.idea/.gitignore | 3 + .../inspectionProfiles/Project_Default.xml | 6 + klyushenkova_ksenia_lab_2/.idea/lab_2.iml | 9 ++ klyushenkova_ksenia_lab_2/.idea/modules.xml | 8 ++ klyushenkova_ksenia_lab_3/.gitignore | 2 + klyushenkova_ksenia_lab_3/README.md | 27 ++++ .../director_service/Dockerfile | 11 ++ .../director_service/director_service.py | 122 ++++++++++++++++++ klyushenkova_ksenia_lab_3/docker-compose.yaml | 27 ++++ .../movie_service/Dockerfile | 11 ++ .../movie_service/movie_service.py | 97 ++++++++++++++ klyushenkova_ksenia_lab_3/nginx.conf | 49 +++++++ klyushenkova_ksenia_lab_3/requirements.txt | 3 + 13 files changed, 375 insertions(+) create mode 100644 klyushenkova_ksenia_lab_2/.idea/.gitignore create mode 100644 klyushenkova_ksenia_lab_2/.idea/inspectionProfiles/Project_Default.xml create mode 100644 klyushenkova_ksenia_lab_2/.idea/lab_2.iml create mode 100644 klyushenkova_ksenia_lab_2/.idea/modules.xml create mode 100644 klyushenkova_ksenia_lab_3/.gitignore create mode 100644 klyushenkova_ksenia_lab_3/README.md create mode 100644 klyushenkova_ksenia_lab_3/director_service/Dockerfile create mode 100644 klyushenkova_ksenia_lab_3/director_service/director_service.py create mode 100644 klyushenkova_ksenia_lab_3/docker-compose.yaml create mode 100644 klyushenkova_ksenia_lab_3/movie_service/Dockerfile create mode 100644 klyushenkova_ksenia_lab_3/movie_service/movie_service.py create mode 100644 klyushenkova_ksenia_lab_3/nginx.conf create mode 100644 klyushenkova_ksenia_lab_3/requirements.txt diff --git a/klyushenkova_ksenia_lab_2/.idea/.gitignore b/klyushenkova_ksenia_lab_2/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/klyushenkova_ksenia_lab_2/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/klyushenkova_ksenia_lab_2/.idea/inspectionProfiles/Project_Default.xml b/klyushenkova_ksenia_lab_2/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..ac21435 --- /dev/null +++ b/klyushenkova_ksenia_lab_2/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_2/.idea/lab_2.iml b/klyushenkova_ksenia_lab_2/.idea/lab_2.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/klyushenkova_ksenia_lab_2/.idea/lab_2.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_2/.idea/modules.xml b/klyushenkova_ksenia_lab_2/.idea/modules.xml new file mode 100644 index 0000000..5d83d56 --- /dev/null +++ b/klyushenkova_ksenia_lab_2/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/.gitignore b/klyushenkova_ksenia_lab_3/.gitignore new file mode 100644 index 0000000..eb53ea0 --- /dev/null +++ b/klyushenkova_ksenia_lab_3/.gitignore @@ -0,0 +1,2 @@ +/.venv +/.idea \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/README.md b/klyushenkova_ksenia_lab_3/README.md new file mode 100644 index 0000000..804f9be --- /dev/null +++ b/klyushenkova_ksenia_lab_3/README.md @@ -0,0 +1,27 @@ +# Клюшенкова Ксения ПИбд-42 +# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами + +### Язык разработки приложений: Python + +## Выбранные сущности: + +- Режиссёр. Содержит Uuid, Name, Surname +- Фильм. Содержит Uuid, Title, Year, Director_id + + +## Описание: +Для каждой сущности были реализованы стандартные CRUD-операции: получение всех записей, получение конкретной записи, +создание записи, изменение записи, удаление записи. + +Дополнительно для сущности Режиссёр: +1. Получение режиссёра со списком фильмов(с полной информацией). При этом сервис взаимодействует с другим сервисом, +чтобы получить список фильмов по идентификатору автора. +2. Операция проверка наличия режиссёра по идентификатору (для сервиса книг). + +Дополнительно для сущности Фильм прописаны: +1. Получение списка записей с полной информацией о режиссёре (не только идентификатор). +2. Получение списка записей по идентификатору режиссёра (для сервиса режиссёров). +3. Получение конкретной записи с полной информацией о режиссёре. + + +## [Видео](https://disk.yandex.ru/i/TnsxOrHV9SuFLw) \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/director_service/Dockerfile b/klyushenkova_ksenia_lab_3/director_service/Dockerfile new file mode 100644 index 0000000..0b48feb --- /dev/null +++ b/klyushenkova_ksenia_lab_3/director_service/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +COPY director_service/director_service.py . + +CMD ["python", "director_service.py"] \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/director_service/director_service.py b/klyushenkova_ksenia_lab_3/director_service/director_service.py new file mode 100644 index 0000000..93e250d --- /dev/null +++ b/klyushenkova_ksenia_lab_3/director_service/director_service.py @@ -0,0 +1,122 @@ +from fastapi import FastAPI, HTTPException +from fastapi.responses import JSONResponse +from pydantic import BaseModel +from uuid import UUID, uuid4 +import requests +from typing import List, Optional +import uvicorn + + +app = FastAPI(root_path="/director_service", title="Director API", description="API для управления режиссерами", version="1.0.0") + + +class Director: + def __init__(self, name: str, surname: str, uuid_: Optional[UUID] = None): + self.uuid_ = uuid_ if uuid_ else uuid4() + self.name: str = name + self.surname: str = surname + + def to_dict(self): + return { + "uuid": str(self.uuid_), + "name": self.name, + "surname": self.surname + } + + def to_dict_with_movies(self, movies: list): + return { + "uuid": str(self.uuid_), + "name": self.name, + "surname": self.surname, + "movies": movies + } + + +class CreateDirectorRequest(BaseModel): + name: str + surname: str + + +class UpdateDirectorRequest(BaseModel): + name: Optional[str] + surname: Optional[str] + + +directors: List[Director] = [ + Director(name="Christopher", surname="Nolan", uuid_=UUID("997aa4c5-ebb2-4794-ba81-e742f9f1fa30")), + Director(name="Quentin", surname="Tarantino", uuid_=UUID("694827e4-0f93-45a5-8f75-bad7ef2d21fe")), + Director(name="Greta", surname="Gerwig", uuid_=UUID("eb815350-c7b9-4446-8434-4c0640c21995")) +] + +movies_url = "http://movie_service:8081/" + + +@app.get("/", response_model=List[dict]) +def get_all(): + """Получение списка всех режиссёров.""" + return [director.to_dict() for director in directors] + + +@app.get("/{uuid_}", response_model=dict) +def get_one(uuid_: UUID): + """Получение режиссёра по идентификатору.""" + for director in directors: + if director.uuid_ == uuid_: + return director.to_dict() + raise HTTPException(status_code=404, detail="Режиссёр с таким uuid не был найден") + + +@app.get("/with-movies/{uuid_}", response_model=dict) +def get_one_with_movies(uuid_: UUID): + """Получение режиссёра со списком его фильмов.""" + for director in directors: + if director.uuid_ == uuid_: + response = requests.get(f"{movies_url}by-director/{uuid_}") + if response.status_code != 200: + raise HTTPException(status_code=404, detail="Не удалось получить фильмы режиссёра") + return director.to_dict_with_movies(response.json()) + raise HTTPException(status_code=404, detail="Режиссёр с таким uuid не был найден") + + +@app.get("/check/{uuid_}", response_class=JSONResponse) +def check_exist(uuid_: UUID): + """Проверка наличия режиссёра по идентификатору (для movie_service).""" + for director in directors: + if director.uuid_ == uuid_: + return JSONResponse(content="", status_code=200) + return JSONResponse(content="", status_code=404) + + +@app.post("/", response_model=dict) +def create(director: CreateDirectorRequest): + """Создание нового режиссёра.""" + new_director = Director(name=director.name, surname=director.surname) + directors.append(new_director) + return new_director.to_dict() + + +@app.put("/{uuid_}", response_model=dict) +def update_by_id(uuid_: UUID, director_update: UpdateDirectorRequest): + """Изменение данных режиссёра по идентификатору.""" + for director in directors: + if director.uuid_ == uuid_: + if director_update.name is not None: + director.name = director_update.name + if director_update.surname is not None: + director.surname = director_update.surname + return director.to_dict() + raise HTTPException(status_code=404, detail="Режиссёр с таким uuid не был найден") + + +@app.delete("/{uuid_}", response_class=JSONResponse) +def delete(uuid_: UUID): + """Удаление режиссёра по идентификатору.""" + for director in directors: + if director.uuid_ == uuid_: + directors.remove(director) + return JSONResponse(content="Режиссёр успешно удалён", status_code=200) + raise HTTPException(status_code=404, detail="Режиссёр с таким uuid не был найден") + + +if __name__ == "__main__": + uvicorn.run("director_service:app", host="0.0.0.0", port=8080, reload=True) \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/docker-compose.yaml b/klyushenkova_ksenia_lab_3/docker-compose.yaml new file mode 100644 index 0000000..5d91b26 --- /dev/null +++ b/klyushenkova_ksenia_lab_3/docker-compose.yaml @@ -0,0 +1,27 @@ +services: + + director_service: + container_name: director_service + build: + context: . + dockerfile: ./director_service/Dockerfile + expose: + - 8080 + + movie_service: + container_name: movie_service + build: + context: . + dockerfile: ./movie_service/Dockerfile + expose: + - 8081 + + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - director_service + - movie_service \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/movie_service/Dockerfile b/klyushenkova_ksenia_lab_3/movie_service/Dockerfile new file mode 100644 index 0000000..73ba7ef --- /dev/null +++ b/klyushenkova_ksenia_lab_3/movie_service/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +COPY movie_service/movie_service.py . + +CMD ["python", "movie_service.py"] \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/movie_service/movie_service.py b/klyushenkova_ksenia_lab_3/movie_service/movie_service.py new file mode 100644 index 0000000..b151899 --- /dev/null +++ b/klyushenkova_ksenia_lab_3/movie_service/movie_service.py @@ -0,0 +1,97 @@ +from fastapi import FastAPI, HTTPException, Request +from pydantic import BaseModel +from typing import List, Optional +from uuid import UUID, uuid4 +import requests +import uvicorn + + +app = FastAPI(root_path="/movie_service", title="Movie Service", description="Service to manage movies and their directors", version="1.0.0") + +directors_url = "http://director_service:8080/" + +class Movie(BaseModel): + uuid: UUID + title: str + year: int + director_id: UUID + +class MovieCreate(BaseModel): + title: str + year: int + director_id: UUID + +movies: List[Movie] = [ + Movie(uuid=UUID("89fa1e7a-7e88-445e-a4d8-6d4497ea8f19"), title="Inception", year=2010, director_id=UUID("997aa4c5-ebb2-4794-ba81-e742f9f1fa30")), + Movie(uuid=UUID("0351ee11-f11b-4d83-b2c8-1075b0c357dc"), title="Pulp Fiction", year=1994, director_id=UUID("694827e4-0f93-45a5-8f75-bad7ef2d21fe")), + Movie(uuid=UUID("dfc17619-7690-47aa-ae8e-6a5068f8ddec"), title="Lady Bird", year=2017, director_id=UUID("eb815350-c7b9-4446-8434-4c0640c21995")), + Movie(uuid=UUID("3fad0e6b-cefc-40dd-99c0-adc5ec0290d2"), title="Django Unchained", year=2012, director_id=UUID("694827e4-0f93-45a5-8f75-bad7ef2d21fe")), + Movie(uuid=UUID("cf0cf26e-c1c6-43ea-bf59-1f8edb180e41"), title="Interstellar", year=2014, director_id=UUID("997aa4c5-ebb2-4794-ba81-e742f9f1fa30")), + Movie(uuid=UUID("2ac3c734-21ad-4450-884d-7de1f5452b9d"), title="The Grand Budapest Hotel", year=2014, director_id=UUID("eb815350-c7b9-4446-8434-4c0640c21995")) +] + +@app.get("/", response_model=List[Movie], summary="Get all movies", description="Retrieve a list of all movies.") +def get_all_movies(): + return movies + +@app.get("/full", summary="Get all movies with director info", description="Retrieve all movies with additional director information.") +def get_all_movies_with_directors(): + directors = requests.get(directors_url).json() + response = [] + for movie in movies: + director_info = next((d for d in directors if d["uuid"] == str(movie.director_id)), None) + if director_info: + response.append({**movie.dict(), "director_info": director_info}) + return response + +@app.get("/by-director/{director_uuid}", response_model=List[Movie], summary="Get movies by director", description="Retrieve all movies for a given director.") +def get_movies_by_director(director_uuid: UUID): + return [movie for movie in movies if movie.director_id == director_uuid] + +@app.get("/{movie_uuid}", response_model=Movie, summary="Get a movie by ID", description="Retrieve a single movie by its ID.") +def get_movie_by_id(movie_uuid: UUID): + movie = next((movie for movie in movies if movie.uuid == movie_uuid), None) + if not movie: + raise HTTPException(status_code=404, detail="Movie not found") + return movie + +@app.get("/full/{movie_uuid}", summary="Get movie with director info", description="Retrieve a movie with additional director information.") +def get_movie_with_director_info(movie_uuid: UUID): + movie = next((movie for movie in movies if movie.uuid == movie_uuid), None) + if not movie: + raise HTTPException(status_code=404, detail="Movie not found") + + director_info = requests.get(f"{directors_url}{movie.director_id}").json() + return {**movie.dict(), "director_info": director_info} + +@app.post("/", response_model=Movie, summary="Create a new movie", description="Add a new movie to the database.") +def create_movie(movie: MovieCreate): + # Check if director exists + response = requests.get(f"{directors_url}check/{movie.director_id}") + if response.status_code == 404: + raise HTTPException(status_code=404, detail="Director not found") + + new_movie = Movie(uuid=uuid4(), **movie.dict()) + movies.append(new_movie) + return new_movie + +@app.put("/{movie_uuid}", response_model=Movie, summary="Update a movie", description="Update an existing movie by its ID.") +def update_movie(movie_uuid: UUID, movie_update: MovieCreate): + movie = next((movie for movie in movies if movie.uuid == movie_uuid), None) + if not movie: + raise HTTPException(status_code=404, detail="Movie not found") + + movie.title = movie_update.title + movie.year = movie_update.year + movie.director_id = movie_update.director_id + return movie + +@app.delete("/{movie_uuid}", summary="Delete a movie", description="Remove a movie by its ID.") +def delete_movie(movie_uuid: UUID): + global movies + movies = [movie for movie in movies if movie.uuid != movie_uuid] + return {"detail": "Movie deleted successfully"} + + +if __name__ == "__main__": + uvicorn.run("movie_service:app", host="0.0.0.0", port=8081, reload=True) \ No newline at end of file diff --git a/klyushenkova_ksenia_lab_3/nginx.conf b/klyushenkova_ksenia_lab_3/nginx.conf new file mode 100644 index 0000000..f1722fe --- /dev/null +++ b/klyushenkova_ksenia_lab_3/nginx.conf @@ -0,0 +1,49 @@ +events { worker_connections 1024; } + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /director_service/ { + proxy_pass http://director_service:8080/; + 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 /movie_service/ { + proxy_pass http://movie_service:8081/; + 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 /director_service/docs { + proxy_pass http://director_service:8080/docs; + } + + location /movie_service/docs { + proxy_pass http://movie_service:8081/docs; + } + + location /director_service/openapi.json { + proxy_pass http://director_service:8080/openapi.json; + 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 /movie_service/openapi.json { + proxy_pass http://movie_service:8081/openapi.json; + 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/klyushenkova_ksenia_lab_3/requirements.txt b/klyushenkova_ksenia_lab_3/requirements.txt new file mode 100644 index 0000000..7bf9524 --- /dev/null +++ b/klyushenkova_ksenia_lab_3/requirements.txt @@ -0,0 +1,3 @@ +fastapi +uvicorn +requests \ No newline at end of file