Skip to content

Commit

Permalink
Merge pull request #8 from wafflestudio/feature/profile
Browse files Browse the repository at this point in the history
Feature/profile
  • Loading branch information
deveroskp authored Jan 10, 2025
2 parents 7f7097a + e2caee3 commit 6840d35
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 6 deletions.
4 changes: 3 additions & 1 deletion watchapedia/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from watchapedia.app.movie.views import movie_router
from watchapedia.app.review.views import review_router
from watchapedia.app.comment.views import comment_router
from watchapedia.app.participant.views import participant_router

api_router = APIRouter()

api_router.include_router(user_router, prefix='/users', tags=['users'])
api_router.include_router(movie_router, prefix='/movies', tags=['movies'])
api_router.include_router(review_router, prefix='/reviews', tags=['reviews'])
api_router.include_router(comment_router, prefix='/comments', tags=['comments'])
api_router.include_router(comment_router, prefix='/comments', tags=['comments'])
api_router.include_router(participant_router, prefix='/participants', tags=['participants'])
10 changes: 10 additions & 0 deletions watchapedia/app/participant/dto/requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Annotated
from pydantic import BaseModel
from pydantic.functional_validators import AfterValidator
from watchapedia.app.movie.dto.requests import validate_url
from watchapedia.app.review.dto.requests import validate_content

class ParticipantProfileUpdateRequest(BaseModel):
name: str | None = None
profile_url: Annotated[str | None, AfterValidator(validate_url)] = None
biography: Annotated[str | None, AfterValidator(validate_content)] = None
32 changes: 32 additions & 0 deletions watchapedia/app/participant/dto/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from pydantic import BaseModel
from watchapedia.app.participant.models import Participant

class MovieDataResponse(BaseModel):
id: int
title: str
year: int
average_rating: float | None
poster_url: str | None
cast : str | None


class ParticipantDataResponse(BaseModel):
role: str
movies: list[MovieDataResponse]

class ParticipantProfileResponse(BaseModel):
id: int
name: str
profile_url: str | None
roles: list[str]
biography: str | None

@staticmethod
def from_entity(participant: Participant, roles: list[str]) -> "ParticipantProfileResponse":
return ParticipantProfileResponse(
id=participant.id,
name=participant.name,
profile_url=participant.profile_url,
roles=roles,
biography=participant.biography
)
6 changes: 5 additions & 1 deletion watchapedia/app/participant/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@

class ParticipantAlreadyExistsError(HTTPException):
def __init__(self):
super().__init__(status_code=409, detail="Participant already exists")
super().__init__(status_code=409, detail="Participant already exists")

class ParticipantNotFoundError(HTTPException):
def __init__(self):
super().__init__(status_code=404, detail="Participant not found")
29 changes: 26 additions & 3 deletions watchapedia/app/participant/repository.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import select
from sqlalchemy import select, distinct
from sqlalchemy.orm import Session
from fastapi import Depends
from watchapedia.database.connection import get_db_session
Expand Down Expand Up @@ -42,6 +42,10 @@ def get_participant(self, name: str, profile_url: str | None) -> Participant | N
)
return self.session.scalar(get_participant_query)

def get_participant_by_id(self, participant_id: int) -> Participant | None:
get_participant_query = select(Participant).filter(Participant.id == participant_id)
return self.session.scalar(get_participant_query)

