feat: Add Households to Mealie (#3970)

This commit is contained in:
Michael Genson
2024-08-22 10:14:32 -05:00
committed by GitHub
parent 0c29cef17d
commit eb170cc7e5
315 changed files with 6975 additions and 3577 deletions

View File

@@ -7,6 +7,7 @@ from . import (
comments,
explore,
groups,
households,
organizers,
parser,
recipe,
@@ -21,6 +22,7 @@ router = APIRouter(prefix="/api")
router.include_router(app.router)
router.include_router(auth.router)
router.include_router(users.router)
router.include_router(households.router)
router.include_router(groups.router)
router.include_router(recipe.router)
router.include_router(organizers.router)

View File

@@ -6,7 +6,12 @@ from pydantic import UUID4, ConfigDict
from sqlalchemy.orm import Session
from mealie.core.config import get_app_dirs, get_app_settings
from mealie.core.dependencies.dependencies import get_admin_user, get_current_user, get_integration_id, get_public_group
from mealie.core.dependencies.dependencies import (
get_admin_user,
get_current_user,
get_integration_id,
get_public_group,
)
from mealie.core.exceptions import mealie_registered_exceptions
from mealie.core.root_logger import get_logger
from mealie.core.settings.directories import AppDirectories
@@ -14,8 +19,10 @@ from mealie.core.settings.settings import AppSettings
from mealie.db.db_setup import generate_session
from mealie.lang import local_provider
from mealie.lang.providers import Translator
from mealie.repos.all_repositories import AllRepositories
from mealie.repos._utils import NOT_SET, NotSet
from mealie.repos.all_repositories import AllRepositories, get_repositories
from mealie.routes._base.checks import OperationChecks
from mealie.schema.household.household import HouseholdInDB
from mealie.schema.user.user import GroupInDB, PrivateUser
from mealie.services.event_bus_service.event_bus_service import EventBusService
from mealie.services.event_bus_service.event_types import EventDocumentDataBase, EventTypes
@@ -37,7 +44,7 @@ class _BaseController(ABC): # noqa: B024
@property
def repos(self):
if not self._repos:
self._repos = AllRepositories(self.session)
self._repos = AllRepositories(self.session, group_id=self.group_id, household_id=self.household_id)
return self._repos
@property
@@ -58,6 +65,14 @@ class _BaseController(ABC): # noqa: B024
self._folders = get_app_dirs()
return self._folders
@property
def group_id(self) -> UUID4 | None | NotSet:
return NOT_SET
@property
def household_id(self) -> UUID4 | None | NotSet:
return NOT_SET
model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -71,15 +86,40 @@ class BasePublicController(_BaseController):
...
class BasePublicExploreController(BasePublicController):
class BasePublicGroupExploreController(BasePublicController):
"""
This is a public class for all User restricted controllers in the API.
It includes the common SharedDependencies and some common methods used
by all Admin controllers.
Base class for all controllers that are public and explore group data.
"""
group: GroupInDB = Depends(get_public_group)
@property
def group_id(self) -> UUID4 | None | NotSet:
return self.group.id
def get_explore_url_path(self, endpoint: str) -> str:
if endpoint.startswith("/"):
endpoint = endpoint[1:]
return f"/explore/groups/{self.group.slug}/{endpoint}"
class BasePublicHouseholdExploreController(BasePublicGroupExploreController):
"""
Base class for all controllers that are public and explore household data.
"""
@property
def cross_household_repos(self):
"""
Household-level repos with no household filter. Public controllers don't have access to a household identifier;
instead, they return all public data, filtered by the household preferences.
When using this repo, the caller should filter by household preferences, e.g.:
`household.preferences.privateHousehold = FALSE`
"""
return get_repositories(self.session, group_id=self.group_id, household_id=None)
class BaseUserController(_BaseController):
"""
@@ -105,10 +145,18 @@ class BaseUserController(_BaseController):
def group_id(self) -> UUID4:
return self.user.group_id
@property
def household_id(self) -> UUID4:
return self.user.household_id
@property
def group(self) -> GroupInDB:
return self.repos.groups.get_one(self.group_id)
@property
def household(self) -> HouseholdInDB:
return self.repos.households.get_one(self.household_id)
@property
def checks(self) -> OperationChecks:
if not self._checks:
@@ -125,6 +173,13 @@ class BaseAdminController(BaseUserController):
user: PrivateUser = Depends(get_admin_user)
@property
def repos(self):
if not self._repos:
# Admins have access to all groups and households, so we don't want to filter by group_id or household_id
self._repos = AllRepositories(self.session, group_id=None, household_id=None)
return self._repos
class BaseCrudController(BaseUserController):
"""
@@ -133,10 +188,18 @@ class BaseCrudController(BaseUserController):
event_bus: EventBusService = Depends(EventBusService.as_dependency)
def publish_event(self, event_type: EventTypes, document_data: EventDocumentDataBase, message: str = "") -> None:
def publish_event(
self,
event_type: EventTypes,
document_data: EventDocumentDataBase,
group_id: UUID4,
household_id: UUID4 | None,
message: str = "",
) -> None:
self.event_bus.dispatch(
integration_id=self.integration_id,
group_id=self.group_id,
group_id=group_id,
household_id=household_id,
event_type=event_type,
document_data=document_data,
message=message,

View File

@@ -35,7 +35,7 @@ class MealieCrudRoute(APIRoute):
response = await original_route_handler(request)
response_body = json.loads(response.body)
if isinstance(response_body, dict):
if last_modified := response_body.get("updateAt"):
if last_modified := response_body.get("updatedAt"):
response.headers["last-modified"] = last_modified
# Force no-cache for all responses to prevent browser from caching API calls

View File

@@ -2,11 +2,11 @@ from mealie.routes._base.routers import AdminAPIRouter
from . import (
admin_about,
admin_analytics,
admin_backups,
admin_email,
admin_maintenance,
admin_management_groups,
admin_management_households,
admin_management_users,
)
@@ -14,8 +14,8 @@ router = AdminAPIRouter(prefix="/admin")
router.include_router(admin_about.router, tags=["Admin: About"])
router.include_router(admin_management_users.router, tags=["Admin: Manage Users"])
router.include_router(admin_management_households.router, tags=["Admin: Manage Households"])
router.include_router(admin_management_groups.router, tags=["Admin: Manage Groups"])
router.include_router(admin_email.router, tags=["Admin: Email"])
router.include_router(admin_backups.router, tags=["Admin: Backups"])
router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"])
router.include_router(admin_analytics.router, tags=["Admin: Analytics"])

View File

@@ -27,6 +27,7 @@ class AdminAboutController(BaseAdminController):
db_type=settings.DB_ENGINE,
db_url=settings.DB_URL_PUBLIC,
default_group=settings.DEFAULT_GROUP,
default_household=settings.DEFAULT_HOUSEHOLD,
allow_signup=settings.ALLOW_SIGNUP,
build_id=settings.GIT_COMMIT_HASH,
recipe_scraper_version=recipe_scraper_version.__version__,
@@ -44,6 +45,7 @@ class AdminAboutController(BaseAdminController):
uncategorized_recipes=self.repos.recipes.count_uncategorized(), # type: ignore
untagged_recipes=self.repos.recipes.count_untagged(), # type: ignore
total_users=self.repos.users.count_all(),
total_households=self.repos.households.count_all(),
total_groups=self.repos.groups.count_all(),
)

View File

@@ -1,20 +0,0 @@
from functools import cached_property
from fastapi import APIRouter
from mealie.routes._base import BaseAdminController, controller
from mealie.schema.analytics.analytics import MealieAnalytics
from mealie.services.analytics.service_analytics import AnalyticsService
router = APIRouter(prefix="/analytics", include_in_schema=False) # deprecated - use statistics route instead
@controller(router)
class AdminAboutController(BaseAdminController):
@cached_property
def service(self) -> AnalyticsService:
return AnalyticsService(self.repos)
@router.get("", response_model=MealieAnalytics)
def get_analytics(self):
return self.service.calculate_analytics()

View File

@@ -13,11 +13,11 @@ from mealie.services.group_services.group_service import GroupService
from .._base import BaseAdminController, controller
from .._base.mixins import HttpRepo
router = APIRouter(prefix="/groups", tags=["Admin: Groups"])
router = APIRouter(prefix="/groups")
@controller(router)
class AdminUserManagementRoutes(BaseAdminController):
class AdminGroupManagementRoutes(BaseAdminController):
@cached_property
def repo(self):
if not self.user:
@@ -64,6 +64,7 @@ class AdminUserManagementRoutes(BaseAdminController):
group.preferences = self.repos.group_preferences.update(item_id, preferences)
if data.name not in ["", group.name]:
# only update the group if the name changed, since the name is the only field that can be updated
group.name = data.name
group = self.repo.update(item_id, group)

View File

@@ -0,0 +1,91 @@
from functools import cached_property
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import UUID4
from sqlalchemy import func, select
from mealie.db.models.users.users import User
from mealie.schema.household.household import (
HouseholdCreate,
HouseholdInDB,
HouseholdPagination,
UpdateHouseholdAdmin,
)
from mealie.schema.mapper import mapper
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.response.responses import ErrorResponse
from mealie.services.household_services.household_service import HouseholdService
from .._base import BaseAdminController, controller
from .._base.mixins import HttpRepo
router = APIRouter(prefix="/households")
@controller(router)
class AdminHouseholdManagementRoutes(BaseAdminController):
@cached_property
def repo(self):
if not self.user:
raise Exception("No user is logged in.")
return self.repos.households
# =======================================================================
# CRUD Operations
@property
def mixins(self):
return HttpRepo[HouseholdCreate, HouseholdInDB, UpdateHouseholdAdmin](
self.repo,
self.logger,
self.registered_exceptions,
)
@router.get("", response_model=HouseholdPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
response = self.repo.page_all(
pagination=q,
override=HouseholdInDB,
)
response.set_pagination_guides(router.url_path_for("get_all"), q.model_dump())
return response
@router.post("", response_model=HouseholdInDB, status_code=status.HTTP_201_CREATED)
def create_one(self, data: HouseholdCreate):
return HouseholdService.create_household(self.repos, data)
@router.get("/{item_id}", response_model=HouseholdInDB)
def get_one(self, item_id: UUID4):
return self.mixins.get_one(item_id)
@router.put("/{item_id}", response_model=HouseholdInDB)
def update_one(self, item_id: UUID4, data: UpdateHouseholdAdmin):
household = self.repo.get_one(item_id)
if data.preferences:
preferences = self.repos.household_preferences.get_one(value=item_id, key="household_id")
preferences = mapper(data.preferences, preferences)
household.preferences = self.repos.household_preferences.update(item_id, preferences)
if data.name not in ["", household.name]:
# only update the household if the name changed, since the name is the only field that can be updated
household.name = data.name
household = self.repo.update(item_id, household)
return household
@router.delete("/{item_id}", response_model=HouseholdInDB)
def delete_one(self, item_id: UUID4):
item = self.repo.get_one(item_id)
if item:
stmt = select(func.count(User.id)).filter_by(group_id=item.group_id, household_id=item_id)
user_count = self.session.scalar(stmt)
if user_count:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ErrorResponse.respond(message="Cannot delete household with users"),
)
return self.mixins.delete_one(item_id)

View File

@@ -14,7 +14,7 @@ from mealie.schema.user.user_passwords import ForgotPassword, PasswordResetToken
from mealie.services.user_services.password_reset_service import PasswordResetService
from mealie.services.user_services.user_service import UserService
router = APIRouter(prefix="/users", tags=["Admin: Users"])
router = APIRouter(prefix="/users")
@controller(router)

View File

@@ -16,12 +16,20 @@ def get_app_info(session: Session = Depends(generate_session)):
"""Get general application information"""
settings = get_app_settings()
repos = get_repositories(session)
default_group = repos.groups.get_by_name(settings.DEFAULT_GROUP)
public_repos = get_repositories(session, group_id=None, household_id=None)
default_group_slug: str | None = None
default_household_slug: str | None = None
default_group = public_repos.groups.get_by_name(settings.DEFAULT_GROUP)
if default_group and default_group.preferences and not default_group.preferences.private_group:
default_group_slug = default_group.slug
else:
default_group_slug = None
if default_group and default_group_slug:
group_repos = get_repositories(session, group_id=default_group.id, household_id=None)
default_household = group_repos.households.get_by_name(settings.DEFAULT_HOUSEHOLD)
if default_household and default_household.preferences and not default_household.preferences.private_household:
default_household_slug = default_household.slug
return AppInfo(
version=APP_VERSION,
@@ -29,6 +37,7 @@ def get_app_info(session: Session = Depends(generate_session)):
production=settings.PRODUCTION,
allow_signup=settings.ALLOW_SIGNUP,
default_group_slug=default_group_slug,
default_household_slug=default_household_slug,
enable_oidc=settings.OIDC_READY,
oidc_redirect=settings.OIDC_AUTO_REDIRECT,
oidc_provider_name=settings.OIDC_PROVIDER_NAME,

View File

@@ -7,13 +7,14 @@ from . import (
controller_public_recipes,
)
prefix = "/explore"
router = APIRouter(prefix="/explore/groups/{group_slug}")
router = APIRouter()
# group
router.include_router(controller_public_foods.router, tags=["Explore: Foods"])
router.include_router(controller_public_organizers.categories_router, tags=["Explore: Categories"])
router.include_router(controller_public_organizers.tags_router, tags=["Explore: Tags"])
router.include_router(controller_public_organizers.tools_router, tags=["Explore: Tools"])
router.include_router(controller_public_cookbooks.router, prefix=prefix, tags=["Explore: Cookbooks"])
router.include_router(controller_public_foods.router, prefix=prefix, tags=["Explore: Foods"])
router.include_router(controller_public_organizers.categories_router, prefix=prefix, tags=["Explore: Categories"])
router.include_router(controller_public_organizers.tags_router, prefix=prefix, tags=["Explore: Tags"])
router.include_router(controller_public_organizers.tools_router, prefix=prefix, tags=["Explore: Tools"])
router.include_router(controller_public_recipes.router, prefix=prefix, tags=["Explore: Recipes"])
# household
router.include_router(controller_public_cookbooks.router, tags=["Explore: Cookbooks"])
router.include_router(controller_public_recipes.router, tags=["Explore: Recipes"])

View File

@@ -3,46 +3,46 @@ from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException
from pydantic import UUID4
from mealie.repos.all_repositories import get_repositories
from mealie.routes._base import controller
from mealie.routes._base.base_controllers import BasePublicExploreController
from mealie.routes._base.base_controllers import BasePublicHouseholdExploreController
from mealie.schema.cookbook.cookbook import ReadCookBook, RecipeCookBook
from mealie.schema.make_dependable import make_dependable
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
router = APIRouter(prefix="/cookbooks/{group_slug}")
router = APIRouter(prefix="/cookbooks")
@controller(router)
class PublicCookbooksController(BasePublicExploreController):
class PublicCookbooksController(BasePublicHouseholdExploreController):
@property
def cookbooks(self):
return self.repos.cookbooks.by_group(self.group.id)
@property
def recipes(self):
return self.repos.recipes.by_group(self.group.id)
def cross_household_cookbooks(self):
return self.cross_household_repos.cookbooks
@router.get("", response_model=PaginationBase[ReadCookBook])
def get_all(
self, q: PaginationQuery = Depends(make_dependable(PaginationQuery)), search: str | None = None
self,
q: PaginationQuery = Depends(make_dependable(PaginationQuery)),
search: str | None = None,
) -> PaginationBase[ReadCookBook]:
public_filter = "public = TRUE"
public_filter = "(household.preferences.privateHousehold = FALSE AND public = TRUE)"
if q.query_filter:
q.query_filter = f"({q.query_filter}) AND {public_filter}"
else:
q.query_filter = public_filter
response = self.cookbooks.page_all(
response = self.cross_household_cookbooks.page_all(
pagination=q,
override=ReadCookBook,
search=search,
)
response.set_pagination_guides(router.url_path_for("get_all", group_slug=self.group.slug), q.model_dump())
response.set_pagination_guides(self.get_explore_url_path(router.url_path_for("get_all")), q.model_dump())
return response
@router.get("/{item_id}", response_model=RecipeCookBook)
def get_one(self, item_id: UUID4 | str) -> RecipeCookBook:
NOT_FOUND_EXCEPTION = HTTPException(404, "cookbook not found")
if isinstance(item_id, UUID):
match_attr = "id"
else:
@@ -51,12 +51,24 @@ class PublicCookbooksController(BasePublicExploreController):
match_attr = "id"
except ValueError:
match_attr = "slug"
cookbook = self.cookbooks.get_one(item_id, match_attr)
cookbook = self.cross_household_cookbooks.get_one(item_id, match_attr)
if not cookbook or not cookbook.public:
raise HTTPException(404, "cookbook not found")
raise NOT_FOUND_EXCEPTION
household = self.repos.households.get_one(cookbook.household_id)
if not household or household.preferences.private_household:
raise NOT_FOUND_EXCEPTION
recipes = self.recipes.page_all(
PaginationQuery(page=1, per_page=-1, query_filter="settings.public = TRUE"), cookbook=cookbook
# limit recipes to only the household the cookbook belongs to
recipes_repo = get_repositories(
self.session, group_id=self.group_id, household_id=cookbook.household_id
).recipes
recipes = recipes_repo.page_all(
PaginationQuery(
page=1,
per_page=-1,
query_filter="settings.public = TRUE",
),
cookbook=cookbook,
)
return cookbook.cast(RecipeCookBook, recipes=recipes.items)

View File

@@ -2,23 +2,25 @@ from fastapi import APIRouter, Depends, HTTPException
from pydantic import UUID4
from mealie.routes._base import controller
from mealie.routes._base.base_controllers import BasePublicExploreController
from mealie.routes._base.base_controllers import BasePublicGroupExploreController
from mealie.schema.make_dependable import make_dependable
from mealie.schema.recipe.recipe_ingredient import IngredientFood
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
router = APIRouter(prefix="/foods/{group_slug}")
router = APIRouter(prefix="/foods")
@controller(router)
class PublicFoodsController(BasePublicExploreController):
class PublicFoodsController(BasePublicGroupExploreController):
@property
def ingredient_foods(self):
return self.repos.ingredient_foods.by_group(self.group.id)
return self.repos.ingredient_foods
@router.get("", response_model=PaginationBase[IngredientFood])
def get_all(
self, q: PaginationQuery = Depends(make_dependable(PaginationQuery)), search: str | None = None
self,
q: PaginationQuery = Depends(make_dependable(PaginationQuery)),
search: str | None = None,
) -> PaginationBase[IngredientFood]:
response = self.ingredient_foods.page_all(
pagination=q,
@@ -26,7 +28,7 @@ class PublicFoodsController(BasePublicExploreController):
search=search,
)
response.set_pagination_guides(router.url_path_for("get_all", group_slug=self.group.slug), q.model_dump())
response.set_pagination_guides(self.get_explore_url_path(router.url_path_for("get_all")), q.model_dump())
return response
@router.get("/{item_id}", response_model=IngredientFood)

View File

@@ -2,28 +2,30 @@ from fastapi import APIRouter, Depends, HTTPException
from pydantic import UUID4
from mealie.routes._base import controller
from mealie.routes._base.base_controllers import BasePublicExploreController
from mealie.routes._base.base_controllers import BasePublicGroupExploreController
from mealie.schema.make_dependable import make_dependable
from mealie.schema.recipe.recipe import RecipeCategory, RecipeTag, RecipeTool
from mealie.schema.recipe.recipe_category import CategoryOut, TagOut
from mealie.schema.recipe.recipe_tool import RecipeToolOut
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
base_prefix = "/organizers/{group_slug}"
base_prefix = "/organizers"
categories_router = APIRouter(prefix=f"{base_prefix}/categories")
tags_router = APIRouter(prefix=f"{base_prefix}/tags")
tools_router = APIRouter(prefix=f"{base_prefix}/tools")
@controller(categories_router)
class PublicCategoriesController(BasePublicExploreController):
class PublicCategoriesController(BasePublicGroupExploreController):
@property
def categories(self):
return self.repos.categories.by_group(self.group.id)
return self.repos.categories
@categories_router.get("", response_model=PaginationBase[RecipeCategory])
def get_all(
self, q: PaginationQuery = Depends(make_dependable(PaginationQuery)), search: str | None = None
self,
q: PaginationQuery = Depends(make_dependable(PaginationQuery)),
search: str | None = None,
) -> PaginationBase[RecipeCategory]:
response = self.categories.page_all(
pagination=q,
@@ -31,9 +33,7 @@ class PublicCategoriesController(BasePublicExploreController):
search=search,
)
response.set_pagination_guides(
categories_router.url_path_for("get_all", group_slug=self.group.slug), q.model_dump()
)
response.set_pagination_guides(self.get_explore_url_path(tags_router.url_path_for("get_all")), q.model_dump())
return response
@categories_router.get("/{item_id}", response_model=CategoryOut)
@@ -46,14 +46,16 @@ class PublicCategoriesController(BasePublicExploreController):
@controller(tags_router)
class PublicTagsController(BasePublicExploreController):
class PublicTagsController(BasePublicGroupExploreController):
@property
def tags(self):
return self.repos.tags.by_group(self.group.id)
return self.repos.tags
@tags_router.get("", response_model=PaginationBase[RecipeTag])
def get_all(
self, q: PaginationQuery = Depends(make_dependable(PaginationQuery)), search: str | None = None
self,
q: PaginationQuery = Depends(make_dependable(PaginationQuery)),
search: str | None = None,
) -> PaginationBase[RecipeTag]:
response = self.tags.page_all(
pagination=q,
@@ -61,7 +63,7 @@ class PublicTagsController(BasePublicExploreController):
search=search,
)
response.set_pagination_guides(tags_router.url_path_for("get_all", group_slug=self.group.slug), q.model_dump())
response.set_pagination_guides(self.get_explore_url_path(tags_router.url_path_for("get_all")), q.model_dump())
return response
@tags_router.get("/{item_id}", response_model=TagOut)
@@ -74,14 +76,16 @@ class PublicTagsController(BasePublicExploreController):
@controller(tools_router)
class PublicToolsController(BasePublicExploreController):
class PublicToolsController(BasePublicGroupExploreController):
@property
def tools(self):
return self.repos.tools.by_group(self.group.id)
return self.repos.tools
@tools_router.get("", response_model=PaginationBase[RecipeTool])
def get_all(
self, q: PaginationQuery = Depends(make_dependable(PaginationQuery)), search: str | None = None
self,
q: PaginationQuery = Depends(make_dependable(PaginationQuery)),
search: str | None = None,
) -> PaginationBase[RecipeTool]:
response = self.tools.page_all(
pagination=q,
@@ -89,7 +93,7 @@ class PublicToolsController(BasePublicExploreController):
search=search,
)
response.set_pagination_guides(tools_router.url_path_for("get_all", group_slug=self.group.slug), q.model_dump())
response.set_pagination_guides(self.get_explore_url_path(tools_router.url_path_for("get_all")), q.model_dump())
return response
@tools_router.get("/{item_id}", response_model=RecipeToolOut)

View File

@@ -4,8 +4,9 @@ import orjson
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import UUID4
from mealie.repos.all_repositories import get_repositories
from mealie.routes._base import controller
from mealie.routes._base.base_controllers import BasePublicExploreController
from mealie.routes._base.base_controllers import BasePublicHouseholdExploreController
from mealie.routes.recipe.recipe_crud_routes import JSONBytes
from mealie.schema.cookbook.cookbook import ReadCookBook
from mealie.schema.make_dependable import make_dependable
@@ -13,18 +14,18 @@ from mealie.schema.recipe import Recipe
from mealie.schema.recipe.recipe import RecipeSummary
from mealie.schema.response.pagination import PaginationBase, PaginationQuery, RecipeSearchQuery
router = APIRouter(prefix="/recipes/{group_slug}")
router = APIRouter(prefix="/recipes")
@controller(router)
class PublicRecipesController(BasePublicExploreController):
class PublicRecipesController(BasePublicHouseholdExploreController):
@property
def cookbooks(self):
return self.repos.cookbooks.by_group(self.group.id)
def cross_household_cookbooks(self):
return self.cross_household_repos.cookbooks
@property
def recipes(self):
return self.repos.recipes.by_group(self.group.id)
def cross_household_recipes(self):
return self.cross_household_repos.recipes
@router.get("", response_model=PaginationBase[RecipeSummary])
def get_all(
@@ -38,7 +39,9 @@ class PublicRecipesController(BasePublicExploreController):
foods: list[UUID4 | str] | None = Query(None),
) -> PaginationBase[RecipeSummary]:
cookbook_data: ReadCookBook | None = None
recipes_repo = self.cross_household_recipes
if search_query.cookbook:
COOKBOOK_NOT_FOUND_EXCEPTION = HTTPException(404, "cookbook not found")
if isinstance(search_query.cookbook, UUID):
cb_match_attr = "id"
else:
@@ -47,18 +50,26 @@ class PublicRecipesController(BasePublicExploreController):
cb_match_attr = "id"
except ValueError:
cb_match_attr = "slug"
cookbook_data = self.cookbooks.get_one(search_query.cookbook, cb_match_attr)
cookbook_data = self.cross_household_cookbooks.get_one(search_query.cookbook, cb_match_attr)
if cookbook_data is None or not cookbook_data.public:
raise HTTPException(status_code=404, detail="cookbook not found")
raise COOKBOOK_NOT_FOUND_EXCEPTION
household = self.repos.households.get_one(cookbook_data.household_id)
if not household or household.preferences.private_household:
raise COOKBOOK_NOT_FOUND_EXCEPTION
public_filter = "settings.public = TRUE"
# filter recipes by the cookbook's household
recipes_repo = get_repositories(
self.session, group_id=self.group_id, household_id=cookbook_data.household_id
).recipes
public_filter = "(household.preferences.privateHousehold = FALSE AND settings.public = TRUE)"
if q.query_filter:
q.query_filter = f"({q.query_filter}) AND {public_filter}"
else:
q.query_filter = public_filter
pagination_response = self.recipes.page_all(
pagination_response = recipes_repo.page_all(
pagination=q,
cookbook=cookbook_data,
categories=categories,
@@ -75,7 +86,7 @@ class PublicRecipesController(BasePublicExploreController):
# merge default pagination with the request's query params
query_params = q.model_dump() | {**request.query_params}
pagination_response.set_pagination_guides(
router.url_path_for("get_all", group_slug=self.group.slug),
self.get_explore_url_path(router.url_path_for("get_all")),
{k: v for k, v in query_params.items() if v is not None},
)
@@ -86,9 +97,13 @@ class PublicRecipesController(BasePublicExploreController):
@router.get("/{recipe_slug}", response_model=Recipe)
def get_recipe(self, recipe_slug: str) -> Recipe:
recipe = self.repos.recipes.by_group(self.group.id).get_one(recipe_slug)
RECIPE_NOT_FOUND_EXCEPTION = HTTPException(404, "recipe not found")
recipe = self.cross_household_recipes.get_one(recipe_slug)
if not recipe or not recipe.settings.public:
raise HTTPException(404, "recipe not found")
raise RECIPE_NOT_FOUND_EXCEPTION
household = self.repos.households.get_one(recipe.household_id)
if not household or household.preferences.private_household:
raise RECIPE_NOT_FOUND_EXCEPTION
return recipe

View File

@@ -1,36 +1,17 @@
from fastapi import APIRouter
from . import (
controller_cookbooks,
controller_group_notifications,
controller_group_recipe_actions,
controller_group_reports,
controller_group_self_service,
controller_invitations,
controller_labels,
controller_mealplan,
controller_mealplan_config,
controller_mealplan_rules,
controller_migrations,
controller_seeder,
controller_shopping_lists,
controller_webhooks,
)
router = APIRouter()
router.include_router(controller_group_self_service.router)
router.include_router(controller_mealplan_rules.router)
router.include_router(controller_mealplan_config.router)
router.include_router(controller_mealplan.router)
router.include_router(controller_cookbooks.router)
router.include_router(controller_webhooks.router)
router.include_router(controller_invitations.router)
router.include_router(controller_migrations.router)
router.include_router(controller_group_reports.router)
router.include_router(controller_shopping_lists.router)
router.include_router(controller_shopping_lists.item_router)
router.include_router(controller_labels.router)
router.include_router(controller_group_notifications.router)
router.include_router(controller_group_recipe_actions.router)
router.include_router(controller_seeder.router)

View File

@@ -17,7 +17,7 @@ router = APIRouter(prefix="/groups/reports", tags=["Groups: Reports"])
class GroupReportsController(BaseUserController):
@cached_property
def repo(self):
return self.repos.group_reports.by_group(self.user.group_id)
return self.repos.group_reports
def registered_exceptions(self, ex: type[Exception]) -> str:
return {

View File

@@ -1,14 +1,16 @@
from functools import cached_property
from fastapi import HTTPException, status
from fastapi import Query
from pydantic import UUID4
from mealie.routes._base.base_controllers import BaseUserController
from mealie.routes._base.controller import controller
from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.group.group_permissions import SetPermissions
from mealie.schema.group.group_preferences import ReadGroupPreferences, UpdateGroupPreferences
from mealie.schema.group.group_statistics import GroupStatistics, GroupStorage
from mealie.schema.user.user import GroupSummary, UserOut
from mealie.schema.group.group_statistics import GroupStorage
from mealie.schema.household.household import HouseholdSummary
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.user.user import GroupSummary, UserSummary
from mealie.services.group_services.group_service import GroupService
router = UserAPIRouter(prefix="/groups", tags=["Groups: Self Service"])
@@ -25,10 +27,20 @@ class GroupSelfServiceController(BaseUserController):
"""Returns the Group Data for the Current User"""
return self.group.cast(GroupSummary)
@router.get("/members", response_model=list[UserOut])
def get_group_members(self):
"""Returns the Group of user lists"""
return self.repos.users.multi_query(query_by={"group_id": self.group.id}, override_schema=UserOut)
@router.get("/members", response_model=list[UserSummary])
def get_group_members(self, household_id: UUID4 | None = Query(None, alias="householdId")):
"""Returns all users belonging to the current group, optionally filtered by household_id"""
query_filter = f"household_id={household_id}" if household_id else None
private_users = self.repos.users.page_all(PaginationQuery(page=1, per_page=-1, query_filter=query_filter)).items
return [user.cast(UserSummary) for user in private_users]
@router.get("/households", response_model=list[HouseholdSummary])
def get_group_households(self):
"""Returns all households belonging to the current group"""
households = self.repos.households.page_all(PaginationQuery(page=1, per_page=-1)).items
return [household.cast(HouseholdSummary) for household in households]
@router.get("/preferences", response_model=ReadGroupPreferences)
def get_group_preferences(self):
@@ -38,28 +50,6 @@ class GroupSelfServiceController(BaseUserController):
def update_group_preferences(self, new_pref: UpdateGroupPreferences):
return self.repos.group_preferences.update(self.group_id, new_pref)
@router.put("/permissions", response_model=UserOut)
def set_member_permissions(self, permissions: SetPermissions):
self.checks.can_manage()
target_user = self.repos.users.get_one(permissions.user_id)
if not target_user:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
if target_user.group_id != self.group_id:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="User is not a member of this group")
target_user.can_invite = permissions.can_invite
target_user.can_manage = permissions.can_manage
target_user.can_organize = permissions.can_organize
return self.repos.users.update(permissions.user_id, target_user)
@router.get("/statistics", response_model=GroupStatistics)
def get_statistics(self):
return self.service.calculate_statistics()
@router.get("/storage", response_model=GroupStorage)
def get_storage(self):
return self.service.calculate_group_storage()

View File

@@ -17,14 +17,14 @@ from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelPagination
from mealie.schema.response.pagination import PaginationQuery
from mealie.services.group_services.labels_service import MultiPurposeLabelService
router = APIRouter(prefix="/groups/labels", tags=["Group: Multi Purpose Labels"], route_class=MealieCrudRoute)
router = APIRouter(prefix="/groups/labels", tags=["Groups: Multi Purpose Labels"], route_class=MealieCrudRoute)
@controller(router)
class MultiPurposeLabelsController(BaseUserController):
@cached_property
def service(self):
return MultiPurposeLabelService(self.repos, self.group.id)
return MultiPurposeLabelService(self.repos)
@cached_property
def repo(self):

View File

@@ -1,26 +0,0 @@
from mealie.routes._base.base_controllers import BaseUserController
from mealie.routes._base.controller import controller
from mealie.routes._base.mixins import HttpRepo
from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.recipe.recipe_category import CategoryBase
from mealie.schema.user.user import GroupInDB
router = UserAPIRouter(prefix="/groups/categories", tags=["Groups: Mealplan Categories"])
@controller(router)
class GroupMealplanConfigController(BaseUserController):
@property
def mixins(self):
return HttpRepo[GroupInDB, GroupInDB, GroupInDB](self.repos.groups, self.logger)
@router.get("", response_model=list[CategoryBase])
def get_mealplan_categories(self):
data = self.mixins.get_one(self.user.group_id)
return data.categories
@router.put("", response_model=list[CategoryBase])
def update_mealplan_categories(self, new_categories: list[CategoryBase]):
data = self.mixins.get_one(self.user.group_id)
data.categories = new_categories
return self.mixins.update_one(data, data.id).categories

View File

@@ -21,7 +21,7 @@ from mealie.services.migrations import (
TandoorMigrator,
)
router = UserAPIRouter(prefix="/groups/migrations", tags=["Group: Migrations"])
router = UserAPIRouter(prefix="/groups/migrations", tags=["Groups: Migrations"])
@controller(router)
@@ -43,6 +43,7 @@ class GroupMigrationController(BaseUserController):
"db": self.repos,
"session": self.session,
"user_id": self.user.id,
"household_id": self.household_id,
"group_id": self.group_id,
"add_migration_tag": add_migration_tag,
"translator": self.translator,

View File

@@ -15,7 +15,7 @@ router = APIRouter(prefix="/groups/seeders", tags=["Groups: Seeders"])
class DataSeederController(BaseUserController):
@cached_property
def service(self) -> SeederService:
return SeederService(self.repos, self.user, self.group)
return SeederService(self.repos)
def _wrap(self, func):
try:

View File

@@ -0,0 +1,28 @@
from fastapi import APIRouter
from . import (
controller_cookbooks,
controller_group_notifications,
controller_group_recipe_actions,
controller_household_self_service,
controller_invitations,
controller_mealplan,
controller_mealplan_rules,
controller_shopping_lists,
controller_webhooks,
)
router = APIRouter()
router.include_router(controller_cookbooks.router)
router.include_router(controller_group_notifications.router)
router.include_router(controller_group_recipe_actions.router)
router.include_router(controller_household_self_service.router)
router.include_router(controller_invitations.router)
router.include_router(controller_shopping_lists.router)
router.include_router(controller_shopping_lists.item_router)
router.include_router(controller_webhooks.router)
# mealplan_rules must be added before mealplan due to the way the routes are defined
router.include_router(controller_mealplan_rules.router)
router.include_router(controller_mealplan.router)

View File

@@ -1,3 +1,4 @@
from collections import defaultdict
from functools import cached_property
from uuid import UUID
@@ -19,14 +20,14 @@ from mealie.services.event_bus_service.event_types import (
EventTypes,
)
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"], route_class=MealieCrudRoute)
router = APIRouter(prefix="/households/cookbooks", tags=["Households: Cookbooks"], route_class=MealieCrudRoute)
@controller(router)
class GroupCookbookController(BaseCrudController):
@cached_property
def repo(self):
return self.repos.cookbooks.by_group(self.group_id)
return self.repos.cookbooks
def registered_exceptions(self, ex: type[Exception]) -> str:
registered = {
@@ -54,13 +55,15 @@ class GroupCookbookController(BaseCrudController):
@router.post("", response_model=ReadCookBook, status_code=201)
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, household_id=self.household_id)
cookbook = self.mixins.create_one(data)
if cookbook:
self.publish_event(
event_type=EventTypes.cookbook_created,
document_data=EventCookbookData(operation=EventOperation.create, cookbook_id=cookbook.id),
group_id=cookbook.group_id,
household_id=cookbook.household_id,
message=self.t("notifications.generic-created", name=cookbook.name),
)
@@ -68,19 +71,25 @@ class GroupCookbookController(BaseCrudController):
@router.put("", response_model=list[ReadCookBook])
def update_many(self, data: list[UpdateCookBook]):
updated = []
updated_by_group_and_household: defaultdict[UUID4, defaultdict[UUID4, list[ReadCookBook]]] = defaultdict(
lambda: defaultdict(list)
)
for cookbook in data:
cb = self.mixins.update_one(cookbook, cookbook.id)
updated.append(cb)
updated_by_group_and_household[cb.group_id][cb.household_id].append(cb)
if updated:
self.publish_event(
event_type=EventTypes.cookbook_updated,
document_data=EventCookbookBulkData(
operation=EventOperation.update, cookbook_ids=[cb.id for cb in updated]
),
)
if updated_by_group_and_household:
for group_id, household_dict in updated_by_group_and_household.items():
for household_id, updated in household_dict.items():
self.publish_event(
event_type=EventTypes.cookbook_updated,
document_data=EventCookbookBulkData(
operation=EventOperation.update, cookbook_ids=[cb.id for cb in updated]
),
group_id=group_id,
household_id=household_id,
)
return updated
@@ -102,7 +111,7 @@ class GroupCookbookController(BaseCrudController):
return cookbook.cast(
RecipeCookBook,
recipes=self.repos.recipes.by_group(self.group_id).by_category_and_tags(
recipes=self.repos.recipes.by_category_and_tags(
cookbook.categories,
cookbook.tags,
cookbook.tools,
@@ -119,6 +128,8 @@ class GroupCookbookController(BaseCrudController):
self.publish_event(
event_type=EventTypes.cookbook_updated,
document_data=EventCookbookData(operation=EventOperation.update, cookbook_id=cookbook.id),
group_id=cookbook.group_id,
household_id=cookbook.household_id,
message=self.t("notifications.generic-updated", name=cookbook.name),
)
@@ -131,6 +142,8 @@ class GroupCookbookController(BaseCrudController):
self.publish_event(
event_type=EventTypes.cookbook_deleted,
document_data=EventCookbookData(operation=EventOperation.delete, cookbook_id=cookbook.id),
group_id=cookbook.group_id,
household_id=cookbook.household_id,
message=self.t("notifications.generic-deleted", name=cookbook.name),
)

View File

@@ -7,7 +7,7 @@ from mealie.routes._base.base_controllers import BaseUserController
from mealie.routes._base.controller import controller
from mealie.routes._base.mixins import HttpRepo
from mealie.routes._base.routers import MealieCrudRoute
from mealie.schema.group.group_events import (
from mealie.schema.household.group_events import (
GroupEventNotifierCreate,
GroupEventNotifierOut,
GroupEventNotifierPrivate,
@@ -29,7 +29,7 @@ from mealie.services.event_bus_service.event_types import (
)
router = APIRouter(
prefix="/groups/events/notifications", tags=["Group: Event Notifications"], route_class=MealieCrudRoute
prefix="/households/events/notifications", tags=["Households: Event Notifications"], route_class=MealieCrudRoute
)
@@ -42,7 +42,7 @@ class GroupEventsNotifierController(BaseUserController):
if not self.user:
raise Exception("No user is logged in.")
return self.repos.group_event_notifier.by_group(self.user.group_id)
return self.repos.group_event_notifier
# =======================================================================
# CRUD Operations
@@ -63,7 +63,7 @@ class GroupEventsNotifierController(BaseUserController):
@router.post("", response_model=GroupEventNotifierOut, status_code=201)
def create_one(self, data: GroupEventNotifierCreate):
save_data = cast(data, GroupEventNotifierSave, group_id=self.user.group_id)
save_data = cast(data, GroupEventNotifierSave, group_id=self.group_id, household_id=self.household_id)
return self.mixins.create_one(save_data)
@router.get("/{item_id}", response_model=GroupEventNotifierOut)
@@ -100,5 +100,5 @@ class GroupEventsNotifierController(BaseUserController):
document_data=EventDocumentDataBase(document_type=EventDocumentType.generic, operation=EventOperation.info),
)
test_listener = AppriseEventListener(self.group_id)
test_listener = AppriseEventListener(self.group_id, self.household_id)
test_listener.publish_to_subscribers(test_event, [item.apprise_url])

View File

@@ -6,7 +6,7 @@ from pydantic import UUID4
from mealie.routes._base.base_controllers import BaseUserController
from mealie.routes._base.controller import controller
from mealie.routes._base.mixins import HttpRepo
from mealie.schema.group.group_recipe_action import (
from mealie.schema.household.group_recipe_action import (
CreateGroupRecipeAction,
GroupRecipeActionOut,
GroupRecipeActionPagination,
@@ -14,14 +14,14 @@ from mealie.schema.group.group_recipe_action import (
)
from mealie.schema.response.pagination import PaginationQuery
router = APIRouter(prefix="/groups/recipe-actions", tags=["Groups: Recipe Actions"])
router = APIRouter(prefix="/households/recipe-actions", tags=["Households: Recipe Actions"])
@controller(router)
class GroupRecipeActionController(BaseUserController):
@cached_property
def repo(self):
return self.repos.group_recipe_actions.by_group(self.group_id)
return self.repos.group_recipe_actions
@property
def mixins(self):
@@ -39,7 +39,7 @@ class GroupRecipeActionController(BaseUserController):
@router.post("", response_model=GroupRecipeActionOut, status_code=201)
def create_one(self, data: CreateGroupRecipeAction):
save = data.cast(SaveGroupRecipeAction, group_id=self.group.id)
save = data.cast(SaveGroupRecipeAction, group_id=self.group_id, household_id=self.household_id)
return self.mixins.create_one(save)
@router.get("/{item_id}", response_model=GroupRecipeActionOut)

View File

@@ -0,0 +1,69 @@
from functools import cached_property
from fastapi import HTTPException, status
from mealie.routes._base.base_controllers import BaseUserController
from mealie.routes._base.controller import controller
from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.household.household import HouseholdInDB
from mealie.schema.household.household_permissions import SetPermissions
from mealie.schema.household.household_preferences import ReadHouseholdPreferences, UpdateHouseholdPreferences
from mealie.schema.household.household_statistics import HouseholdStatistics
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.user.user import UserOut
from mealie.services.household_services.household_service import HouseholdService
router = UserAPIRouter(prefix="/households", tags=["Households: Self Service"])
@controller(router)
class HouseholdSelfServiceController(BaseUserController):
@cached_property
def service(self) -> HouseholdService:
return HouseholdService(self.group_id, self.household_id, self.repos)
@router.get("/self", response_model=HouseholdInDB)
def get_logged_in_user_household(self):
"""Returns the Household Data for the Current User"""
return self.household
@router.get("/members", response_model=list[UserOut])
def get_household_members(self):
"""Returns all users belonging to the current household"""
private_users = self.repos.users.page_all(
PaginationQuery(page=1, per_page=-1, query_filter=f"household_id={self.household_id}")
).items
return [user.cast(UserOut) for user in private_users]
@router.get("/preferences", response_model=ReadHouseholdPreferences)
def get_household_preferences(self):
return self.household.preferences
@router.put("/preferences", response_model=ReadHouseholdPreferences)
def update_household_preferences(self, new_pref: UpdateHouseholdPreferences):
return self.repos.household_preferences.update(self.household_id, new_pref)
@router.put("/permissions", response_model=UserOut)
def set_member_permissions(self, permissions: SetPermissions):
self.checks.can_manage()
target_user = self.repos.users.get_one(permissions.user_id)
if not target_user:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
if target_user.group_id != self.group_id:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="User is not a member of this group")
if target_user.household_id != self.household_id:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="User is not a member of this household")
target_user.can_invite = permissions.can_invite
target_user.can_manage = permissions.can_manage
target_user.can_organize = permissions.can_organize
return self.repos.users.update(permissions.user_id, target_user)
@router.get("/statistics", response_model=HouseholdStatistics)
def get_statistics(self):
return self.service.calculate_statistics()

View File

@@ -4,23 +4,24 @@ from fastapi import APIRouter, Header, HTTPException, status
from mealie.core.security import url_safe_token
from mealie.routes._base import BaseUserController, controller
from mealie.schema.group.invite_token import (
from mealie.schema.household.invite_token import (
CreateInviteToken,
EmailInitationResponse,
EmailInvitation,
ReadInviteToken,
SaveInviteToken,
)
from mealie.schema.response.pagination import PaginationQuery
from mealie.services.email.email_service import EmailService
router = APIRouter(prefix="/groups/invitations", tags=["Groups: Invitations"])
router = APIRouter(prefix="/households/invitations", tags=["Households: Invitations"])
@controller(router)
class GroupInvitationsController(BaseUserController):
@router.get("", response_model=list[ReadInviteToken])
def get_invite_tokens(self):
return self.repos.group_invite_tokens.multi_query({"group_id": self.group_id})
return self.repos.group_invite_tokens.page_all(PaginationQuery(page=1, per_page=-1)).items
@router.post("", response_model=ReadInviteToken, status_code=status.HTTP_201_CREATED)
def create_invite_token(self, uses: CreateInviteToken):
@@ -30,7 +31,9 @@ class GroupInvitationsController(BaseUserController):
detail="User is not allowed to create invite tokens",
)
token = SaveInviteToken(uses_left=uses.uses, group_id=self.group_id, token=url_safe_token())
token = SaveInviteToken(
uses_left=uses.uses, group_id=self.group_id, household_id=self.household_id, token=url_safe_token()
)
return self.repos.group_invite_tokens.create(token)
@router.post("/email", response_model=EmailInitationResponse)

View File

@@ -17,14 +17,14 @@ from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.response.responses import ErrorResponse
from mealie.services.event_bus_service.event_types import EventMealplanCreatedData, EventTypes
router = APIRouter(prefix="/groups/mealplans", tags=["Groups: Mealplans"])
router = APIRouter(prefix="/households/mealplans", tags=["Households: Mealplans"])
@controller(router)
class GroupMealplanController(BaseCrudController):
@cached_property
def repo(self) -> RepositoryMeals:
return self.repos.meals.by_group(self.group_id)
return self.repos.meals
def registered_exceptions(self, ex: type[Exception]) -> str:
registered = {
@@ -42,7 +42,7 @@ class GroupMealplanController(BaseCrudController):
@router.get("/today")
def get_todays_meals(self):
return self.repo.get_today(group_id=self.group_id)
return self.repo.get_today()
@router.post("/random", response_model=ReadPlanEntry)
def create_random_meal(self, data: CreateRandomEntry):
@@ -55,11 +55,9 @@ class GroupMealplanController(BaseCrudController):
to the random meal selector.
"""
# Get relevant group rules
rules = self.repos.group_meal_plan_rules.by_group(self.group_id).get_rules(
PlanRulesDay.from_date(data.date), data.entry_type.value
)
rules = self.repos.group_meal_plan_rules.get_rules(PlanRulesDay.from_date(data.date), data.entry_type.value)
recipe_repo = self.repos.recipes.by_group(self.group_id)
recipe_repo = self.repos.recipes
random_recipes: list[Recipe] = []
if not rules: # If no rules are set, return any random recipe from the group
@@ -74,9 +72,7 @@ class GroupMealplanController(BaseCrudController):
categories.extend(rule.categories)
if tags or categories:
random_recipes = self.repos.recipes.by_group(self.group_id).get_random_by_categories_and_tags(
categories, tags
)
random_recipes = self.repos.recipes.get_random_by_categories_and_tags(categories, tags)
else:
random_recipes = recipe_repo.get_random()
@@ -124,7 +120,7 @@ class GroupMealplanController(BaseCrudController):
@router.post("", response_model=ReadPlanEntry, status_code=201)
def create_one(self, data: CreatePlanEntry):
data = mapper.cast(data, SavePlanEntry, group_id=self.group.id, user_id=self.user.id)
data = mapper.cast(data, SavePlanEntry, group_id=self.group_id, user_id=self.user.id)
result = self.mixins.create_one(data)
self.publish_event(
@@ -136,6 +132,8 @@ class GroupMealplanController(BaseCrudController):
recipe_slug=result.recipe.slug if result.recipe else None,
date=data.date,
),
group_id=result.group_id,
household_id=result.household_id,
message=f"Mealplan entry created for {data.date} for {data.entry_type}",
)

View File

@@ -11,14 +11,14 @@ from mealie.schema import mapper
from mealie.schema.meal_plan.plan_rules import PlanRulesCreate, PlanRulesOut, PlanRulesPagination, PlanRulesSave
from mealie.schema.response.pagination import PaginationQuery
router = UserAPIRouter(prefix="/groups/mealplans/rules", tags=["Groups: Mealplan Rules"])
router = UserAPIRouter(prefix="/households/mealplans/rules", tags=["Households: Mealplan Rules"])
@controller(router)
class GroupMealplanConfigController(BaseUserController):
@cached_property
def repo(self):
return self.repos.group_meal_plan_rules.by_group(self.group_id)
return self.repos.group_meal_plan_rules
@cached_property
def mixins(self):
@@ -36,7 +36,7 @@ class GroupMealplanConfigController(BaseUserController):
@router.post("", response_model=PlanRulesOut, status_code=201)
def create_one(self, data: PlanRulesCreate):
save = mapper.cast(data, PlanRulesSave, group_id=self.group.id)
save = mapper.cast(data, PlanRulesSave, group_id=self.group.id, household_id=self.household.id)
return self.mixins.create_one(save)
@router.get("/{item_id}", response_model=PlanRulesOut)

View File

@@ -7,7 +7,7 @@ from pydantic import UUID4
from mealie.routes._base.base_controllers import BaseCrudController
from mealie.routes._base.controller import controller
from mealie.routes._base.mixins import HttpRepo
from mealie.schema.group.group_shopping_list import (
from mealie.schema.household.group_shopping_list import (
ShoppingListAddRecipeParams,
ShoppingListCreate,
ShoppingListItemCreate,
@@ -32,9 +32,9 @@ from mealie.services.event_bus_service.event_types import (
EventShoppingListItemBulkData,
EventTypes,
)
from mealie.services.group_services.shopping_lists import ShoppingListService
from mealie.services.household_services.shopping_lists import ShoppingListService
item_router = APIRouter(prefix="/groups/shopping/items", tags=["Group: Shopping List Items"])
item_router = APIRouter(prefix="/households/shopping/items", tags=["Households: Shopping List Items"])
def publish_list_item_events(publisher: Callable, items_collection: ShoppingListItemsCollectionOut) -> None:
@@ -52,6 +52,9 @@ def publish_list_item_events(publisher: Callable, items_collection: ShoppingList
shopping_list_id=shopping_list_id,
shopping_list_item_ids=[item.id for item in items],
),
# since these are all the same shopping list, they share a group_id and household_id
group_id=items[0].group_id,
household_id=items[0].household_id,
)
if items_collection.updated_items:
@@ -67,6 +70,9 @@ def publish_list_item_events(publisher: Callable, items_collection: ShoppingList
shopping_list_id=shopping_list_id,
shopping_list_item_ids=[item.id for item in items],
),
# since these are all the same shopping list, they share a group_id and household_id
group_id=items[0].group_id,
household_id=items[0].household_id,
)
if items_collection.deleted_items:
@@ -82,6 +88,9 @@ def publish_list_item_events(publisher: Callable, items_collection: ShoppingList
shopping_list_id=shopping_list_id,
shopping_list_item_ids=[item.id for item in items],
),
# since these are all the same shopping list, they share a group_id and household_id
group_id=items[0].group_id,
household_id=items[0].household_id,
)
@@ -89,7 +98,7 @@ def publish_list_item_events(publisher: Callable, items_collection: ShoppingList
class ShoppingListItemController(BaseCrudController):
@cached_property
def service(self):
return ShoppingListService(self.repos, self.group, self.user)
return ShoppingListService(self.repos)
@cached_property
def repo(self):
@@ -143,18 +152,18 @@ class ShoppingListItemController(BaseCrudController):
return self.delete_many([item_id])
router = APIRouter(prefix="/groups/shopping/lists", tags=["Group: Shopping Lists"])
router = APIRouter(prefix="/households/shopping/lists", tags=["Households: Shopping Lists"])
@controller(router)
class ShoppingListController(BaseCrudController):
@cached_property
def service(self):
return ShoppingListService(self.repos, self.group, self.user)
return ShoppingListService(self.repos)
@cached_property
def repo(self):
return self.repos.group_shopping_lists.by_group(self.user.group_id)
return self.repos.group_shopping_lists
# =======================================================================
# CRUD Operations
@@ -175,11 +184,13 @@ class ShoppingListController(BaseCrudController):
@router.post("", response_model=ShoppingListOut, status_code=201)
def create_one(self, data: ShoppingListCreate):
shopping_list = self.service.create_one_list(data)
shopping_list = self.service.create_one_list(data, self.user.id)
if shopping_list:
self.publish_event(
event_type=EventTypes.shopping_list_created,
document_data=EventShoppingListData(operation=EventOperation.create, shopping_list_id=shopping_list.id),
group_id=shopping_list.group_id,
household_id=shopping_list.household_id,
message=self.t("notifications.generic-created", name=shopping_list.name),
)
@@ -195,6 +206,8 @@ class ShoppingListController(BaseCrudController):
self.publish_event(
event_type=EventTypes.shopping_list_updated,
document_data=EventShoppingListData(operation=EventOperation.update, shopping_list_id=shopping_list.id),
group_id=shopping_list.group_id,
household_id=shopping_list.household_id,
message=self.t("notifications.generic-updated", name=shopping_list.name),
)
@@ -207,6 +220,8 @@ class ShoppingListController(BaseCrudController):
self.publish_event(
event_type=EventTypes.shopping_list_deleted,
document_data=EventShoppingListData(operation=EventOperation.delete, shopping_list_id=shopping_list.id),
group_id=shopping_list.group_id,
household_id=shopping_list.household_id,
message=self.t("notifications.generic-deleted", name=shopping_list.name),
)
@@ -252,6 +267,8 @@ class ShoppingListController(BaseCrudController):
self.publish_event(
event_type=EventTypes.shopping_list_updated,
document_data=EventShoppingListData(operation=EventOperation.update, shopping_list_id=updated_list.id),
group_id=updated_list.group_id,
household_id=updated_list.household_id,
message=self.t("notifications.generic-updated", name=updated_list.name),
)

View File

@@ -8,18 +8,18 @@ from mealie.routes._base.base_controllers import BaseUserController
from mealie.routes._base.controller import controller
from mealie.routes._base.mixins import HttpRepo
from mealie.schema import mapper
from mealie.schema.group.webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination
from mealie.schema.household.webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination
from mealie.schema.response.pagination import PaginationQuery
from mealie.services.scheduler.tasks.post_webhooks import post_group_webhooks, post_single_webhook
router = APIRouter(prefix="/groups/webhooks", tags=["Groups: Webhooks"])
router = APIRouter(prefix="/households/webhooks", tags=["Households: Webhooks"])
@controller(router)
class ReadWebhookController(BaseUserController):
@cached_property
def repo(self):
return self.repos.webhooks.by_group(self.group_id)
return self.repos.webhooks
@property
def mixins(self) -> HttpRepo:
@@ -37,7 +37,7 @@ class ReadWebhookController(BaseUserController):
@router.post("", response_model=ReadWebhook, status_code=201)
def create_one(self, data: CreateWebhook):
save = mapper.cast(data, SaveWebhook, group_id=self.group.id)
save = mapper.cast(data, SaveWebhook, group_id=self.group_id, household_id=self.household_id)
return self.mixins.create_one(save)
@router.post("/rerun")
@@ -46,7 +46,7 @@ class ReadWebhookController(BaseUserController):
start_time = datetime.min.time()
start_dt = datetime.combine(datetime.now(timezone.utc).date(), start_time)
post_group_webhooks(start_dt=start_dt, group_id=self.group.id)
post_group_webhooks(start_dt=start_dt, group_id=self.group.id, household_id=self.household.id)
@router.get("/{item_id}", response_model=ReadWebhook)
def get_one(self, item_id: UUID4):

View File

@@ -12,7 +12,7 @@ These routes are for development only! These assets are served by Caddy when not
in development mode. If you make changes, be sure to test the production container.
"""
router = APIRouter(prefix="/recipes")
router = APIRouter(prefix="/recipes", include_in_schema=False)
class ImageType(str, Enum):

View File

@@ -9,7 +9,7 @@ These routes are for development only! These assets are served by Caddy when not
in development mode. If you make changes, be sure to test the production container.
"""
router = APIRouter(prefix="/users")
router = APIRouter(prefix="/users", include_in_schema=False)
@router.get("/{user_id}/{file_name}", response_class=FileResponse)

View File

@@ -8,7 +8,7 @@ from mealie.routes._base.mixins import HttpRepo
from mealie.schema import mapper
from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
from mealie.schema.recipe.recipe import RecipeCategory, RecipeCategoryPagination
from mealie.schema.recipe.recipe_category import CategoryBase, CategorySave
from mealie.schema.recipe.recipe_category import CategoryBase, CategoryOut, CategorySave
from mealie.schema.response.pagination import PaginationQuery
from mealie.services import urls
from mealie.services.event_bus_service.event_types import EventCategoryData, EventOperation, EventTypes
@@ -29,11 +29,11 @@ class RecipeCategoryController(BaseCrudController):
# CRUD Operations
@cached_property
def repo(self):
return self.repos.categories.by_group(self.group_id)
return self.repos.categories
@cached_property
def mixins(self):
return HttpRepo(self.repo, self.logger)
return HttpRepo[CategorySave, CategoryOut, CategorySave](self.repo, self.logger)
@router.get("", response_model=RecipeCategoryPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery), search: str | None = None):
@@ -56,6 +56,8 @@ class RecipeCategoryController(BaseCrudController):
self.publish_event(
event_type=EventTypes.category_created,
document_data=EventCategoryData(operation=EventOperation.create, category_id=new_category.id),
group_id=new_category.group_id,
household_id=None,
message=self.t(
"notifications.generic-created-with-url",
name=new_category.name,
@@ -82,6 +84,8 @@ class RecipeCategoryController(BaseCrudController):
self.publish_event(
event_type=EventTypes.category_updated,
document_data=EventCategoryData(operation=EventOperation.update, category_id=category.id),
group_id=category.group_id,
household_id=None,
message=self.t(
"notifications.generic-updated-with-url",
name=category.name,
@@ -102,6 +106,8 @@ class RecipeCategoryController(BaseCrudController):
self.publish_event(
event_type=EventTypes.category_deleted,
document_data=EventCategoryData(operation=EventOperation.delete, category_id=category.id),
group_id=category.group_id,
household_id=None,
message=self.t("notifications.generic-deleted", name=category.name),
)
@@ -121,5 +127,5 @@ class RecipeCategoryController(BaseCrudController):
id=category.id,
slug=category.slug,
name=category.name,
recipes=self.repos.recipes.by_group(self.group_id).get_by_categories([category]),
recipes=self.repos.recipes.get_by_categories([category]),
)

View File

@@ -20,7 +20,7 @@ router = APIRouter(prefix="/tags", tags=["Organizer: Tags"])
class TagController(BaseCrudController):
@cached_property
def repo(self):
return self.repos.tags.by_group(self.group_id)
return self.repos.tags
@cached_property
def mixins(self):
@@ -58,6 +58,8 @@ class TagController(BaseCrudController):
self.publish_event(
event_type=EventTypes.tag_created,
document_data=EventTagData(operation=EventOperation.create, tag_id=new_tag.id),
group_id=new_tag.group_id,
household_id=None,
message=self.t(
"notifications.generic-created-with-url",
name=new_tag.name,
@@ -77,6 +79,8 @@ class TagController(BaseCrudController):
self.publish_event(
event_type=EventTypes.tag_updated,
document_data=EventTagData(operation=EventOperation.update, tag_id=tag.id),
group_id=tag.group_id,
household_id=None,
message=self.t(
"notifications.generic-updated-with-url",
name=tag.name,
@@ -103,6 +107,8 @@ class TagController(BaseCrudController):
self.publish_event(
event_type=EventTypes.tag_deleted,
document_data=EventTagData(operation=EventOperation.delete, tag_id=tag.id),
group_id=tag.group_id,
household_id=None,
message=self.t("notifications.generic-deleted", name=tag.name),
)

View File

@@ -18,7 +18,7 @@ router = APIRouter(prefix="/tools", tags=["Organizer: Tools"])
class RecipeToolController(BaseUserController):
@cached_property
def repo(self):
return self.repos.tools.by_group(self.group_id)
return self.repos.tools
@property
def mixins(self) -> HttpRepo:

View File

@@ -1,12 +1,11 @@
from fastapi import APIRouter
from . import all_recipe_routes, bulk_actions, comments, recipe_crud_routes, shared_routes, timeline_events
from . import bulk_actions, comments, recipe_crud_routes, shared_routes, timeline_events
prefix = "/recipes"
router = APIRouter()
router.include_router(all_recipe_routes.router, prefix=prefix, tags=["Recipe: Query All"])
router.include_router(recipe_crud_routes.router_exports)
router.include_router(recipe_crud_routes.router)
router.include_router(comments.router, prefix=prefix, tags=["Recipe: Comments"])

View File

@@ -1,20 +0,0 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm.session import Session
from mealie.db.db_setup import generate_session
from mealie.repos.all_repositories import get_repositories
from mealie.schema.recipe import RecipeSummary
router = APIRouter()
@router.get("/summary/untagged", response_model=list[RecipeSummary])
async def get_untagged_recipes(count: bool = False, session: Session = Depends(generate_session)):
db = get_repositories(session)
return db.recipes.count_untagged(count=count, override_schema=RecipeSummary)
@router.get("/summary/uncategorized", response_model=list[RecipeSummary])
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
db = get_repositories(session)
return db.recipes.count_uncategorized(count=count, override_schema=RecipeSummary)

View File

@@ -10,5 +10,5 @@ class RecipeCommentsController(BaseUserController):
@router.get("/{slug}/comments", response_model=list[RecipeCommentOut])
async def get_recipe_comments(self, slug: str):
"""Get all comments for a recipe"""
recipe = self.repos.recipes.by_group(self.group_id).get_one(slug)
recipe = self.repos.recipes.get_one(slug)
return self.repos.comments.multi_query({"recipe_id": recipe.id})

View File

@@ -30,7 +30,7 @@ from mealie.core.dependencies import (
validate_recipe_token,
)
from mealie.core.security import create_recipe_slug_token
from mealie.db.models.group.cookbook import CookBook
from mealie.db.models.household.cookbook import CookBook
from mealie.pkgs import cache
from mealie.repos.repository_generic import RepositoryGeneric
from mealie.repos.repository_recipes import RepositoryRecipes
@@ -95,15 +95,15 @@ class JSONBytes(JSONResponse):
class BaseRecipeController(BaseCrudController):
@cached_property
def repo(self) -> RepositoryRecipes:
return self.repos.recipes.by_group(self.group_id)
return self.repos.recipes
@cached_property
def cookbooks_repo(self) -> RepositoryGeneric[ReadCookBook, CookBook]:
return self.repos.cookbooks.by_group(self.group_id)
return self.repos.cookbooks
@cached_property
def service(self) -> RecipeService:
return RecipeService(self.repos, self.user, self.group, translator=self.translator)
return RecipeService(self.repos, self.user, self.household, translator=self.translator)
@cached_property
def mixins(self):
@@ -207,7 +207,7 @@ class RecipeController(BaseRecipeController):
) from e
if req.include_tags:
ctx = ScraperContext(self.user.id, self.group_id, self.repos)
ctx = ScraperContext(self.repos)
recipe.tags = extras.use_tags(ctx) # type: ignore
@@ -217,6 +217,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=new_recipe.slug),
group_id=new_recipe.group_id,
household_id=new_recipe.household_id,
message=self.t(
"notifications.generic-created-with-url",
name=new_recipe.name,
@@ -236,6 +238,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeBulkReportData(operation=EventOperation.create, report_id=report_id),
group_id=self.group_id,
household_id=self.household_id,
)
return {"reportId": report_id}
@@ -265,6 +269,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=recipe.slug),
group_id=recipe.group_id,
household_id=recipe.household_id,
)
return recipe.slug
@@ -290,6 +296,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=recipe.slug),
group_id=recipe.group_id,
household_id=recipe.household_id,
)
return recipe.slug
@@ -324,7 +332,7 @@ class RecipeController(BaseRecipeController):
raise HTTPException(status_code=404, detail="cookbook not found")
# we use the repo by user so we can sort favorites correctly
pagination_response = self.repo.by_user(self.user.id).page_all(
pagination_response = self.repos.recipes.by_user(self.user.id).page_all(
pagination=q,
cookbook=cookbook_data,
categories=categories,
@@ -374,6 +382,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=new_recipe.slug),
group_id=new_recipe.group_id,
household_id=new_recipe.household_id,
message=self.t(
"notifications.generic-created-with-url",
name=new_recipe.name,
@@ -395,6 +405,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_created,
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=new_recipe.slug),
group_id=new_recipe.group_id,
household_id=new_recipe.household_id,
message=self.t(
"notifications.generic-duplicated",
name=new_recipe.name,
@@ -415,6 +427,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_updated,
document_data=EventRecipeData(operation=EventOperation.update, recipe_slug=recipe.slug),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t(
"notifications.generic-updated-with-url",
name=recipe.name,
@@ -436,6 +450,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_updated,
document_data=EventRecipeData(operation=EventOperation.update, recipe_slug=recipe.slug),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t(
"notifications.generic-updated-with-url",
name=recipe.name,
@@ -458,6 +474,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_updated,
document_data=EventRecipeData(operation=EventOperation.update, recipe_slug=recipe.slug),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t(
"notifications.generic-updated-with-url",
name=recipe.name,
@@ -479,6 +497,8 @@ class RecipeController(BaseRecipeController):
self.publish_event(
event_type=EventTypes.recipe_deleted,
document_data=EventRecipeData(operation=EventOperation.delete, recipe_slug=recipe.slug),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t("notifications.generic-deleted", name=recipe.name),
)

View File

@@ -12,7 +12,7 @@ router = APIRouter()
@router.get("/shared/{token_id}", response_model=Recipe)
def get_shared_recipe(token_id: UUID4, session: Session = Depends(generate_session)):
db = get_repositories(session)
db = get_repositories(session, group_id=None, household_id=None)
token_summary = db.recipe_share_tokens.get_one(token_id)

View File

@@ -32,7 +32,7 @@ class RecipeTimelineEventsController(BaseCrudController):
@cached_property
def recipes_repo(self):
return self.repos.recipes.by_group(self.group_id)
return self.repos.recipes
@cached_property
def mixins(self):
@@ -69,6 +69,8 @@ class RecipeTimelineEventsController(BaseCrudController):
document_data=EventRecipeTimelineEventData(
operation=EventOperation.create, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id
),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t(
"notifications.generic-updated-with-url",
name=recipe.name,
@@ -92,6 +94,8 @@ class RecipeTimelineEventsController(BaseCrudController):
document_data=EventRecipeTimelineEventData(
operation=EventOperation.update, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id
),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t(
"notifications.generic-updated-with-url",
name=recipe.name,
@@ -117,6 +121,8 @@ class RecipeTimelineEventsController(BaseCrudController):
document_data=EventRecipeTimelineEventData(
operation=EventOperation.delete, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id
),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t(
"notifications.generic-updated-with-url",
name=recipe.name,
@@ -145,6 +151,8 @@ class RecipeTimelineEventsController(BaseCrudController):
document_data=EventRecipeTimelineEventData(
operation=EventOperation.update, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id
),
group_id=recipe.group_id,
household_id=recipe.household_id,
message=self.t(
"notifications.generic-updated-with-url",
name=recipe.name,

View File

@@ -15,7 +15,7 @@ router = UserAPIRouter(prefix="/shared/recipes", tags=["Shared: Recipes"])
class RecipeSharedController(BaseUserController):
@cached_property
def repo(self):
return self.repos.recipe_share_tokens.by_group(self.group_id)
return self.repos.recipe_share_tokens
@cached_property
def mixins(self):

View File

@@ -162,13 +162,14 @@ def serve_recipe_with_meta_public(
session: Session = Depends(generate_session),
):
try:
repos = AllRepositories(session)
group = repos.groups.get_by_slug_or_id(group_slug)
public_repos = AllRepositories(session)
group = public_repos.groups.get_by_slug_or_id(group_slug)
if not group or group.preferences.private_group: # type: ignore
return response_404()
recipe = repos.recipes.by_group(group.id).get_one(recipe_slug)
group_repos = AllRepositories(session, group_id=group.id)
recipe = group_repos.recipes.get_one(recipe_slug)
if not recipe or not recipe.settings.public: # type: ignore
return response_404()
@@ -189,9 +190,9 @@ async def serve_recipe_with_meta(
return serve_recipe_with_meta_public(group_slug, recipe_slug, session)
try:
repos = AllRepositories(session)
repos = AllRepositories(session, group_id=user.group_id)
recipe = repos.recipes.by_group(user.group_id).get_one(recipe_slug, "slug")
recipe = repos.recipes.get_one(recipe_slug, "slug")
if recipe is None:
return response_404()

View File

@@ -25,7 +25,7 @@ router = APIRouter(prefix="/foods", tags=["Recipes: Foods"], route_class=MealieC
class IngredientFoodsController(BaseUserController):
@cached_property
def repo(self):
return self.repos.ingredient_foods.by_group(self.group_id)
return self.repos.ingredient_foods
@cached_property
def mixins(self):

View File

@@ -25,7 +25,7 @@ router = APIRouter(prefix="/units", tags=["Recipes: Units"], route_class=MealieC
class IngredientUnitsController(BaseUserController):
@cached_property
def repo(self):
return self.repos.ingredient_units.by_group(self.group_id)
return self.repos.ingredient_units
@cached_property
def mixins(self):

View File

@@ -11,7 +11,7 @@ from mealie.routes.users._helpers import assert_user_change_allowed
from mealie.schema.response import ErrorResponse, SuccessResponse
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserOut
from mealie.schema.user.user import UserPagination, UserRatings, UserRatingSummary, UserSummary, UserSummaryPagination
from mealie.schema.user.user import UserPagination, UserRatings, UserRatingSummary
user_router = UserAPIRouter(prefix="/users", tags=["Users: CRUD"])
admin_router = AdminAPIRouter(prefix="/users", tags=["Users: Admin CRUD"])
@@ -58,18 +58,6 @@ class AdminUserController(BaseAdminController):
@controller(user_router)
class UserController(BaseUserController):
@user_router.get("/group-users", response_model=UserSummaryPagination)
def get_all_group_users(self, q: PaginationQuery = Depends(PaginationQuery)):
"""Returns all users from the current group"""
response = self.repos.users.by_group(self.group_id).page_all(
pagination=q,
override=UserSummary,
)
response.set_pagination_guides(user_router.url_path_for("get_all_group_users"), q.model_dump())
return response
@user_router.get("/self", response_model=UserOut)
def get_logged_in_user(self):
return self.user

View File

@@ -22,7 +22,7 @@ class UserRatingsController(BaseUserController):
except ValueError:
pass
recipes_repo = self.repos.recipes.by_group(self.group_id)
recipes_repo = self.repos.recipes
if isinstance(slug_or_id, UUID):
recipe = recipes_repo.get_one(slug_or_id, key="id")
else:

View File

@@ -29,7 +29,7 @@ class RegistrationController(BasePublicController):
registration_service = RegistrationService(
self.logger,
get_repositories(self.session),
get_repositories(self.session, group_id=None, household_id=None),
self.translator,
)
@@ -38,6 +38,7 @@ class RegistrationController(BasePublicController):
self.event_bus.dispatch(
integration_id="registration",
group_id=result.group_id,
household_id=result.household_id,
event_type=EventTypes.user_signup,
document_data=EventUserSignupData(username=result.username, email=result.email),
)

View File

@@ -14,7 +14,7 @@ router = APIRouter()
@router.get("/user/name", response_model=ValidationResponse)
def validate_user(name: str, session: Session = Depends(generate_session)):
"""Checks if a user with the given name exists"""
db = get_repositories(session)
db = get_repositories(session, group_id=None, household_id=None)
existing_element = db.users.get_one(name, "username", any_case=True)
return ValidationResponse(valid=existing_element is None)
@@ -22,7 +22,7 @@ def validate_user(name: str, session: Session = Depends(generate_session)):
@router.get("/user/email", response_model=ValidationResponse)
def validate_user_email(email: str, session: Session = Depends(generate_session)):
"""Checks if a user with the given name exists"""
db = get_repositories(session)
db = get_repositories(session, group_id=None, household_id=None)
existing_element = db.users.get_one(email, "email", any_case=True)
return ValidationResponse(valid=existing_element is None)
@@ -30,15 +30,23 @@ def validate_user_email(email: str, session: Session = Depends(generate_session)
@router.get("/group", response_model=ValidationResponse)
def validate_group(name: str, session: Session = Depends(generate_session)):
"""Checks if a group with the given name exists"""
db = get_repositories(session)
db = get_repositories(session, group_id=None, household_id=None)
existing_element = db.groups.get_by_name(name)
return ValidationResponse(valid=existing_element is None)
@router.get("/household", response_model=ValidationResponse)
def validate_household(name: str, session: Session = Depends(generate_session)):
"""Checks if a household with the given name exists"""
db = get_repositories(session, group_id=None, household_id=None)
existing_element = db.households.get_by_name(name)
return ValidationResponse(valid=existing_element is None)
@router.get("/recipe", response_model=ValidationResponse)
def validate_recipe(group_id: UUID, name: str, session: Session = Depends(generate_session)):
"""Checks if a group with the given slug exists"""
db = get_repositories(session)
"""Checks if a recipe with the given slug exists"""
db = get_repositories(session, group_id=None, household_id=None)
slug = slugify(name)
existing_element = db.recipes.get_by_slug(group_id, slug)
return ValidationResponse(valid=existing_element is None)