Coverage for opt/mealie/lib/python3.12/site-packages/mealie/routes/users/ratings.py: 95%
52 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 03:04 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 03:04 +0000
1from functools import cached_property
2from uuid import UUID
4from fastapi import HTTPException, status
5from pydantic import UUID4
7from mealie.repos.all_repositories import get_repositories
8from mealie.routes._base import BaseUserController, controller
9from mealie.routes._base.routers import UserAPIRouter
10from mealie.routes.users._helpers import assert_user_change_allowed
11from mealie.schema.response.responses import ErrorResponse
12from mealie.schema.user.user import UserRatingCreate, UserRatingOut, UserRatings, UserRatingUpdate
14router = UserAPIRouter()
17@controller(router)
18class UserRatingsController(BaseUserController):
19 @cached_property
20 def group_recipes(self):
21 return get_repositories(self.session, group_id=self.group_id, household_id=None).recipes
23 def get_recipe_or_404(self, slug_or_id: str | UUID):
24 """Fetches a recipe by slug or id, or raises a 404 error if not found."""
25 if isinstance(slug_or_id, str): 25 ↛ 31line 25 didn't jump to line 31 because the condition on line 25 was always true
26 try:
27 slug_or_id = UUID(slug_or_id)
28 except ValueError:
29 pass
31 if isinstance(slug_or_id, UUID): 31 ↛ 32line 31 didn't jump to line 32 because the condition on line 31 was never true
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")
36 if not recipe:
37 raise HTTPException(
38 status.HTTP_404_NOT_FOUND,
39 detail=ErrorResponse.respond(message="Not found."),
40 )
42 return recipe
44 @router.get("/{id}/ratings", response_model=UserRatings[UserRatingOut])
45 async def get_ratings(self, id: UUID4):
46 """Get user's rated recipes"""
47 return UserRatings(ratings=self.repos.user_ratings.get_by_user(id))
49 @router.get("/{id}/favorites", response_model=UserRatings[UserRatingOut])
50 async def get_favorites(self, id: UUID4):
51 """Get user's favorited recipes"""
52 return UserRatings(ratings=self.repos.user_ratings.get_by_user(id, favorites_only=True))
54 @router.post("/{id}/ratings/{slug}")
55 def set_rating(self, id: UUID4, slug: str, data: UserRatingUpdate):
56 """Sets the user's rating for a recipe"""
57 assert_user_change_allowed(id, self.user, self.user)
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
76 self.repos.user_ratings.update(user_rating.id, user_rating)
78 @router.post("/{id}/favorites/{slug}")
79 def add_favorite(self, id: UUID4, slug: str):
80 """Adds a recipe to the user's favorites"""
81 self.set_rating(id, slug, data=UserRatingUpdate(is_favorite=True))
83 @router.delete("/{id}/favorites/{slug}")
84 def remove_favorite(self, id: UUID4, slug: str):
85 """Removes a recipe from the user's favorites"""
86 self.set_rating(id, slug, data=UserRatingUpdate(is_favorite=False))