feat: User-specific Recipe Ratings (#3345)

This commit is contained in:
Michael Genson
2024-04-11 21:28:43 -05:00
committed by GitHub
parent 8ab09cf03b
commit 2a541f081a
50 changed files with 1497 additions and 443 deletions

View File

@@ -1,6 +1,6 @@
from fastapi import APIRouter
from . import api_tokens, crud, favorites, forgot_password, images, registration
from . import api_tokens, crud, forgot_password, images, ratings, registration
# Must be used because of the way FastAPI works with nested routes
user_prefix = "/users"
@@ -13,4 +13,4 @@ router.include_router(crud.admin_router)
router.include_router(forgot_password.router, prefix=user_prefix, tags=["Users: Passwords"])
router.include_router(images.router, prefix=user_prefix, tags=["Users: Images"])
router.include_router(api_tokens.router)
router.include_router(favorites.router, prefix=user_prefix, tags=["Users: Favorites"])
router.include_router(ratings.router, prefix=user_prefix, tags=["Users: Ratings"])

View File

@@ -11,7 +11,14 @@ from mealie.routes.users._helpers import assert_user_change_allowed
from mealie.schema.response import ErrorResponse, SuccessResponse
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserOut
from mealie.schema.user.user import GroupInDB, UserPagination, UserSummary, UserSummaryPagination
from mealie.schema.user.user import (
GroupInDB,
UserPagination,
UserRatings,
UserRatingSummary,
UserSummary,
UserSummaryPagination,
)
user_router = UserAPIRouter(prefix="/users", tags=["Users: CRUD"])
admin_router = AdminAPIRouter(prefix="/users", tags=["Users: Admin CRUD"])
@@ -74,6 +81,25 @@ class UserController(BaseUserController):
def get_logged_in_user(self):
return self.user
@user_router.get("/self/ratings", response_model=UserRatings[UserRatingSummary])
def get_logged_in_user_ratings(self):
return UserRatings(ratings=self.repos.user_ratings.get_by_user(self.user.id))
@user_router.get("/self/ratings/{recipe_id}", response_model=UserRatingSummary)
def get_logged_in_user_rating_for_recipe(self, recipe_id: UUID4):
user_rating = self.repos.user_ratings.get_by_user_and_recipe(self.user.id, recipe_id)
if user_rating:
return user_rating
else:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
ErrorResponse.respond("User has not rated this recipe"),
)
@user_router.get("/self/favorites", response_model=UserRatings[UserRatingSummary])
def get_logged_in_user_favorites(self):
return UserRatings(ratings=self.repos.user_ratings.get_by_user(self.user.id, favorites_only=True))
@user_router.get("/self/group", response_model=GroupInDB)
def get_logged_in_user_group(self):
return self.group

View File

@@ -1,39 +0,0 @@
from pydantic import UUID4
from mealie.routes._base import BaseUserController, controller
from mealie.routes._base.routers import UserAPIRouter
from mealie.routes.users._helpers import assert_user_change_allowed
from mealie.schema.user import UserFavorites
router = UserAPIRouter()
@controller(router)
class UserFavoritesController(BaseUserController):
@router.get("/{id}/favorites", response_model=UserFavorites)
async def get_favorites(self, id: UUID4):
"""Get user's favorite recipes"""
return self.repos.users.get_one(id, override_schema=UserFavorites)
@router.post("/{id}/favorites/{slug}")
def add_favorite(self, id: UUID4, slug: str):
"""Adds a Recipe to the users favorites"""
assert_user_change_allowed(id, self.user)
if not self.user.favorite_recipes:
self.user.favorite_recipes = []
self.user.favorite_recipes.append(slug)
self.repos.users.update(self.user.id, self.user)
@router.delete("/{id}/favorites/{slug}")
def remove_favorite(self, id: UUID4, slug: str):
"""Adds a Recipe to the users favorites"""
assert_user_change_allowed(id, self.user)
if not self.user.favorite_recipes:
self.user.favorite_recipes = []
self.user.favorite_recipes = [x for x in self.user.favorite_recipes if x != slug]
self.repos.users.update(self.user.id, self.user)
return

View File

@@ -0,0 +1,81 @@
from uuid import UUID
from fastapi import HTTPException, status
from pydantic import UUID4
from mealie.routes._base import BaseUserController, controller
from mealie.routes._base.routers import UserAPIRouter
from mealie.routes.users._helpers import assert_user_change_allowed
from mealie.schema.response.responses import ErrorResponse
from mealie.schema.user.user import UserRatingCreate, UserRatingOut, UserRatings, UserRatingUpdate
router = UserAPIRouter()
@controller(router)
class UserRatingsController(BaseUserController):
def get_recipe_or_404(self, slug_or_id: str | UUID):
"""Fetches a recipe by slug or id, or raises a 404 error if not found."""
if isinstance(slug_or_id, str):
try:
slug_or_id = UUID(slug_or_id)
except ValueError:
pass
recipes_repo = self.repos.recipes.by_group(self.group_id)
if isinstance(slug_or_id, UUID):
recipe = recipes_repo.get_one(slug_or_id, key="id")
else:
recipe = recipes_repo.get_one(slug_or_id, key="slug")
if not recipe:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
detail=ErrorResponse.respond(message="Not found."),
)
return recipe
@router.get("/{id}/ratings", response_model=UserRatings[UserRatingOut])
async def get_ratings(self, id: UUID4):
"""Get user's rated recipes"""
return UserRatings(ratings=self.repos.user_ratings.get_by_user(id))
@router.get("/{id}/favorites", response_model=UserRatings[UserRatingOut])
async def get_favorites(self, id: UUID4):
"""Get user's favorited recipes"""
return UserRatings(ratings=self.repos.user_ratings.get_by_user(id, favorites_only=True))
@router.post("/{id}/ratings/{slug}")
def set_rating(self, id: UUID4, slug: str, data: UserRatingUpdate):
"""Sets the user's rating for a recipe"""
assert_user_change_allowed(id, self.user)
recipe = self.get_recipe_or_404(slug)
user_rating = self.repos.user_ratings.get_by_user_and_recipe(id, recipe.id)
if not user_rating:
self.repos.user_ratings.create(
UserRatingCreate(
user_id=id,
recipe_id=recipe.id,
rating=data.rating,
is_favorite=data.is_favorite or False,
)
)
else:
if data.rating is not None:
user_rating.rating = data.rating
if data.is_favorite is not None:
user_rating.is_favorite = data.is_favorite
self.repos.user_ratings.update(user_rating.id, user_rating)
@router.post("/{id}/favorites/{slug}")
def add_favorite(self, id: UUID4, slug: str):
"""Adds a recipe to the user's favorites"""
self.set_rating(id, slug, data=UserRatingUpdate(is_favorite=True))
@router.delete("/{id}/favorites/{slug}")
def remove_favorite(self, id: UUID4, slug: str):
"""Removes a recipe from the user's favorites"""
self.set_rating(id, slug, data=UserRatingUpdate(is_favorite=False))