mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-01-22 08:51:19 -05:00
feat: Remove Explore URLs and make the normal URLs public (#2632)
* add groupSlug to most routes * fixed more routing issues * fixed jank and incorrect routes * remove public explore links * remove unused groupSlug and explore routes * nuked explore pages * fixed public toolstore bug * fixed various routes missing group slug * restored public app header menu * fix janky login redirect * 404 recipe API call returns to login * removed unused explore layout * force redirect when using the wrong group slug * fixed dead admin links * removed unused middleware from earlier attempt * 🧹 * improve cookbooks sidebar fixed sidebar link not working fixed sidebar link target hide cookbooks header when there are none * added group slug to user * fix $auth typehints * vastly simplified groupSlug logic * allow logged-in users to view other groups * fixed some edgecases that bypassed isOwnGroup * fixed static home ref * 🧹 * fixed redirect logic * lint warning * removed group slug from group and user pages refactored all components to use route groupSlug or user group slug moved some group pages to recipe pages * fixed some bad types * 🧹 * moved groupSlug routes under /g/groupSlug * move /recipe/ to /r/ * fix backend url generation and metadata injection * moved shopping lists to root/other route fixes * changed shared from /recipes/ to /r/ * fixed 404 redirect not awaiting * removed unused import * fix doc links * fix public recipe setting not affecting public API * fixed backend tests * fix nuxt-generate command --------- Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
@@ -58,7 +58,7 @@ async def get_public_group(group_slug: str = fastapi.Path(...), session=Depends(
|
||||
repos = get_repositories(session)
|
||||
group = repos.groups.get_by_slug_or_id(group_slug)
|
||||
|
||||
if not group or group.preferences.private_group:
|
||||
if not group or group.preferences.private_group or not group.preferences.recipe_public:
|
||||
raise HTTPException(404, "group not found")
|
||||
else:
|
||||
return group
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, orm
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from mealie.core.config import get_app_settings
|
||||
@@ -94,6 +95,10 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||
"group",
|
||||
}
|
||||
|
||||
@hybrid_property
|
||||
def group_slug(self) -> str:
|
||||
return self.group.slug
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, session, full_name, password, group: str | None = None, **kwargs) -> None:
|
||||
if group is None:
|
||||
|
||||
@@ -187,7 +187,7 @@ class RecipeController(BaseRecipeController):
|
||||
message=self.t(
|
||||
"notifications.generic-created-with-url",
|
||||
name=new_recipe.name,
|
||||
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, new_recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -306,7 +306,7 @@ class RecipeController(BaseRecipeController):
|
||||
message=self.t(
|
||||
"notifications.generic-created-with-url",
|
||||
name=new_recipe.name,
|
||||
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, new_recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -347,7 +347,7 @@ class RecipeController(BaseRecipeController):
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -368,7 +368,7 @@ class RecipeController(BaseRecipeController):
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -390,7 +390,7 @@ class RecipeController(BaseRecipeController):
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -95,7 +95,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -120,7 +120,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -148,7 +148,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ __app_settings = get_app_settings()
|
||||
__contents = ""
|
||||
|
||||
|
||||
def content_with_meta(recipe: Recipe) -> str:
|
||||
def content_with_meta(group_slug: str, recipe: Recipe) -> str:
|
||||
# Inject meta tags
|
||||
recipe_url = f"{__app_settings.BASE_URL}/recipe/{recipe.slug}"
|
||||
recipe_url = f"{__app_settings.BASE_URL}/g/{group_slug}/r/{recipe.slug}"
|
||||
image_url = f"{__app_settings.BASE_URL}/api/media/recipes/{recipe.id}/images/original.webp?version={recipe.image}"
|
||||
|
||||
ingredients: list[str] = []
|
||||
@@ -122,28 +122,29 @@ def serve_recipe_with_meta_public(
|
||||
return response_404()
|
||||
|
||||
# Inject meta tags
|
||||
return Response(content_with_meta(recipe), media_type="text/html")
|
||||
return Response(content_with_meta(group_slug, recipe), media_type="text/html")
|
||||
except Exception:
|
||||
return response_404()
|
||||
|
||||
|
||||
async def serve_recipe_with_meta(
|
||||
slug: str,
|
||||
user: PrivateUser = Depends(try_get_current_user),
|
||||
group_slug: str,
|
||||
recipe_slug: str,
|
||||
user: PrivateUser | None = Depends(try_get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
if not user:
|
||||
return Response(__contents, media_type="text/html", status_code=401)
|
||||
return serve_recipe_with_meta_public(group_slug, recipe_slug, session)
|
||||
|
||||
try:
|
||||
repos = AllRepositories(session)
|
||||
|
||||
recipe = repos.recipes.by_group(user.group_id).get_one(slug, "slug")
|
||||
recipe = repos.recipes.by_group(user.group_id).get_one(recipe_slug, "slug")
|
||||
if recipe is None:
|
||||
return response_404()
|
||||
|
||||
# Serve contents as HTML
|
||||
return Response(content_with_meta(recipe), media_type="text/html")
|
||||
return Response(content_with_meta(group_slug, recipe), media_type="text/html")
|
||||
except Exception:
|
||||
return response_404()
|
||||
|
||||
@@ -155,6 +156,5 @@ def mount_spa(app: FastAPI):
|
||||
global __contents
|
||||
__contents = pathlib.Path(__app_settings.STATIC_FILES).joinpath("index.html").read_text()
|
||||
|
||||
app.get("/recipe/{slug}")(serve_recipe_with_meta)
|
||||
app.get("/explore/recipes/{group_slug}/{recipe_slug}")(serve_recipe_with_meta_public)
|
||||
app.get("/g/{group_slug}/r/{recipe_slug}")(serve_recipe_with_meta)
|
||||
app.mount("/", SPAStaticFiles(directory=__app_settings.STATIC_FILES, html=True), name="spa")
|
||||
|
||||
@@ -104,6 +104,7 @@ class UserOut(UserBase):
|
||||
id: UUID4
|
||||
group: str
|
||||
group_id: UUID4
|
||||
group_slug: str
|
||||
tokens: list[LongLiveTokenOut] | None
|
||||
cache_key: str
|
||||
favorite_recipes: list[str] | None = []
|
||||
|
||||
@@ -11,9 +11,9 @@ def _base_or(base_url: str | None) -> str:
|
||||
return base_url
|
||||
|
||||
|
||||
def recipe_url(recipe_slug: str, base_url: str | None) -> str:
|
||||
def recipe_url(group_slug: str, recipe_slug: str, base_url: str | None) -> str:
|
||||
base = _base_or(base_url)
|
||||
return f"{base}/recipe/{recipe_slug}"
|
||||
return f"{base}/g/{group_slug}/r/{recipe_slug}"
|
||||
|
||||
|
||||
def shopping_list_url(shopping_list_id: UUID4 | str, base_url: str | None) -> str:
|
||||
|
||||
Reference in New Issue
Block a user