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:
Michael Genson
2023-11-05 19:07:02 -06:00
committed by GitHub
parent 94cf690e8f
commit 80968b02bb
87 changed files with 555 additions and 501 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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),
),
)

View File

@@ -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),
),
)

View File

@@ -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")

View File

@@ -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 = []

View File

@@ -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: