Coverage for opt/mealie/lib/python3.12/site-packages/mealie/routes/users/ratings.py: 41%

52 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-11-25 17:29 +0000

1from functools import cached_property 1a

2from uuid import UUID 1a

3 

4from fastapi import HTTPException, status 1a

5from pydantic import UUID4 1a

6 

7from mealie.repos.all_repositories import get_repositories 1a

8from mealie.routes._base import BaseUserController, controller 1a

9from mealie.routes._base.routers import UserAPIRouter 1a

10from mealie.routes.users._helpers import assert_user_change_allowed 1a

11from mealie.schema.response.responses import ErrorResponse 1a

12from mealie.schema.user.user import UserRatingCreate, UserRatingOut, UserRatings, UserRatingUpdate 1a

13 

14router = UserAPIRouter() 1a

15 

16 

17@controller(router) 1a

18class UserRatingsController(BaseUserController): 1a

19 @cached_property 1a

20 def group_recipes(self): 1a

21 return get_repositories(self.session, group_id=self.group_id, household_id=None).recipes 

22 

23 def get_recipe_or_404(self, slug_or_id: str | UUID): 1a

24 """Fetches a recipe by slug or id, or raises a 404 error if not found.""" 

25 if isinstance(slug_or_id, str): 

26 try: 

27 slug_or_id = UUID(slug_or_id) 

28 except ValueError: 

29 pass 

30 

31 if isinstance(slug_or_id, UUID): 

32 recipe = self.group_recipes.get_one(slug_or_id, key="id") 

33 else: 

34 recipe = self.group_recipes.get_one(slug_or_id, key="slug") 

35 

36 if not recipe: 

37 raise HTTPException( 

38 status.HTTP_404_NOT_FOUND, 

39 detail=ErrorResponse.respond(message="Not found."), 

40 ) 

41 

42 return recipe 

43 

44 @router.get("/{id}/ratings", response_model=UserRatings[UserRatingOut]) 1a

45 async def get_ratings(self, id: UUID4): 1a

46 """Get user's rated recipes""" 

47 return UserRatings(ratings=self.repos.user_ratings.get_by_user(id)) 

48 

49 @router.get("/{id}/favorites", response_model=UserRatings[UserRatingOut]) 1a

50 async def get_favorites(self, id: UUID4): 1a

51 """Get user's favorited recipes""" 

52 return UserRatings(ratings=self.repos.user_ratings.get_by_user(id, favorites_only=True)) 

53 

54 @router.post("/{id}/ratings/{slug}") 1a

55 def set_rating(self, id: UUID4, slug: str, data: UserRatingUpdate): 1a

56 """Sets the user's rating for a recipe""" 

57 assert_user_change_allowed(id, self.user, self.user) 

58 

59 recipe = self.get_recipe_or_404(slug) 

60 user_rating = self.repos.user_ratings.get_by_user_and_recipe(id, recipe.id) 

61 if not user_rating: 

62 self.repos.user_ratings.create( 

63 UserRatingCreate( 

64 user_id=id, 

65 recipe_id=recipe.id, 

66 rating=data.rating, 

67 is_favorite=data.is_favorite or False, 

68 ) 

69 ) 

70 else: 

71 if data.rating is not None: 

72 user_rating.rating = data.rating 

73 if data.is_favorite is not None: 

74 user_rating.is_favorite = data.is_favorite 

75 

76 self.repos.user_ratings.update(user_rating.id, user_rating) 

77 

78 @router.post("/{id}/favorites/{slug}") 1a

79 def add_favorite(self, id: UUID4, slug: str): 1a

80 """Adds a recipe to the user's favorites""" 

81 self.set_rating(id, slug, data=UserRatingUpdate(is_favorite=True)) 

82 

83 @router.delete("/{id}/favorites/{slug}") 1a

84 def remove_favorite(self, id: UUID4, slug: str): 1a

85 """Removes a recipe from the user's favorites""" 

86 self.set_rating(id, slug, data=UserRatingUpdate(is_favorite=False))