mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-10-27 08:14:30 -04:00
fix: dash slug names (#5709)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
144d4caea6
commit
107dfc34de
@@ -22,6 +22,14 @@ class PermissionDenied(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SlugError(Exception):
|
||||||
|
"""
|
||||||
|
This exception is raised when the recipe name generates an invalid slug.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoEntryFound(Exception):
|
class NoEntryFound(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised when a user tries to access a resource that does not exist.
|
This exception is raised when a user tries to access a resource that does not exist.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
import sqlalchemy.exc
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
BackgroundTasks,
|
BackgroundTasks,
|
||||||
Depends,
|
Depends,
|
||||||
@@ -80,13 +81,25 @@ class RecipeController(BaseRecipeController):
|
|||||||
|
|
||||||
if thrownType == exceptions.PermissionDenied:
|
if thrownType == exceptions.PermissionDenied:
|
||||||
self.logger.error("Permission Denied on recipe controller action")
|
self.logger.error("Permission Denied on recipe controller action")
|
||||||
raise HTTPException(status_code=403, detail=ErrorResponse.respond(message="Permission Denied"))
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN, detail=ErrorResponse.respond(message="Permission Denied")
|
||||||
|
)
|
||||||
elif thrownType == exceptions.NoEntryFound:
|
elif thrownType == exceptions.NoEntryFound:
|
||||||
self.logger.error("No Entry Found on recipe controller action")
|
self.logger.error("No Entry Found on recipe controller action")
|
||||||
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found"))
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail=ErrorResponse.respond(message="No Entry Found")
|
||||||
|
)
|
||||||
elif thrownType == sqlalchemy.exc.IntegrityError:
|
elif thrownType == sqlalchemy.exc.IntegrityError:
|
||||||
self.logger.error("SQL Integrity Error on recipe controller action")
|
self.logger.error("SQL Integrity Error on recipe controller action")
|
||||||
raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists"))
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail=ErrorResponse.respond(message="Recipe already exists")
|
||||||
|
)
|
||||||
|
elif thrownType == exceptions.SlugError:
|
||||||
|
self.logger.error("Failed to generate a valid slug from recipe name")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ErrorResponse.respond(message="Unable to generate recipe slug"),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.error("Unknown Error on recipe controller action")
|
self.logger.error("Unknown Error on recipe controller action")
|
||||||
self.logger.exception(ex)
|
self.logger.exception(ex)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from sqlalchemy.orm import Session, joinedload, selectinload
|
|||||||
from sqlalchemy.orm.interfaces import LoaderOption
|
from sqlalchemy.orm.interfaces import LoaderOption
|
||||||
|
|
||||||
from mealie.core.config import get_app_dirs
|
from mealie.core.config import get_app_dirs
|
||||||
|
from mealie.core.exceptions import SlugError
|
||||||
from mealie.db.models.users.users import User
|
from mealie.db.models.users.users import User
|
||||||
from mealie.schema._mealie import MealieModel, SearchType
|
from mealie.schema._mealie import MealieModel, SearchType
|
||||||
from mealie.schema._mealie.mealie_model import UpdatedAtField
|
from mealie.schema._mealie.mealie_model import UpdatedAtField
|
||||||
@@ -45,8 +46,13 @@ def create_recipe_slug(name: str, max_length: int = 250) -> str:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A truncated slug string
|
A truncated slug string
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the name cannot be converted to a valid slug
|
||||||
"""
|
"""
|
||||||
generated_slug = slugify(name)
|
generated_slug = slugify(name)
|
||||||
|
if not generated_slug:
|
||||||
|
raise SlugError("Recipe name cannot be empty or contain only special characters")
|
||||||
if len(generated_slug) > max_length:
|
if len(generated_slug) > max_length:
|
||||||
generated_slug = generated_slug[:max_length]
|
generated_slug = generated_slug[:max_length]
|
||||||
return generated_slug
|
return generated_slug
|
||||||
|
|||||||
@@ -929,6 +929,13 @@ def test_create_recipe_with_extremely_long_slug(api_client: TestClient, unique_u
|
|||||||
assert updated_recipe["slug"] == slugify(new_name)
|
assert updated_recipe["slug"] == slugify(new_name)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_recipe_slug_not_empty(api_client: TestClient, unique_user: TestUser):
|
||||||
|
recipe_name = "---" # will result in an empty slug
|
||||||
|
|
||||||
|
response = api_client.post(api_routes.recipes, json={"name": recipe_name}, headers=unique_user.token)
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
def test_create_recipe_slug_length_validation(api_client: TestClient, unique_user: TestUser):
|
def test_create_recipe_slug_length_validation(api_client: TestClient, unique_user: TestUser):
|
||||||
"""Test that recipe slugs are properly truncated to a reasonable length."""
|
"""Test that recipe slugs are properly truncated to a reasonable length."""
|
||||||
very_long_name = "A" * 500 # 500 character name
|
very_long_name = "A" * 500 # 500 character name
|
||||||
|
|||||||
Reference in New Issue
Block a user