Merge pull request 'klyushenkova_ksenia_lab_3 is ready' (#380) from klyushenkova_ksenia_lab_3 into main
Reviewed-on: #380
This commit is contained in:
commit
7afc676844
2
klyushenkova_ksenia_lab_3/.gitignore
vendored
Normal file
2
klyushenkova_ksenia_lab_3/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/.venv
|
||||
/.idea
|
27
klyushenkova_ksenia_lab_3/README.md
Normal file
27
klyushenkova_ksenia_lab_3/README.md
Normal file
@ -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)
|
11
klyushenkova_ksenia_lab_3/director_service/Dockerfile
Normal file
11
klyushenkova_ksenia_lab_3/director_service/Dockerfile
Normal file
@ -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"]
|
122
klyushenkova_ksenia_lab_3/director_service/director_service.py
Normal file
122
klyushenkova_ksenia_lab_3/director_service/director_service.py
Normal file
@ -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)
|
27
klyushenkova_ksenia_lab_3/docker-compose.yaml
Normal file
27
klyushenkova_ksenia_lab_3/docker-compose.yaml
Normal file
@ -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
|
11
klyushenkova_ksenia_lab_3/movie_service/Dockerfile
Normal file
11
klyushenkova_ksenia_lab_3/movie_service/Dockerfile
Normal file
@ -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"]
|
97
klyushenkova_ksenia_lab_3/movie_service/movie_service.py
Normal file
97
klyushenkova_ksenia_lab_3/movie_service/movie_service.py
Normal file
@ -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)
|
49
klyushenkova_ksenia_lab_3/nginx.conf
Normal file
49
klyushenkova_ksenia_lab_3/nginx.conf
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
3
klyushenkova_ksenia_lab_3/requirements.txt
Normal file
3
klyushenkova_ksenia_lab_3/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
fastapi
|
||||
uvicorn
|
||||
requests
|
Loading…
Reference in New Issue
Block a user