def get_movie_participant(self, movie_id: int, participant_id: int) -> MovieParticipant | None:
get_movie_participant_query = select(MovieParticipant).filter(
(MovieParticipant.movie_id == movie_id)
Expand All @@ -50,5 +54,24 @@ def get_movie_participant(self, movie_id: int, participant_id: int) -> MoviePart
return self.session.scalar(get_movie_participant_query)


def update_participant(self, biography: str) -> None:
...
def update_participant(self, participant: Participant, name: str | None, profile_url : str | None, biography: str | None ) -> None:
if name:
participant.name = name
if profile_url:
participant.profile_url = profile_url
if biography:
participant.biography = biography
self.session.flush()

def get_participant_roles(self, participant_id: int) -> list[str]:
get_participant_roles_query = select(distinct(MovieParticipant.role)).filter(
MovieParticipant.participant_id == participant_id
)
return self.session.scalars(get_participant_roles_query).all()

def get_participant_movies(self, participant_id: int, cast: str) -> list[Movie]:
get_participant_movies_query = select(Movie).join(MovieParticipant).filter(
(MovieParticipant.participant_id == participant_id)
& (MovieParticipant.role.contains(cast))
)
return self.session.scalars(get_participant_movies_query).all()
73 changes: 73 additions & 0 deletions watchapedia/app/participant/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import Annotated
from fastapi import Depends
from watchapedia.app.participant.repository import ParticipantRepository
from watchapedia.app.participant.errors import ParticipantNotFoundError
from watchapedia.common.errors import InvalidFormatError
from watchapedia.app.participant.dto.responses import ParticipantDataResponse, MovieDataResponse, ParticipantProfileResponse
from watchapedia.app.movie.models import Movie
from watchapedia.app.participant.models import Participant
from collections import defaultdict

class ParticipantService():
def __init__(self,
participant_repository: Annotated[ParticipantRepository, Depends()]
):
self.participant_repository = participant_repository

def get_participant_profile(self, participant_id: int) -> ParticipantProfileResponse:
participant = self.participant_repository.get_participant_by_id(participant_id)
tmp = self.get_participant_roles(participant_id)
# 감독과 배우 구분
roles = set()
for role in tmp:
if "감독" in role:
roles.add("감독")
elif "주연" in role or "조연" in role or "단역" in role:
roles.add("배우")
if participant is None:
raise ParticipantNotFoundError()
return participant, roles

def get_participant_movies(self, participant_id: int) -> list[ParticipantDataResponse]:
participant = self.participant_repository.get_participant_by_id(participant_id)
if participant is None:
raise ParticipantNotFoundError()
participant_info = {"감독": [], "출연": []}
roles = self.get_participant_roles(participant_id)
casts = set()
for role in roles:
casts.add(role.split("|")[0].strip())
for cast in casts:
movies = self.participant_repository.get_participant_movies(participant_id, cast)
if cast == "감독":
participant_info["감독"].extend(self._process_movies(movies, cast))
elif cast in ["주연", "조연", "단역"]:
participant_info["출연"].extend(self._process_movies(movies, cast))
participant_info["감독"] = sorted(participant_info["감독"], key=lambda x: x.year, reverse=True) # 연도 내림차순 정렬
participant_info["출연"] = sorted(participant_info["출연"], key=lambda x: x.year, reverse=True) # 연도 내림차순 정렬
return [self._process_participants(role=cast, movies=movies) for cast, movies in participant_info.items()]


def update_participant(self, participant_id: int, name: str | None, profile_url: str | None, biography: str | None):
participant = self.participant_repository.get_participant_by_id(participant_id)
if participant is None:
raise ParticipantNotFoundError()
if not any([name, profile_url, biography]):
raise InvalidFormatError()
self.participant_repository.update_participant(participant, name, profile_url, biography)


def get_participant_roles(self, participant_id: int):
return self.participant_repository.get_participant_roles(participant_id)

def _process_movies(self, movies: list[Movie], cast: str) -> list[MovieDataResponse]:
return [MovieDataResponse(id=movie.id,
title=movie.title,
year=movie.year,
average_rating=movie.average_rating,
poster_url=movie.poster_url,
cast=cast)
for movie in movies]

def _process_participants(self, role: str, movies: list[MovieDataResponse]) -> ParticipantDataResponse:
return ParticipantDataResponse(role=role, movies=movies)
54 changes: 54 additions & 0 deletions watchapedia/app/participant/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import Annotated
from fastapi import APIRouter, Depends
from watchapedia.app.participant.service import ParticipantService
from watchapedia.app.participant.dto.requests import ParticipantProfileUpdateRequest
from watchapedia.app.participant.dto.responses import ParticipantDataResponse, ParticipantProfileResponse

participant_router = APIRouter()

@participant_router.get('/{participant_id}',
status_code=200,
summary="인물 프로필 출력",
description="인물 id를 받아 해당 인물의 정보(이름, 프로필 url, 역할, 바이오그래피)를 반환합니다.",
response_model=ParticipantProfileResponse
)

def get_participant_profile(
participant_id: int,
participant_service: Annotated[ParticipantService, Depends()],
) -> ParticipantProfileResponse:
profile, roles = participant_service.get_participant_profile(participant_id)
return ParticipantProfileResponse.from_entity(profile, roles)

@participant_router.get('/{participant_id}/movies',
status_code=200,
summary="인물과 관련된 영화 출력",
description="인물 id를 받아 해당 인물과 관련된 영화들의 정보를 개봉연도가 높은 순서로로 반환합니다.",
response_model=list[ParticipantDataResponse]
)
def get_participant_movie(
participant_id: int,
participant_service: Annotated[ParticipantService, Depends()],
) -> list[ParticipantDataResponse]:
return participant_service.get_participant_movies(participant_id)


@participant_router.patch('/{participant_id}',
status_code=200,
summary="인물 정보 수정",
description="인물 id를 받아 해당 인물의 정보(이름, 프로필 url, 바이오그래피)를 수정합니다.",
)
def update_participant_profile(
participant_id: int,
participant_service: Annotated[ParticipantService, Depends()],
profile: ParticipantProfileUpdateRequest
):
participant_service.update_participant(
participant_id,
profile.name,
profile.profile_url,
profile.biography

)

return "Success"
6 changes: 5 additions & 1 deletion watchapedia/common/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ def __init__(self) -> None:

class PermissionDeniedError(HTTPException):
def __init__(self) -> None:
super().__init__(status_code=401, detail="Permission denied")
super().__init__(status_code=401, detail="Permission denied")

class InvalidFormatError(HTTPException):
def __init__(self):
super().__init__(status_code=400, detail="Invalid format")

0 comments on commit 6840d35

Please sign in to comment.