feat: Improve Public URL Readability (#2482)

* added support for group slugs

* modified frontend to use links with group slug

* fixed test refs

* unused import

---------

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
Michael Genson
2023-08-20 13:38:46 -05:00
committed by GitHub
parent 99372aa2b6
commit 095edef95e
12 changed files with 166 additions and 18 deletions

View File

@@ -32,6 +32,7 @@ class Group(SqlAlchemyBase, BaseMixins):
__tablename__ = "groups"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: Mapped[str] = mapped_column(sa.String, index=True, nullable=False, unique=True)
slug: Mapped[str | None] = mapped_column(sa.String, index=True, unique=True)
users: Mapped[list["User"]] = orm.relationship("User", back_populates="group")
categories: Mapped[Category] = orm.relationship(
Category, secondary=group_to_categories, single_parent=True, uselist=True

View File

@@ -1,5 +1,11 @@
from collections.abc import Iterable
from typing import cast
from uuid import UUID
from pydantic import UUID4
from slugify import slugify
from sqlalchemy import func, select
from sqlalchemy.exc import IntegrityError
from mealie.db.models.group import Group
from mealie.db.models.recipe.category import Category
@@ -8,19 +14,55 @@ from mealie.db.models.recipe.tag import Tag
from mealie.db.models.recipe.tool import Tool
from mealie.db.models.users.users import User
from mealie.schema.group.group_statistics import GroupStatistics
from mealie.schema.user.user import GroupInDB
from mealie.schema.user.user import GroupBase, GroupInDB
from ..db.models._model_base import SqlAlchemyBase
from .repository_generic import RepositoryGeneric
class RepositoryGroup(RepositoryGeneric[GroupInDB, Group]):
def create(self, data: GroupBase | dict) -> GroupInDB:
if isinstance(data, GroupBase):
data = data.dict()
max_attempts = 10
original_name = cast(str, data["name"])
attempts = 0
while True:
try:
data["slug"] = slugify(data["name"])
return super().create(data)
except IntegrityError:
self.session.rollback()
attempts += 1
if attempts >= max_attempts:
raise
data["name"] = f"{original_name} ({attempts})"
def create_many(self, data: Iterable[GroupInDB | dict]) -> list[GroupInDB]:
# since create uses special logic for resolving slugs, we don't want to use the standard create_many method
return [self.create(new_group) for new_group in data]
def get_by_name(self, name: str) -> GroupInDB | None:
dbgroup = self.session.execute(select(self.model).filter_by(name=name)).scalars().one_or_none()
if dbgroup is None:
return None
return self.schema.from_orm(dbgroup)
def get_by_slug_or_id(self, slug_or_id: str | UUID) -> GroupInDB | None:
if isinstance(slug_or_id, str):
try:
slug_or_id = UUID(slug_or_id)
except ValueError:
pass
if isinstance(slug_or_id, UUID):
return self.get_one(slug_or_id)
else:
return self.get_one(slug_or_id, key="slug")
def statistics(self, group_id: UUID4) -> GroupStatistics:
def model_count(model: type[SqlAlchemyBase]) -> int:
stmt = select(func.count(model.id)).filter_by(group_id=group_id)

View File

@@ -1,5 +1,4 @@
from fastapi import APIRouter, HTTPException
from pydantic import UUID4
from mealie.routes._base import controller
from mealie.routes._base.base_controllers import BasePublicController
@@ -10,14 +9,14 @@ router = APIRouter(prefix="/explore", tags=["Explore: Recipes"])
@controller(router)
class PublicRecipesController(BasePublicController):
@router.get("/recipes/{group_id}/{recipe_slug}", response_model=Recipe)
def get_recipe(self, group_id: UUID4, recipe_slug: str) -> Recipe:
group = self.repos.groups.get_one(group_id)
@router.get("/recipes/{group_slug}/{recipe_slug}", response_model=Recipe)
def get_recipe(self, group_slug: str, recipe_slug: str) -> Recipe:
group = self.repos.groups.get_by_slug_or_id(group_slug)
if not group or group.preferences.private_group:
raise HTTPException(404, "group not found")
recipe = self.repos.recipes.by_group(group_id).get_one(recipe_slug)
recipe = self.repos.recipes.by_group(group.id).get_one(recipe_slug)
if not recipe or not recipe.settings.public:
raise HTTPException(404, "recipe not found")

View File

@@ -181,6 +181,7 @@ class PrivateUser(UserOut):
class UpdateGroup(GroupBase):
id: UUID4
name: str
slug: str
categories: list[CategoryBase] | None = []
webhooks: list[Any] = []