mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
feat: add initial notification support
* Add updated recipe notification * Add recipe deleted notification * Add notifications translations * Shopping lists full c/u/d notifications * Add categories c/u/d notifications * Deal with None values in translation provider * Add tag c/u/d notifications * Add cookbook c/u/d notifications * use single key pairs for consistency with frontend * change dependency injection strategy * use generic update messages * use service to manage url generation server-side * use new strategies for messages * fix translator Co-authored-by: Miroito <alban.vachette@gmail.com>
This commit is contained in:
parent
841b560abc
commit
b2066dfe72
@ -11,5 +11,12 @@
|
|||||||
"integrity-error": "Database integrity error",
|
"integrity-error": "Database integrity error",
|
||||||
"username-conflict-error": "This username is already taken",
|
"username-conflict-error": "This username is already taken",
|
||||||
"email-conflict-error": "This email is already in use"
|
"email-conflict-error": "This email is already in use"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"generic-created": "{name} was created",
|
||||||
|
"generic-updated": "{name} was updated",
|
||||||
|
"generic-created-with-url": "{name} has been created, {url}",
|
||||||
|
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||||
|
"generic-deleted": "{name} has been created"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,10 @@ class JsonProvider:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if i == last:
|
if i == last:
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if not value:
|
||||||
|
value = ""
|
||||||
|
translation_value = translation_value.replace("{" + key + "}", value)
|
||||||
return translation_value
|
return translation_value
|
||||||
|
|
||||||
return default or key
|
return default or key
|
||||||
|
@ -5,6 +5,8 @@ from fastapi import Depends
|
|||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.core.exceptions import mealie_registered_exceptions
|
from mealie.core.exceptions import mealie_registered_exceptions
|
||||||
|
from mealie.lang import local_provider
|
||||||
|
from mealie.lang.providers import Translator
|
||||||
from mealie.repos.all_repositories import AllRepositories
|
from mealie.repos.all_repositories import AllRepositories
|
||||||
from mealie.routes._base.checks import OperationChecks
|
from mealie.routes._base.checks import OperationChecks
|
||||||
from mealie.routes._base.dependencies import SharedDependencies
|
from mealie.routes._base.dependencies import SharedDependencies
|
||||||
@ -19,6 +21,10 @@ class BasePublicController(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
deps: SharedDependencies = Depends(SharedDependencies.public)
|
deps: SharedDependencies = Depends(SharedDependencies.public)
|
||||||
|
translator: Translator = Depends(local_provider)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.t = self.translator.t if self.translator else local_provider().t
|
||||||
|
|
||||||
|
|
||||||
class BaseUserController(ABC):
|
class BaseUserController(ABC):
|
||||||
@ -29,6 +35,10 @@ class BaseUserController(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
deps: SharedDependencies = Depends(SharedDependencies.user)
|
deps: SharedDependencies = Depends(SharedDependencies.user)
|
||||||
|
translator: Translator = Depends(local_provider)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.t = self.translator.t if self.translator else local_provider().t
|
||||||
|
|
||||||
def registered_exceptions(self, ex: type[Exception]) -> str:
|
def registered_exceptions(self, ex: type[Exception]) -> str:
|
||||||
registered = {
|
registered = {
|
||||||
@ -65,3 +75,7 @@ class BaseAdminController(BaseUserController):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
deps: SharedDependencies = Depends(SharedDependencies.admin)
|
deps: SharedDependencies = Depends(SharedDependencies.admin)
|
||||||
|
translator: Translator = Depends(local_provider)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.t = self.translator.t if self.translator else local_provider().t
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.core.exceptions import mealie_registered_exceptions
|
from mealie.core.exceptions import mealie_registered_exceptions
|
||||||
@ -8,12 +8,17 @@ from mealie.routes._base import BaseUserController, controller
|
|||||||
from mealie.routes._base.mixins import HttpRepo
|
from mealie.routes._base.mixins import HttpRepo
|
||||||
from mealie.schema import mapper
|
from mealie.schema import mapper
|
||||||
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
||||||
|
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||||
|
from mealie.services.event_bus_service.message_types import EventTypes
|
||||||
|
|
||||||
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"])
|
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"])
|
||||||
|
|
||||||
|
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class GroupCookbookController(BaseUserController):
|
class GroupCookbookController(BaseUserController):
|
||||||
|
|
||||||
|
event_bus: EventBusService = Depends(EventBusService)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def repo(self):
|
def repo(self):
|
||||||
return self.deps.repos.cookbooks.by_group(self.group_id)
|
return self.deps.repos.cookbooks.by_group(self.group_id)
|
||||||
@ -41,7 +46,15 @@ class GroupCookbookController(BaseUserController):
|
|||||||
@router.post("", response_model=ReadCookBook, status_code=201)
|
@router.post("", response_model=ReadCookBook, status_code=201)
|
||||||
def create_one(self, data: CreateCookBook):
|
def create_one(self, data: CreateCookBook):
|
||||||
data = mapper.cast(data, SaveCookBook, group_id=self.group_id)
|
data = mapper.cast(data, SaveCookBook, group_id=self.group_id)
|
||||||
return self.mixins.create_one(data)
|
val = self.mixins.create_one(data)
|
||||||
|
|
||||||
|
if val:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.cookbook_created,
|
||||||
|
msg=self.t("notifications.generic-created", name=val.name),
|
||||||
|
)
|
||||||
|
return val
|
||||||
|
|
||||||
@router.put("", response_model=list[ReadCookBook])
|
@router.put("", response_model=list[ReadCookBook])
|
||||||
def update_many(self, data: list[UpdateCookBook]):
|
def update_many(self, data: list[UpdateCookBook]):
|
||||||
@ -75,8 +88,23 @@ class GroupCookbookController(BaseUserController):
|
|||||||
|
|
||||||
@router.put("/{item_id}", response_model=ReadCookBook)
|
@router.put("/{item_id}", response_model=ReadCookBook)
|
||||||
def update_one(self, item_id: str, data: CreateCookBook):
|
def update_one(self, item_id: str, data: CreateCookBook):
|
||||||
return self.mixins.update_one(data, item_id) # type: ignore
|
val = self.mixins.update_one(data, item_id) # type: ignore
|
||||||
|
if val:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.cookbook_updated,
|
||||||
|
msg=self.t("notifications.generic-updated", name=val.name),
|
||||||
|
)
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
@router.delete("/{item_id}", response_model=ReadCookBook)
|
@router.delete("/{item_id}", response_model=ReadCookBook)
|
||||||
def delete_one(self, item_id: str):
|
def delete_one(self, item_id: str):
|
||||||
return self.mixins.delete_one(item_id)
|
val = self.mixins.delete_one(item_id)
|
||||||
|
if val:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.cookbook_deleted,
|
||||||
|
msg=self.t("notifications.generic-deleted", name=val.name),
|
||||||
|
)
|
||||||
|
return val
|
||||||
|
@ -28,6 +28,9 @@ item_router = APIRouter(prefix="/groups/shopping/items", tags=["Group: Shopping
|
|||||||
|
|
||||||
@controller(item_router)
|
@controller(item_router)
|
||||||
class ShoppingListItemController(BaseUserController):
|
class ShoppingListItemController(BaseUserController):
|
||||||
|
|
||||||
|
event_bus: EventBusService = Depends(EventBusService)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service(self):
|
def service(self):
|
||||||
return ShoppingListService(self.repos)
|
return ShoppingListService(self.repos)
|
||||||
@ -106,7 +109,7 @@ class ShoppingListController(BaseUserController):
|
|||||||
# CRUD Operations
|
# CRUD Operations
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def mixins(self) -> HttpRepo:
|
def mixins(self) -> HttpRepo[ShoppingListCreate, ShoppingListOut, ShoppingListSave]:
|
||||||
return HttpRepo(self.repo, self.deps.logger, self.registered_exceptions, "An unexpected error occurred.")
|
return HttpRepo(self.repo, self.deps.logger, self.registered_exceptions, "An unexpected error occurred.")
|
||||||
|
|
||||||
@router.get("", response_model=list[ShoppingListSummary])
|
@router.get("", response_model=list[ShoppingListSummary])
|
||||||
@ -122,7 +125,7 @@ class ShoppingListController(BaseUserController):
|
|||||||
self.event_bus.dispatch(
|
self.event_bus.dispatch(
|
||||||
self.deps.acting_user.group_id,
|
self.deps.acting_user.group_id,
|
||||||
EventTypes.shopping_list_created,
|
EventTypes.shopping_list_created,
|
||||||
msg="A new shopping list has been created.",
|
msg=self.t("notifications.generic-created", name=val.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
return val
|
return val
|
||||||
@ -133,11 +136,25 @@ class ShoppingListController(BaseUserController):
|
|||||||
|
|
||||||
@router.put("/{item_id}", response_model=ShoppingListOut)
|
@router.put("/{item_id}", response_model=ShoppingListOut)
|
||||||
def update_one(self, item_id: UUID4, data: ShoppingListUpdate):
|
def update_one(self, item_id: UUID4, data: ShoppingListUpdate):
|
||||||
return self.mixins.update_one(data, item_id)
|
data = self.mixins.update_one(data, item_id) # type: ignore
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.shopping_list_updated,
|
||||||
|
msg=self.t("notifications.generic-updated", name=data.name),
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
@router.delete("/{item_id}", response_model=ShoppingListOut)
|
@router.delete("/{item_id}", response_model=ShoppingListOut)
|
||||||
def delete_one(self, item_id: UUID4):
|
def delete_one(self, item_id: UUID4):
|
||||||
return self.mixins.delete_one(item_id) # type: ignore
|
data = self.mixins.delete_one(item_id) # type: ignore
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.shopping_list_updated,
|
||||||
|
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Other Operations
|
# Other Operations
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, Depends
|
||||||
from pydantic import UUID4, BaseModel
|
from pydantic import UUID4, BaseModel
|
||||||
|
|
||||||
from mealie.routes._base import BaseUserController, controller
|
from mealie.routes._base import BaseUserController, controller
|
||||||
@ -9,6 +9,9 @@ from mealie.schema import mapper
|
|||||||
from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
|
from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
|
||||||
from mealie.schema.recipe.recipe import RecipeCategory
|
from mealie.schema.recipe.recipe import RecipeCategory
|
||||||
from mealie.schema.recipe.recipe_category import CategoryBase, CategorySave
|
from mealie.schema.recipe.recipe_category import CategoryBase, CategorySave
|
||||||
|
from mealie.services import urls
|
||||||
|
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||||
|
from mealie.services.event_bus_service.message_types import EventTypes
|
||||||
|
|
||||||
router = APIRouter(prefix="/categories", tags=["Organizer: Categories"])
|
router = APIRouter(prefix="/categories", tags=["Organizer: Categories"])
|
||||||
|
|
||||||
@ -24,6 +27,9 @@ class CategorySummary(BaseModel):
|
|||||||
|
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class RecipeCategoryController(BaseUserController):
|
class RecipeCategoryController(BaseUserController):
|
||||||
|
|
||||||
|
event_bus: EventBusService = Depends(EventBusService)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# CRUD Operations
|
# CRUD Operations
|
||||||
@cached_property
|
@cached_property
|
||||||
@ -43,7 +49,18 @@ class RecipeCategoryController(BaseUserController):
|
|||||||
def create_one(self, category: CategoryIn):
|
def create_one(self, category: CategoryIn):
|
||||||
"""Creates a Category in the database"""
|
"""Creates a Category in the database"""
|
||||||
save_data = mapper.cast(category, CategorySave, group_id=self.group_id)
|
save_data = mapper.cast(category, CategorySave, group_id=self.group_id)
|
||||||
return self.mixins.create_one(save_data)
|
data = self.mixins.create_one(save_data)
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.category_created,
|
||||||
|
msg=self.t(
|
||||||
|
"notifications.generic-created-with-url",
|
||||||
|
name=data.name,
|
||||||
|
url=urls.category_url(data.slug, self.deps.settings.BASE_URL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
@router.get("/{item_id}", response_model=CategorySummary)
|
@router.get("/{item_id}", response_model=CategorySummary)
|
||||||
def get_one(self, item_id: UUID4):
|
def get_one(self, item_id: UUID4):
|
||||||
@ -56,7 +73,19 @@ class RecipeCategoryController(BaseUserController):
|
|||||||
def update_one(self, item_id: UUID4, update_data: CategoryIn):
|
def update_one(self, item_id: UUID4, update_data: CategoryIn):
|
||||||
"""Updates an existing Tag in the database"""
|
"""Updates an existing Tag in the database"""
|
||||||
save_data = mapper.cast(update_data, CategorySave, group_id=self.group_id)
|
save_data = mapper.cast(update_data, CategorySave, group_id=self.group_id)
|
||||||
return self.mixins.update_one(save_data, item_id)
|
data = self.mixins.update_one(save_data, item_id)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.category_updated,
|
||||||
|
msg=self.t(
|
||||||
|
"notifications.generic-updated-with-url",
|
||||||
|
name=data.name,
|
||||||
|
url=urls.category_url(data.slug, self.deps.settings.BASE_URL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
@router.delete("/{item_id}")
|
@router.delete("/{item_id}")
|
||||||
def delete_one(self, item_id: UUID4):
|
def delete_one(self, item_id: UUID4):
|
||||||
@ -65,7 +94,12 @@ class RecipeCategoryController(BaseUserController):
|
|||||||
category does not impact a recipe. The category will be removed
|
category does not impact a recipe. The category will be removed
|
||||||
from any recipes that contain it
|
from any recipes that contain it
|
||||||
"""
|
"""
|
||||||
self.mixins.delete_one(item_id)
|
if data := self.mixins.delete_one(item_id):
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.category_deleted,
|
||||||
|
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||||
|
)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Read All Operations
|
# Read All Operations
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.routes._base import BaseUserController, controller
|
from mealie.routes._base import BaseUserController, controller
|
||||||
@ -9,12 +9,18 @@ from mealie.schema import mapper
|
|||||||
from mealie.schema.recipe import RecipeTagResponse, TagIn
|
from mealie.schema.recipe import RecipeTagResponse, TagIn
|
||||||
from mealie.schema.recipe.recipe import RecipeTag
|
from mealie.schema.recipe.recipe import RecipeTag
|
||||||
from mealie.schema.recipe.recipe_category import TagSave
|
from mealie.schema.recipe.recipe_category import TagSave
|
||||||
|
from mealie.services import urls
|
||||||
|
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||||
|
from mealie.services.event_bus_service.message_types import EventTypes
|
||||||
|
|
||||||
router = APIRouter(prefix="/tags", tags=["Organizer: Tags"])
|
router = APIRouter(prefix="/tags", tags=["Organizer: Tags"])
|
||||||
|
|
||||||
|
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class TagController(BaseUserController):
|
class TagController(BaseUserController):
|
||||||
|
|
||||||
|
event_bus: EventBusService = Depends(EventBusService)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def repo(self):
|
def repo(self):
|
||||||
return self.repos.tags.by_group(self.group_id)
|
return self.repos.tags.by_group(self.group_id)
|
||||||
@ -42,13 +48,35 @@ class TagController(BaseUserController):
|
|||||||
def create_one(self, tag: TagIn):
|
def create_one(self, tag: TagIn):
|
||||||
"""Creates a Tag in the database"""
|
"""Creates a Tag in the database"""
|
||||||
save_data = mapper.cast(tag, TagSave, group_id=self.group_id)
|
save_data = mapper.cast(tag, TagSave, group_id=self.group_id)
|
||||||
return self.repo.create(save_data)
|
data = self.repo.create(save_data)
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.tag_created,
|
||||||
|
msg=self.t(
|
||||||
|
"notifications.generic-created-with-url",
|
||||||
|
name=data.name,
|
||||||
|
url=urls.tag_url(data.slug, self.deps.settings.BASE_URL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
@router.put("/{item_id}", response_model=RecipeTagResponse)
|
@router.put("/{item_id}", response_model=RecipeTagResponse)
|
||||||
def update_one(self, item_id: UUID4, new_tag: TagIn):
|
def update_one(self, item_id: UUID4, new_tag: TagIn):
|
||||||
"""Updates an existing Tag in the database"""
|
"""Updates an existing Tag in the database"""
|
||||||
save_data = mapper.cast(new_tag, TagSave, group_id=self.group_id)
|
save_data = mapper.cast(new_tag, TagSave, group_id=self.group_id)
|
||||||
return self.repo.update(item_id, save_data)
|
data = self.repo.update(item_id, save_data)
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.tag_updated,
|
||||||
|
msg=self.t(
|
||||||
|
"notifications.generic-updated-with-url",
|
||||||
|
name=data.name,
|
||||||
|
url=urls.tag_url(data.slug, self.deps.settings.BASE_URL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
@router.delete("/{item_id}")
|
@router.delete("/{item_id}")
|
||||||
def delete_recipe_tag(self, item_id: UUID4):
|
def delete_recipe_tag(self, item_id: UUID4):
|
||||||
@ -57,10 +85,17 @@ class TagController(BaseUserController):
|
|||||||
from any recipes that contain it"""
|
from any recipes that contain it"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.repo.delete(item_id)
|
data = self.repo.delete(item_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST) from e
|
raise HTTPException(status.HTTP_400_BAD_REQUEST) from e
|
||||||
|
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.tag_deleted,
|
||||||
|
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/slug/{tag_slug}", response_model=RecipeTagResponse)
|
@router.get("/slug/{tag_slug}", response_model=RecipeTagResponse)
|
||||||
async def get_one_by_slug(self, tag_slug: str):
|
async def get_one_by_slug(self, tag_slug: str):
|
||||||
return self.repo.get_one(tag_slug, "slug", override_schema=RecipeTagResponse)
|
return self.repo.get_one(tag_slug, "slug", override_schema=RecipeTagResponse)
|
||||||
|
@ -29,6 +29,9 @@ from mealie.schema.recipe.recipe_asset import RecipeAsset
|
|||||||
from mealie.schema.recipe.recipe_scraper import ScrapeRecipeTest
|
from mealie.schema.recipe.recipe_scraper import ScrapeRecipeTest
|
||||||
from mealie.schema.response.responses import ErrorResponse
|
from mealie.schema.response.responses import ErrorResponse
|
||||||
from mealie.schema.server.tasks import ServerTaskNames
|
from mealie.schema.server.tasks import ServerTaskNames
|
||||||
|
from mealie.services import urls
|
||||||
|
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||||
|
from mealie.services.event_bus_service.message_types import EventTypes
|
||||||
from mealie.services.recipe.recipe_data_service import RecipeDataService
|
from mealie.services.recipe.recipe_data_service import RecipeDataService
|
||||||
from mealie.services.recipe.recipe_service import RecipeService
|
from mealie.services.recipe.recipe_service import RecipeService
|
||||||
from mealie.services.recipe.template_service import TemplateService
|
from mealie.services.recipe.template_service import TemplateService
|
||||||
@ -120,6 +123,8 @@ router = UserAPIRouter(prefix="/recipes", tags=["Recipe: CRUD"])
|
|||||||
|
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class RecipeController(BaseRecipeController):
|
class RecipeController(BaseRecipeController):
|
||||||
|
event_bus: EventBusService = Depends(EventBusService)
|
||||||
|
|
||||||
def handle_exceptions(self, ex: Exception) -> None:
|
def handle_exceptions(self, ex: Exception) -> None:
|
||||||
match type(ex):
|
match type(ex):
|
||||||
case exceptions.PermissionDenied:
|
case exceptions.PermissionDenied:
|
||||||
@ -152,7 +157,20 @@ class RecipeController(BaseRecipeController):
|
|||||||
|
|
||||||
recipe.tags = extras.use_tags(ctx) # type: ignore
|
recipe.tags = extras.use_tags(ctx) # type: ignore
|
||||||
|
|
||||||
return self.service.create_one(recipe).slug
|
new_recipe = self.service.create_one(recipe)
|
||||||
|
|
||||||
|
if new_recipe:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.recipe_created,
|
||||||
|
msg=self.t(
|
||||||
|
"notifications.generic-created-with-url",
|
||||||
|
name=new_recipe.name,
|
||||||
|
url=urls.recipe_url(new_recipe.slug, self.deps.settings.BASE_URL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return new_recipe.slug
|
||||||
|
|
||||||
@router.post("/create-url/bulk", status_code=202)
|
@router.post("/create-url/bulk", status_code=202)
|
||||||
def parse_recipe_url_bulk(self, bulk: CreateRecipeByUrlBulk, bg_tasks: BackgroundTasks):
|
def parse_recipe_url_bulk(self, bulk: CreateRecipeByUrlBulk, bg_tasks: BackgroundTasks):
|
||||||
@ -249,6 +267,17 @@ class RecipeController(BaseRecipeController):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.handle_exceptions(e)
|
self.handle_exceptions(e)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.recipe_updated,
|
||||||
|
msg=self.t(
|
||||||
|
"notifications.generic-updated-with-url",
|
||||||
|
name=data.name,
|
||||||
|
url=urls.recipe_url(data.slug, self.deps.settings.BASE_URL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@router.patch("/{slug}")
|
@router.patch("/{slug}")
|
||||||
@ -264,10 +293,19 @@ class RecipeController(BaseRecipeController):
|
|||||||
def delete_one(self, slug: str):
|
def delete_one(self, slug: str):
|
||||||
"""Deletes a recipe by slug"""
|
"""Deletes a recipe by slug"""
|
||||||
try:
|
try:
|
||||||
return self.service.delete_one(slug)
|
data = self.service.delete_one(slug)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.handle_exceptions(e)
|
self.handle_exceptions(e)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
self.event_bus.dispatch(
|
||||||
|
self.deps.acting_user.group_id,
|
||||||
|
EventTypes.recipe_deleted,
|
||||||
|
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
# ==================================================================================================================
|
# ==================================================================================================================
|
||||||
# Image and Assets
|
# Image and Assets
|
||||||
|
|
||||||
|
@ -19,8 +19,14 @@ class RegistrationController(BasePublicController):
|
|||||||
|
|
||||||
if not settings.ALLOW_SIGNUP and data.group_token is None or data.group_token == "":
|
if not settings.ALLOW_SIGNUP and data.group_token is None or data.group_token == "":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ErrorResponse.respond("User Registration is Disabled")
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail=ErrorResponse.respond("User Registration is Disabled"),
|
||||||
|
)
|
||||||
|
|
||||||
|
registration_service = RegistrationService(
|
||||||
|
self.deps.logger,
|
||||||
|
get_repositories(self.deps.session),
|
||||||
|
self.translator,
|
||||||
)
|
)
|
||||||
|
|
||||||
registration_service = RegistrationService(self.deps.logger, get_repositories(self.deps.session), self.deps.t)
|
|
||||||
return registration_service.register_user(data)
|
return registration_service.register_user(data)
|
||||||
|
1
mealie/services/urls/__init__.py
Normal file
1
mealie/services/urls/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .url_constructors import *
|
36
mealie/services/urls/url_constructors.py
Normal file
36
mealie/services/urls/url_constructors.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from pydantic import UUID4
|
||||||
|
|
||||||
|
from mealie.core.config import get_app_settings
|
||||||
|
|
||||||
|
|
||||||
|
def _base_or(base_url: str | None) -> str:
|
||||||
|
if base_url is None:
|
||||||
|
settings = get_app_settings()
|
||||||
|
return settings.BASE_URL
|
||||||
|
|
||||||
|
return base_url
|
||||||
|
|
||||||
|
|
||||||
|
def recipe_url(recipe_slug: str, base_url: str | None) -> str:
|
||||||
|
base = _base_or(base_url)
|
||||||
|
return f"{base}/recipe/{recipe_slug}"
|
||||||
|
|
||||||
|
|
||||||
|
def shopping_list_url(shopping_list_id: UUID4 | str, base_url: str | None) -> str:
|
||||||
|
base = _base_or(base_url)
|
||||||
|
return f"{base}/shopping-list/{shopping_list_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def tag_url(tag_slug: str, base_url: str | None) -> str:
|
||||||
|
base = _base_or(base_url)
|
||||||
|
return f"{base}/recipes/tags/{tag_slug}"
|
||||||
|
|
||||||
|
|
||||||
|
def category_url(category_slug: str, base_url: str | None) -> str:
|
||||||
|
base = _base_or(base_url)
|
||||||
|
return f"{base}/recipes/categories/{category_slug}"
|
||||||
|
|
||||||
|
|
||||||
|
def tool_url(tool_slug: str, base_url: str | None) -> str:
|
||||||
|
base = _base_or(base_url)
|
||||||
|
return f"{base}/recipes/tool/{tool_slug}"
|
@ -17,10 +17,10 @@ class RegistrationService:
|
|||||||
logger: Logger
|
logger: Logger
|
||||||
repos: AllRepositories
|
repos: AllRepositories
|
||||||
|
|
||||||
def __init__(self, logger: Logger, db: AllRepositories, t: Translator):
|
def __init__(self, logger: Logger, db: AllRepositories, translator: Translator):
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.repos = db
|
self.repos = db
|
||||||
self.t = t
|
self.t = translator.t
|
||||||
|
|
||||||
def _create_new_user(self, group: GroupInDB, new_group: bool) -> PrivateUser:
|
def _create_new_user(self, group: GroupInDB, new_group: bool) -> PrivateUser:
|
||||||
new_user = UserIn(
|
new_user = UserIn(
|
||||||
@ -58,9 +58,9 @@ class RegistrationService:
|
|||||||
self.registration = registration
|
self.registration = registration
|
||||||
|
|
||||||
if self.repos.users.get_by_username(registration.username):
|
if self.repos.users.get_by_username(registration.username):
|
||||||
raise HTTPException(status.HTTP_409_CONFLICT, {"message": self.t.t("exceptions.username-conflict-error")})
|
raise HTTPException(status.HTTP_409_CONFLICT, {"message": self.t("exceptions.username-conflict-error")})
|
||||||
elif self.repos.users.get(registration.email, "email"):
|
elif self.repos.users.get(registration.email, "email"):
|
||||||
raise HTTPException(status.HTTP_409_CONFLICT, {"message": self.t.t("exceptions.email-conflict-error")})
|
raise HTTPException(status.HTTP_409_CONFLICT, {"message": self.t("exceptions.email-conflict-error")})
|
||||||
|
|
||||||
self.logger.info(f"Registering user {registration.username}")
|
self.logger.info(f"Registering user {registration.username}")
|
||||||
token_entry = None
|
token_entry = None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user