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