From 87abe4ce1b88f91a3d4c3ee888273bc8b05b0b50 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:29:37 +0000 Subject: [PATCH 01/45] added public household routes --- mealie/routes/_base/base_controllers.py | 8 +++- mealie/routes/explore/__init__.py | 2 + .../explore/controller_public_households.py | 37 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 mealie/routes/explore/controller_public_households.py diff --git a/mealie/routes/_base/base_controllers.py b/mealie/routes/_base/base_controllers.py index e5799b182e8..e7d4bd5e28b 100644 --- a/mealie/routes/_base/base_controllers.py +++ b/mealie/routes/_base/base_controllers.py @@ -1,7 +1,7 @@ from abc import ABC from logging import Logger -from fastapi import Depends +from fastapi import Depends, HTTPException from pydantic import UUID4, ConfigDict from sqlalchemy.orm import Session @@ -97,6 +97,12 @@ class BasePublicGroupExploreController(BasePublicController): def group_id(self) -> UUID4 | None | NotSet: return self.group.id + def get_public_household(self, household_slug_or_id: str | UUID4) -> HouseholdInDB: + household = self.repos.households.get_by_slug_or_id(household_slug_or_id) + if not household or household.preferences.private_household: + raise HTTPException(404, "household not found") + return household + def get_explore_url_path(self, endpoint: str) -> str: if endpoint.startswith("/"): endpoint = endpoint[1:] diff --git a/mealie/routes/explore/__init__.py b/mealie/routes/explore/__init__.py index e329fc43f6a..15cc8112d63 100644 --- a/mealie/routes/explore/__init__.py +++ b/mealie/routes/explore/__init__.py @@ -3,6 +3,7 @@ from . import ( controller_public_cookbooks, controller_public_foods, + controller_public_households, controller_public_organizers, controller_public_recipes, ) @@ -11,6 +12,7 @@ # group router.include_router(controller_public_foods.router, tags=["Explore: Foods"]) +router.include_router(controller_public_households.router, tags=["Explore: Households"]) 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"]) diff --git a/mealie/routes/explore/controller_public_households.py b/mealie/routes/explore/controller_public_households.py new file mode 100644 index 00000000000..3422096f43b --- /dev/null +++ b/mealie/routes/explore/controller_public_households.py @@ -0,0 +1,37 @@ +from fastapi import APIRouter, Depends + +from mealie.routes._base import controller +from mealie.routes._base.base_controllers import BasePublicGroupExploreController +from mealie.schema.household.household import HouseholdSummary +from mealie.schema.make_dependable import make_dependable +from mealie.schema.response.pagination import PaginationBase, PaginationQuery + +router = APIRouter(prefix="/households") + + +@controller(router) +class PublicHouseholdsController(BasePublicGroupExploreController): + @property + def households(self): + return self.repos.households + + @router.get("", response_model=PaginationBase[HouseholdSummary]) + def get_all( + self, + q: PaginationQuery = Depends(make_dependable(PaginationQuery)), + search: str | None = None, + ) -> PaginationBase[HouseholdSummary]: + public_filter = "(preferences.private_household = FALSE)" + if q.query_filter: + q.query_filter = f"({q.query_filter}) AND {public_filter}" + else: + q.query_filter = public_filter + + response = self.households.page_all(pagination=q, override=HouseholdSummary, search=search) + response.set_pagination_guides(self.get_explore_url_path(router.url_path_for("get_all")), q.model_dump()) + return response + + @router.get("/{household_slug}", response_model=HouseholdSummary) + def get_household(self, household_slug: str) -> HouseholdSummary: + household = self.get_public_household(household_slug) + return household.cast(HouseholdSummary) From a54f11e7b52e80c1ef71f8f85792ee74e5797d1b Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:03:17 +0000 Subject: [PATCH 02/45] dev:generate --- tests/utils/api_routes/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/utils/api_routes/__init__.py b/tests/utils/api_routes/__init__.py index 6f2bc33c0c9..09ddeb1af46 100644 --- a/tests/utils/api_routes/__init__.py +++ b/tests/utils/api_routes/__init__.py @@ -247,6 +247,16 @@ def explore_groups_group_slug_foods_item_id(group_slug, item_id): return f"{prefix}/explore/groups/{group_slug}/foods/{item_id}" +def explore_groups_group_slug_households(group_slug): + """`/api/explore/groups/{group_slug}/households`""" + return f"{prefix}/explore/groups/{group_slug}/households" + + +def explore_groups_group_slug_households_household_slug(group_slug, household_slug): + """`/api/explore/groups/{group_slug}/households/{household_slug}`""" + return f"{prefix}/explore/groups/{group_slug}/households/{household_slug}" + + def explore_groups_group_slug_organizers_categories(group_slug): """`/api/explore/groups/{group_slug}/organizers/categories`""" return f"{prefix}/explore/groups/{group_slug}/organizers/categories" From a0806123122e31541c5d3d2ed532ccbbb384102b Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:05:25 +0000 Subject: [PATCH 03/45] added household routes to frontend --- frontend/lib/api/public/explore/households.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 frontend/lib/api/public/explore/households.ts diff --git a/frontend/lib/api/public/explore/households.ts b/frontend/lib/api/public/explore/households.ts new file mode 100644 index 00000000000..188d75053c4 --- /dev/null +++ b/frontend/lib/api/public/explore/households.ts @@ -0,0 +1,20 @@ +import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; +import { HouseholdSummary } from "~/lib/api/types/household"; +import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated"; + +const prefix = "/api"; +const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}` + +const routes = { + householdsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/households`, + householdsGroupSlugHouseholdSlug: (groupSlug: string | number, householdSlug: string | number) => `${exploreGroupSlug(groupSlug)}/households/${householdSlug}`, +}; + +export class PublicHouseholdApi extends BaseCRUDAPIReadOnly { + baseRoute = routes.householdsGroupSlug(this.groupSlug); + itemRoute = (itemId: string | number) => routes.householdsGroupSlugHouseholdSlug(this.groupSlug, itemId); + + constructor(requests: ApiRequestInstance, private readonly groupSlug: string) { + super(requests); + } +} From 47d4d5619ee4574bfe7a79d039a612666afe28b0 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:28:46 +0000 Subject: [PATCH 04/45] refactor stores to reuse less code --- .../Domain/Recipe/RecipeExplorerPage.vue | 26 +++---- .../Domain/Recipe/RecipeIngredientEditor.vue | 4 +- .../Domain/Recipe/RecipeOrganizerDialog.vue | 2 +- .../Domain/Recipe/RecipeOrganizerSelector.vue | 4 +- frontend/composables/api/index.ts | 2 +- frontend/composables/partials/types.ts | 3 + .../partials/use-actions-factory.ts | 5 +- .../composables/partials/use-store-factory.ts | 53 +++++++++++++ frontend/composables/store/index.ts | 10 +-- .../composables/store/use-category-store.ts | 77 ++++--------------- frontend/composables/store/use-food-store.ts | 73 ++++-------------- frontend/composables/store/use-label-store.ts | 45 ++--------- frontend/composables/store/use-tag-store.ts | 76 ++++-------------- frontend/composables/store/use-tool-store.ts | 77 ++++--------------- frontend/composables/store/use-unit-store.ts | 49 +++--------- .../_groupSlug/r/_slug/ingredient-parser.vue | 2 - .../g/_groupSlug/recipes/categories/index.vue | 8 +- .../pages/g/_groupSlug/recipes/tags/index.vue | 8 +- .../g/_groupSlug/recipes/tools/index.vue | 2 +- frontend/pages/group/data/categories.vue | 2 +- frontend/pages/group/data/foods.vue | 6 +- frontend/pages/group/data/labels.vue | 2 +- frontend/pages/group/data/tags.vue | 2 +- frontend/pages/group/data/tools.vue | 2 +- frontend/pages/group/data/units.vue | 12 +-- frontend/pages/shopping-lists/_id.vue | 6 +- 26 files changed, 181 insertions(+), 377 deletions(-) create mode 100644 frontend/composables/partials/types.ts create mode 100644 frontend/composables/partials/use-store-factory.ts diff --git a/frontend/components/Domain/Recipe/RecipeExplorerPage.vue b/frontend/components/Domain/Recipe/RecipeExplorerPage.vue index 03cb0f7f213..6676dbe319b 100644 --- a/frontend/components/Domain/Recipe/RecipeExplorerPage.vue +++ b/frontend/components/Domain/Recipe/RecipeExplorerPage.vue @@ -423,9 +423,9 @@ export default defineComponent({ if (query.categories?.length) { promises.push( waitUntilAndExecute( - () => categories.items.value.length > 0, + () => categories.store.value.length > 0, () => { - const result = categories.items.value.filter((item) => + const result = categories.store.value.filter((item) => (query.categories as string[]).includes(item.id as string) ); @@ -440,9 +440,9 @@ export default defineComponent({ if (query.tags?.length) { promises.push( waitUntilAndExecute( - () => tags.items.value.length > 0, + () => tags.store.value.length > 0, () => { - const result = tags.items.value.filter((item) => (query.tags as string[]).includes(item.id as string)); + const result = tags.store.value.filter((item) => (query.tags as string[]).includes(item.id as string)); selectedTags.value = result as NoUndefinedField[]; } ) @@ -454,9 +454,9 @@ export default defineComponent({ if (query.tools?.length) { promises.push( waitUntilAndExecute( - () => tools.items.value.length > 0, + () => tools.store.value.length > 0, () => { - const result = tools.items.value.filter((item) => (query.tools as string[]).includes(item.id)); + const result = tools.store.value.filter((item) => (query.tools as string[]).includes(item.id)); selectedTools.value = result as NoUndefinedField[]; } ) @@ -469,13 +469,13 @@ export default defineComponent({ promises.push( waitUntilAndExecute( () => { - if (foods.foods.value) { - return foods.foods.value.length > 0; + if (foods.store.value) { + return foods.store.value.length > 0; } return false; }, () => { - const result = foods.foods.value?.filter((item) => (query.foods as string[]).includes(item.id)); + const result = foods.store.value?.filter((item) => (query.foods as string[]).includes(item.id)); selectedFoods.value = result ?? []; } ) @@ -533,10 +533,10 @@ export default defineComponent({ search, reset, state, - categories: categories.items as unknown as NoUndefinedField[], - tags: tags.items as unknown as NoUndefinedField[], - foods: foods.foods, - tools: tools.items as unknown as NoUndefinedField[], + categories: categories.store as unknown as NoUndefinedField[], + tags: tags.store as unknown as NoUndefinedField[], + foods: foods.store, + tools: tools.store as unknown as NoUndefinedField[], sortable, toggleOrderDirection, diff --git a/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue b/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue index f1e8e54f830..cdf37107897 100644 --- a/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue +++ b/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue @@ -289,11 +289,11 @@ export default defineComponent({ createAssignFood, unitAutocomplete, createAssignUnit, - foods: foodStore.foods, + foods: foodStore.store, foodSearch, toggleTitle, unitActions: unitStore.actions, - units: unitStore.units, + units: unitStore.store, unitSearch, validators, workingUnitData: unitsData.data, diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue b/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue index 378cda2b061..91f4478868d 100644 --- a/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue +++ b/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue @@ -135,7 +135,7 @@ export default defineComponent({ await store.actions.createOne({ ...state }); } - const newItem = store.items.value.find((item) => item.name === state.name); + const newItem = store.store.value.find((item) => item.name === state.name); context.emit(CREATED_ITEM_EVENT, newItem); dialog.value = false; diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue index 16fd636bf24..658f043574b 100644 --- a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue +++ b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue @@ -127,9 +127,9 @@ export default defineComponent({ const items = computed(() => { if (!props.returnObject) { - return store.items.value.map((item) => item.name); + return store.store.value.map((item) => item.name); } - return store.items.value; + return store.store.value; }); function removeByIndex(index: number) { diff --git a/frontend/composables/api/index.ts b/frontend/composables/api/index.ts index 3f9056368b8..20d74981d77 100644 --- a/frontend/composables/api/index.ts +++ b/frontend/composables/api/index.ts @@ -1,3 +1,3 @@ export { useAppInfo } from "./use-app-info"; export { useStaticRoutes } from "./static-routes"; -export { useAdminApi, useUserApi } from "./api-client"; +export { useAdminApi, usePublicApi, usePublicExploreApi, useUserApi } from "./api-client"; diff --git a/frontend/composables/partials/types.ts b/frontend/composables/partials/types.ts new file mode 100644 index 00000000000..1be933e3e77 --- /dev/null +++ b/frontend/composables/partials/types.ts @@ -0,0 +1,3 @@ +export type BoundT = { + id?: string | number | null; +}; diff --git a/frontend/composables/partials/use-actions-factory.ts b/frontend/composables/partials/use-actions-factory.ts index f0b829c78ba..3069b7dc17c 100644 --- a/frontend/composables/partials/use-actions-factory.ts +++ b/frontend/composables/partials/use-actions-factory.ts @@ -1,12 +1,9 @@ import { Ref, useAsync } from "@nuxtjs/composition-api"; import { useAsyncKey } from "../use-utils"; +import { BoundT } from "./types"; import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; import { QueryValue } from "~/lib/api/base/route"; -type BoundT = { - id?: string | number | null; -}; - interface PublicStoreActions { getAll(page?: number, perPage?: number, params?: any): Ref; refresh(): Promise; diff --git a/frontend/composables/partials/use-store-factory.ts b/frontend/composables/partials/use-store-factory.ts new file mode 100644 index 00000000000..926d10e3a73 --- /dev/null +++ b/frontend/composables/partials/use-store-factory.ts @@ -0,0 +1,53 @@ +import { ref, reactive, Ref } from "@nuxtjs/composition-api"; +import { usePublicStoreActions, useStoreActions } from "./use-actions-factory"; +import { BoundT } from "./types"; +import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; + +export const useData = function(defaultObject: T) { + const data = reactive({ ...defaultObject }); + function reset() { + Object.assign(data, defaultObject); + }; + + return { data, reset }; +} + +export const usePublicStore = function( + store: Ref, + loading: Ref, + api: BaseCRUDAPIReadOnly, +) { + const actions = { + ...usePublicStoreActions(api, store, loading), + flushStore() { + store.value = []; + }, + }; + + if (!loading.value && (!store.value || store.value.length === 0)) { + const result = actions.getAll(); + store.value = result.value || []; + } + + return { store, actions }; +} + +export const useStore = function( + store: Ref, + loading: Ref, + api: BaseCRUDAPI, +) { + const actions = { + ...useStoreActions(api, store, loading), + flushStore() { + store = ref([]); + }, + }; + + if (!loading.value && (!store.value || store.value.length === 0)) { + const result = actions.getAll(); + store.value = result.value || []; + } + + return { store, actions }; +} diff --git a/frontend/composables/store/index.ts b/frontend/composables/store/index.ts index e00aba57e1a..1b3ae0e5040 100644 --- a/frontend/composables/store/index.ts +++ b/frontend/composables/store/index.ts @@ -1,6 +1,6 @@ -export { useFoodStore, useFoodData } from "./use-food-store"; -export { useUnitStore, useUnitData } from "./use-unit-store"; +export { useCategoryStore, usePublicCategoryStore, useCategoryData } from "./use-category-store"; +export { useFoodStore, usePublicFoodStore, useFoodData } from "./use-food-store"; export { useLabelStore, useLabelData } from "./use-label-store"; -export { useToolStore, useToolData } from "./use-tool-store"; -export { useCategoryStore, useCategoryData } from "./use-category-store"; -export { useTagStore, useTagData } from "./use-tag-store"; +export { useTagStore, usePublicTagStore, useTagData } from "./use-tag-store"; +export { useToolStore, usePublicToolStore, useToolData } from "./use-tool-store"; +export { useUnitStore, useUnitData } from "./use-unit-store"; diff --git a/frontend/composables/store/use-category-store.ts b/frontend/composables/store/use-category-store.ts index 4801bc9ab4b..5b22d654021 100644 --- a/frontend/composables/store/use-category-store.ts +++ b/frontend/composables/store/use-category-store.ts @@ -1,73 +1,26 @@ -import { reactive, ref, Ref } from "@nuxtjs/composition-api"; -import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory"; -import { usePublicExploreApi } from "../api/api-client"; -import { useUserApi } from "~/composables/api"; +import { ref, Ref } from "@nuxtjs/composition-api"; import { RecipeCategory } from "~/lib/api/types/recipe"; +import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { usePublicExploreApi, useUserApi } from "~/composables/api"; -const categoryStore: Ref = ref([]); -const publicStoreLoading = ref(false); -const storeLoading = ref(false); +const store: Ref = ref([]); +const loading = ref(false); +const publicLoading = ref(false); -export function useCategoryData() { - const data = reactive({ +export const useCategoryData = function () { + return useData({ id: "", name: "", - slug: undefined, + slug: "", }); - - function reset() { - data.id = ""; - data.name = ""; - data.slug = undefined; - } - - return { - data, - reset, - }; -} - -export function usePublicCategoryStore(groupSlug: string) { - const api = usePublicExploreApi(groupSlug).explore; - const loading = publicStoreLoading; - - const actions = { - ...usePublicStoreActions(api.categories, categoryStore, loading), - flushStore() { - categoryStore.value = []; - }, - }; - - if (!loading.value && (!categoryStore.value || categoryStore.value?.length === 0)) { - actions.getAll(); - } - - return { - items: categoryStore, - actions, - loading, - }; } -export function useCategoryStore() { - // passing the group slug switches to using the public API +export const useCategoryStore = function () { const api = useUserApi(); - const loading = storeLoading; - - const actions = { - ...useStoreActions(api.categories, categoryStore, loading), - flushStore() { - categoryStore.value = []; - }, - }; - - if (!loading.value && (!categoryStore.value || categoryStore.value?.length === 0)) { - actions.getAll(); - } + return useStore(store, loading, api.categories); +} - return { - items: categoryStore, - actions, - loading, - }; +export const usePublicCategoryStore = function (groupSlug: string) { + const api = usePublicExploreApi(groupSlug).explore; + return usePublicStore(store, publicLoading, api.categories); } diff --git a/frontend/composables/store/use-food-store.ts b/frontend/composables/store/use-food-store.ts index 4b02210c382..cd3026c4d15 100644 --- a/frontend/composables/store/use-food-store.ts +++ b/frontend/composables/store/use-food-store.ts @@ -1,73 +1,28 @@ -import { ref, reactive, Ref } from "@nuxtjs/composition-api"; -import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory"; -import { usePublicExploreApi } from "../api/api-client"; -import { useUserApi } from "~/composables/api"; +import { ref, Ref } from "@nuxtjs/composition-api"; import { IngredientFood } from "~/lib/api/types/recipe"; +import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { usePublicExploreApi, useUserApi } from "~/composables/api"; -let foodStore: Ref = ref([]); -const publicStoreLoading = ref(false); -const storeLoading = ref(false); +const store: Ref = ref([]); +const loading = ref(false); +const publicLoading = ref(false); -/** - * useFoodData returns a template reactive object - * for managing the creation of foods. It also provides a - * function to reset the data back to the initial state. - */ export const useFoodData = function () { - const data: IngredientFood = reactive({ + return useData({ id: "", name: "", description: "", labelId: undefined, onHand: false, }); - - function reset() { - data.id = ""; - data.name = ""; - data.description = ""; - data.labelId = undefined; - data.onHand = false; - } - - return { - data, - reset, - }; -}; - -export const usePublicFoodStore = function (groupSlug: string) { - const api = usePublicExploreApi(groupSlug).explore; - const loading = publicStoreLoading; - - const actions = { - ...usePublicStoreActions(api.foods, foodStore, loading), - flushStore() { - foodStore = ref([]); - }, - }; - - if (!loading.value && (!foodStore.value || foodStore.value.length === 0)) { - foodStore = actions.getAll(); - } - - return { foods: foodStore, actions }; -}; +} export const useFoodStore = function () { const api = useUserApi(); - const loading = storeLoading; - - const actions = { - ...useStoreActions(api.foods, foodStore, loading), - flushStore() { - foodStore.value = []; - }, - }; + return useStore(store, loading, api.foods); +} - if (!loading.value && (!foodStore.value || foodStore.value.length === 0)) { - foodStore = actions.getAll(); - } - - return { foods: foodStore, actions }; -}; +export const usePublicFoodStore = function (groupSlug: string) { + const api = usePublicExploreApi(groupSlug).explore; + return usePublicStore(store, publicLoading, api.foods); +} diff --git a/frontend/composables/store/use-label-store.ts b/frontend/composables/store/use-label-store.ts index 72654d3b63a..fd7e6f2ba51 100644 --- a/frontend/composables/store/use-label-store.ts +++ b/frontend/composables/store/use-label-store.ts @@ -1,50 +1,21 @@ -import { reactive, ref, Ref } from "@nuxtjs/composition-api"; -import { useStoreActions } from "../partials/use-actions-factory"; +import { ref, Ref } from "@nuxtjs/composition-api"; import { MultiPurposeLabelOut } from "~/lib/api/types/labels"; +import { useData, useStore } from "../partials/use-store-factory"; import { useUserApi } from "~/composables/api"; -let labelStore: Ref = ref([]); -const storeLoading = ref(false); +const store: Ref = ref([]); +const loading = ref(false); -export function useLabelData() { - const data = reactive({ +export const useLabelData = function () { + return useData({ groupId: "", id: "", name: "", color: "", }); - - function reset() { - data.groupId = ""; - data.id = ""; - data.name = ""; - data.color = ""; - } - - return { - data, - reset, - }; } -export function useLabelStore() { +export const useLabelStore = function () { const api = useUserApi(); - const loading = storeLoading; - - const actions = { - ...useStoreActions(api.multiPurposeLabels, labelStore, loading), - flushStore() { - labelStore.value = []; - }, - }; - - if (!loading.value && (!labelStore.value || labelStore.value?.length === 0)) { - labelStore = actions.getAll(); - } - - return { - labels: labelStore, - actions, - loading, - }; + return useStore(store, loading, api.multiPurposeLabels); } diff --git a/frontend/composables/store/use-tag-store.ts b/frontend/composables/store/use-tag-store.ts index 395c8e48758..33f594d2fde 100644 --- a/frontend/composables/store/use-tag-store.ts +++ b/frontend/composables/store/use-tag-store.ts @@ -1,72 +1,26 @@ -import { reactive, ref, Ref } from "@nuxtjs/composition-api"; -import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory"; -import { usePublicExploreApi } from "../api/api-client"; -import { useUserApi } from "~/composables/api"; +import { ref, Ref } from "@nuxtjs/composition-api"; import { RecipeTag } from "~/lib/api/types/recipe"; +import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { usePublicExploreApi, useUserApi } from "~/composables/api"; -const items: Ref = ref([]); -const publicStoreLoading = ref(false); -const storeLoading = ref(false); +const store: Ref = ref([]); +const loading = ref(false); +const publicLoading = ref(false); -export function useTagData() { - const data = reactive({ +export const useTagData = function () { + return useData({ id: "", name: "", - slug: undefined, + slug: "", }); - - function reset() { - data.id = ""; - data.name = ""; - data.slug = undefined; - } - - return { - data, - reset, - }; -} - -export function usePublicTagStore(groupSlug: string) { - const api = usePublicExploreApi(groupSlug).explore; - const loading = publicStoreLoading; - - const actions = { - ...usePublicStoreActions(api.tags, items, loading), - flushStore() { - items.value = []; - }, - }; - - if (!loading.value && (!items.value || items.value?.length === 0)) { - actions.getAll(); - } - - return { - items, - actions, - loading, - }; } -export function useTagStore() { +export const useTagStore = function () { const api = useUserApi(); - const loading = storeLoading; - - const actions = { - ...useStoreActions(api.tags, items, loading), - flushStore() { - items.value = []; - }, - }; - - if (!loading.value && (!items.value || items.value?.length === 0)) { - actions.getAll(); - } + return useStore(store, loading, api.tags); +} - return { - items, - actions, - loading, - }; +export const usePublicTagStore = function (groupSlug: string) { + const api = usePublicExploreApi(groupSlug).explore; + return usePublicStore(store, publicLoading, api.tags); } diff --git a/frontend/composables/store/use-tool-store.ts b/frontend/composables/store/use-tool-store.ts index 7b14381e5bb..c5812518b11 100644 --- a/frontend/composables/store/use-tool-store.ts +++ b/frontend/composables/store/use-tool-store.ts @@ -1,74 +1,27 @@ -import { reactive, ref, Ref } from "@nuxtjs/composition-api"; -import { usePublicExploreApi } from "../api/api-client"; -import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory"; -import { useUserApi } from "~/composables/api"; +import { ref, Ref } from "@nuxtjs/composition-api"; import { RecipeTool } from "~/lib/api/types/recipe"; +import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { usePublicExploreApi, useUserApi } from "~/composables/api"; -const toolStore: Ref = ref([]); -const publicStoreLoading = ref(false); -const storeLoading = ref(false); +const store: Ref = ref([]); +const loading = ref(false); +const publicLoading = ref(false); -export function useToolData() { - const data = reactive({ +export const useToolData = function () { + return useData({ id: "", name: "", - slug: undefined, + slug: "", onHand: false, }); - - function reset() { - data.id = ""; - data.name = ""; - data.slug = undefined; - data.onHand = false; - } - - return { - data, - reset, - }; -} - -export function usePublicToolStore(groupSlug: string) { - const api = usePublicExploreApi(groupSlug).explore; - const loading = publicStoreLoading; - - const actions = { - ...usePublicStoreActions(api.tools, toolStore, loading), - flushStore() { - toolStore.value = []; - }, - }; - - if (!loading.value && (!toolStore.value || toolStore.value?.length === 0)) { - actions.getAll(); - } - - return { - items: toolStore, - actions, - loading, - }; } -export function useToolStore() { +export const useToolStore = function () { const api = useUserApi(); - const loading = storeLoading; - - const actions = { - ...useStoreActions(api.tools, toolStore, loading), - flushStore() { - toolStore.value = []; - }, - }; - - if (!loading.value && (!toolStore.value || toolStore.value?.length === 0)) { - actions.getAll(); - } + return useStore(store, loading, api.tools); +} - return { - items: toolStore, - actions, - loading, - }; +export const usePublicToolStore = function (groupSlug: string) { + const api = usePublicExploreApi(groupSlug).explore; + return usePublicStore(store, publicLoading, api.tools); } diff --git a/frontend/composables/store/use-unit-store.ts b/frontend/composables/store/use-unit-store.ts index 527a2ea7707..a9f4d3102bb 100644 --- a/frontend/composables/store/use-unit-store.ts +++ b/frontend/composables/store/use-unit-store.ts @@ -1,53 +1,22 @@ -import { ref, reactive, Ref } from "@nuxtjs/composition-api"; -import { useStoreActions } from "../partials/use-actions-factory"; -import { useUserApi } from "~/composables/api"; +import { ref, Ref } from "@nuxtjs/composition-api"; import { IngredientUnit } from "~/lib/api/types/recipe"; +import { useData, useStore } from "../partials/use-store-factory"; +import { useUserApi } from "~/composables/api"; -let unitStore: Ref = ref([]); -const storeLoading = ref(false); +const store: Ref = ref([]); +const loading = ref(false); -/** - * useUnitData returns a template reactive object - * for managing the creation of units. It also provides a - * function to reset the data back to the initial state. - */ export const useUnitData = function () { - const data: IngredientUnit = reactive({ + return useData({ id: "", name: "", fraction: true, abbreviation: "", description: "", }); - - function reset() { - data.id = ""; - data.name = ""; - data.fraction = true; - data.abbreviation = ""; - data.description = ""; - } - - return { - data, - reset, - }; -}; +} export const useUnitStore = function () { const api = useUserApi(); - const loading = storeLoading; - - const actions = { - ...useStoreActions(api.units, unitStore, loading), - flushStore() { - unitStore.value = []; - }, - }; - - if (!loading.value && (!unitStore.value || unitStore.value.length === 0)) { - unitStore = actions.getAll(); - } - - return { units: unitStore, actions }; -}; + return useStore(store, loading, api.units); +} diff --git a/frontend/pages/g/_groupSlug/r/_slug/ingredient-parser.vue b/frontend/pages/g/_groupSlug/r/_slug/ingredient-parser.vue index 357be89c18a..9aa4518e821 100644 --- a/frontend/pages/g/_groupSlug/r/_slug/ingredient-parser.vue +++ b/frontend/pages/g/_groupSlug/r/_slug/ingredient-parser.vue @@ -272,12 +272,10 @@ export default defineComponent({ const errors = ref([]); function checkForUnit(unit?: IngredientUnit | CreateIngredientUnit) { - // @ts-expect-error; we're just checking if there's an id on this unit and returning a boolean return !!unit?.id; } function checkForFood(food?: IngredientFood | CreateIngredientFood) { - // @ts-expect-error; we're just checking if there's an id on this food and returning a boolean return !!food?.id; } diff --git a/frontend/pages/g/_groupSlug/recipes/categories/index.vue b/frontend/pages/g/_groupSlug/recipes/categories/index.vue index 1cc94cf8681..452f672d8b7 100644 --- a/frontend/pages/g/_groupSlug/recipes/categories/index.vue +++ b/frontend/pages/g/_groupSlug/recipes/categories/index.vue @@ -1,8 +1,8 @@ - + {{ $t("data-pages.foods.seed-dialog-warning") }} @@ -196,7 +196,7 @@ () - const { labels: allLabels } = useLabelStore(); - const { units: allUnits } = useUnitStore(); - const { foods: allFoods } = useFoodStore(); + const { store: allLabels } = useLabelStore(); + const { store: allUnits } = useUnitStore(); + const { store: allFoods } = useFoodStore(); function getLabelColor(item: ShoppingListItemOut | null) { return item?.label?.color; From 22ffed67c0a457dfc5d5378d6aa7ffad4c138bf9 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:40:23 +0000 Subject: [PATCH 05/45] explicitly set data table sort --- frontend/components/Domain/Recipe/RecipeDataTable.vue | 2 ++ frontend/components/global/CrudTable.vue | 10 ++++++++++ frontend/pages/group/data/categories.vue | 1 + frontend/pages/group/data/foods.vue | 1 + frontend/pages/group/data/labels.vue | 1 + frontend/pages/group/data/recipe-actions.vue | 1 + frontend/pages/group/data/tags.vue | 1 + frontend/pages/group/data/tools.vue | 1 + frontend/pages/group/data/units.vue | 1 + 9 files changed, 19 insertions(+) diff --git a/frontend/components/Domain/Recipe/RecipeDataTable.vue b/frontend/components/Domain/Recipe/RecipeDataTable.vue index fd8900004ce..84d07ee6277 100644 --- a/frontend/components/Domain/Recipe/RecipeDataTable.vue +++ b/frontend/components/Domain/Recipe/RecipeDataTable.vue @@ -3,6 +3,8 @@ v-model="selected" item-key="id" show-select + sort-by="createdAt" + sort-desc :headers="headers" :items="recipes" :items-per-page="15" diff --git a/frontend/components/global/CrudTable.vue b/frontend/components/global/CrudTable.vue index caf25a803a2..1c7bfe3c9c2 100644 --- a/frontend/components/global/CrudTable.vue +++ b/frontend/components/global/CrudTable.vue @@ -44,6 +44,8 @@ item-key="id" :show-select="bulkActions.length > 0" :headers="activeHeaders" + :sort-by="initialSort" + :sort-desc="initialSortDesc" :items="data || []" :items-per-page="15" :search="search" @@ -126,6 +128,14 @@ export default defineComponent({ type: Array as () => BulkAction[], default: () => [], }, + initialSort: { + type: String, + default: "id", + }, + initialSortDesc: { + type: Boolean, + default: false, + }, }, setup(props, context) { // =========================================================== diff --git a/frontend/pages/group/data/categories.vue b/frontend/pages/group/data/categories.vue index 544338eac19..271ec53bacb 100644 --- a/frontend/pages/group/data/categories.vue +++ b/frontend/pages/group/data/categories.vue @@ -81,6 +81,7 @@ :headers.sync="tableHeaders" :data="categories || []" :bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]" + initial-sort="name" @delete-one="deleteEventHandler" @edit-one="editEventHandler" @delete-selected="bulkDeleteEventHandler" diff --git a/frontend/pages/group/data/foods.vue b/frontend/pages/group/data/foods.vue index e9bb759c3dd..7c4380eab52 100644 --- a/frontend/pages/group/data/foods.vue +++ b/frontend/pages/group/data/foods.vue @@ -241,6 +241,7 @@ {icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}, {icon: $globals.icons.tags, text: $tc('data-pages.labels.assign-label'), event: 'assign-selected'} ]" + initial-sort="name" @delete-one="deleteEventHandler" @edit-one="editEventHandler" @create-one="createEventHandler" diff --git a/frontend/pages/group/data/labels.vue b/frontend/pages/group/data/labels.vue index 68ad81e5839..d30af8c1838 100644 --- a/frontend/pages/group/data/labels.vue +++ b/frontend/pages/group/data/labels.vue @@ -115,6 +115,7 @@ :headers.sync="tableHeaders" :data="labels || []" :bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]" + initial-sort="name" @delete-one="deleteEventHandler" @edit-one="editEventHandler" @delete-selected="bulkDeleteEventHandler" diff --git a/frontend/pages/group/data/recipe-actions.vue b/frontend/pages/group/data/recipe-actions.vue index 2b4402dc8a9..6a753c520fc 100644 --- a/frontend/pages/group/data/recipe-actions.vue +++ b/frontend/pages/group/data/recipe-actions.vue @@ -101,6 +101,7 @@ :headers.sync="tableHeaders" :data="actions || []" :bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]" + initial-sort="title" @delete-one="deleteEventHandler" @edit-one="editEventHandler" @delete-selected="bulkDeleteEventHandler" diff --git a/frontend/pages/group/data/tags.vue b/frontend/pages/group/data/tags.vue index 90391ab535d..73cc4ddbcd1 100644 --- a/frontend/pages/group/data/tags.vue +++ b/frontend/pages/group/data/tags.vue @@ -81,6 +81,7 @@ :headers.sync="tableHeaders" :data="tags || []" :bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]" + initial-sort="name" @delete-one="deleteEventHandler" @edit-one="editEventHandler" @delete-selected="bulkDeleteEventHandler" diff --git a/frontend/pages/group/data/tools.vue b/frontend/pages/group/data/tools.vue index e4db5eaeb4e..ac769c1a2db 100644 --- a/frontend/pages/group/data/tools.vue +++ b/frontend/pages/group/data/tools.vue @@ -83,6 +83,7 @@ :headers.sync="tableHeaders" :data="tools || []" :bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]" + initial-sort="name" @delete-one="deleteEventHandler" @edit-one="editEventHandler" @delete-selected="bulkDeleteEventHandler" diff --git a/frontend/pages/group/data/units.vue b/frontend/pages/group/data/units.vue index 7d0af54baae..af2d347ed1e 100644 --- a/frontend/pages/group/data/units.vue +++ b/frontend/pages/group/data/units.vue @@ -198,6 +198,7 @@ :headers.sync="tableHeaders" :data="store" :bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]" + initial-sort="name" @delete-one="deleteEventHandler" @edit-one="editEventHandler" @create-one="createEventHandler" From 931b4963675fcc06ba05e856e597cf2a0b203859 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:45:00 +0000 Subject: [PATCH 06/45] fixed sort value --- frontend/components/Domain/Recipe/RecipeDataTable.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/Domain/Recipe/RecipeDataTable.vue b/frontend/components/Domain/Recipe/RecipeDataTable.vue index 84d07ee6277..68a9b1edd74 100644 --- a/frontend/components/Domain/Recipe/RecipeDataTable.vue +++ b/frontend/components/Domain/Recipe/RecipeDataTable.vue @@ -3,7 +3,7 @@ v-model="selected" item-key="id" show-select - sort-by="createdAt" + sort-by="dateAdded" sort-desc :headers="headers" :items="recipes" From e9f84ca6d1a8887bad9111d609ec9db1ab66de41 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:19:51 +0000 Subject: [PATCH 07/45] rename store classes --- frontend/composables/partials/use-actions-factory.ts | 10 +++++----- frontend/composables/partials/use-store-factory.ts | 6 +++--- frontend/composables/store/use-category-store.ts | 4 ++-- frontend/composables/store/use-food-store.ts | 4 ++-- frontend/composables/store/use-tag-store.ts | 4 ++-- frontend/composables/store/use-tool-store.ts | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/composables/partials/use-actions-factory.ts b/frontend/composables/partials/use-actions-factory.ts index 3069b7dc17c..b17b9ac6a3b 100644 --- a/frontend/composables/partials/use-actions-factory.ts +++ b/frontend/composables/partials/use-actions-factory.ts @@ -4,12 +4,12 @@ import { BoundT } from "./types"; import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; import { QueryValue } from "~/lib/api/base/route"; -interface PublicStoreActions { +interface ReadOnlyStoreActions { getAll(page?: number, perPage?: number, params?: any): Ref; refresh(): Promise; } -interface StoreActions extends PublicStoreActions { +interface StoreActions extends ReadOnlyStoreActions { createOne(createData: T): Promise; updateOne(updateData: T): Promise; deleteOne(id: string | number): Promise; @@ -17,16 +17,16 @@ interface StoreActions extends PublicStoreActions { /** - * usePublicStoreActions is a factory function that returns a set of methods + * useReadOnlyActions is a factory function that returns a set of methods * that can be reused to manage the state of a data store without using * Vuex. This is primarily used for basic GET/GETALL operations that required * a lot of refreshing hooks to be called on operations */ -export function usePublicStoreActions( +export function useReadOnlyActions( api: BaseCRUDAPIReadOnly, allRef: Ref | null, loading: Ref -): PublicStoreActions { +): ReadOnlyStoreActions { function getAll(page = 1, perPage = -1, params = {} as Record) { params.orderBy ??= "name"; params.orderDirection ??= "asc"; diff --git a/frontend/composables/partials/use-store-factory.ts b/frontend/composables/partials/use-store-factory.ts index 926d10e3a73..f4f80e14588 100644 --- a/frontend/composables/partials/use-store-factory.ts +++ b/frontend/composables/partials/use-store-factory.ts @@ -1,5 +1,5 @@ import { ref, reactive, Ref } from "@nuxtjs/composition-api"; -import { usePublicStoreActions, useStoreActions } from "./use-actions-factory"; +import { useReadOnlyActions, useStoreActions } from "./use-actions-factory"; import { BoundT } from "./types"; import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; @@ -12,13 +12,13 @@ export const useData = function(defaultObject: T) { return { data, reset }; } -export const usePublicStore = function( +export const useReadOnlyStore = function( store: Ref, loading: Ref, api: BaseCRUDAPIReadOnly, ) { const actions = { - ...usePublicStoreActions(api, store, loading), + ...useReadOnlyActions(api, store, loading), flushStore() { store.value = []; }, diff --git a/frontend/composables/store/use-category-store.ts b/frontend/composables/store/use-category-store.ts index 5b22d654021..e70d14196e5 100644 --- a/frontend/composables/store/use-category-store.ts +++ b/frontend/composables/store/use-category-store.ts @@ -1,6 +1,6 @@ import { ref, Ref } from "@nuxtjs/composition-api"; import { RecipeCategory } from "~/lib/api/types/recipe"; -import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory"; import { usePublicExploreApi, useUserApi } from "~/composables/api"; const store: Ref = ref([]); @@ -22,5 +22,5 @@ export const useCategoryStore = function () { export const usePublicCategoryStore = function (groupSlug: string) { const api = usePublicExploreApi(groupSlug).explore; - return usePublicStore(store, publicLoading, api.categories); + return useReadOnlyStore(store, publicLoading, api.categories); } diff --git a/frontend/composables/store/use-food-store.ts b/frontend/composables/store/use-food-store.ts index cd3026c4d15..1982fb0c2a7 100644 --- a/frontend/composables/store/use-food-store.ts +++ b/frontend/composables/store/use-food-store.ts @@ -1,6 +1,6 @@ import { ref, Ref } from "@nuxtjs/composition-api"; import { IngredientFood } from "~/lib/api/types/recipe"; -import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory"; import { usePublicExploreApi, useUserApi } from "~/composables/api"; const store: Ref = ref([]); @@ -24,5 +24,5 @@ export const useFoodStore = function () { export const usePublicFoodStore = function (groupSlug: string) { const api = usePublicExploreApi(groupSlug).explore; - return usePublicStore(store, publicLoading, api.foods); + return useReadOnlyStore(store, publicLoading, api.foods); } diff --git a/frontend/composables/store/use-tag-store.ts b/frontend/composables/store/use-tag-store.ts index 33f594d2fde..3d9d41faf0e 100644 --- a/frontend/composables/store/use-tag-store.ts +++ b/frontend/composables/store/use-tag-store.ts @@ -1,6 +1,6 @@ import { ref, Ref } from "@nuxtjs/composition-api"; import { RecipeTag } from "~/lib/api/types/recipe"; -import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory"; import { usePublicExploreApi, useUserApi } from "~/composables/api"; const store: Ref = ref([]); @@ -22,5 +22,5 @@ export const useTagStore = function () { export const usePublicTagStore = function (groupSlug: string) { const api = usePublicExploreApi(groupSlug).explore; - return usePublicStore(store, publicLoading, api.tags); + return useReadOnlyStore(store, publicLoading, api.tags); } diff --git a/frontend/composables/store/use-tool-store.ts b/frontend/composables/store/use-tool-store.ts index c5812518b11..38f848a7ada 100644 --- a/frontend/composables/store/use-tool-store.ts +++ b/frontend/composables/store/use-tool-store.ts @@ -1,6 +1,6 @@ import { ref, Ref } from "@nuxtjs/composition-api"; import { RecipeTool } from "~/lib/api/types/recipe"; -import { useData, usePublicStore, useStore } from "../partials/use-store-factory"; +import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory"; import { usePublicExploreApi, useUserApi } from "~/composables/api"; const store: Ref = ref([]); @@ -23,5 +23,5 @@ export const useToolStore = function () { export const usePublicToolStore = function (groupSlug: string) { const api = usePublicExploreApi(groupSlug).explore; - return usePublicStore(store, publicLoading, api.tools); + return useReadOnlyStore(store, publicLoading, api.tools); } From 1d4967da7209187c2c0b2e14cbf478f5b773e9b1 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:24:21 +0000 Subject: [PATCH 08/45] re-organized household routes --- .../explore/controller_public_households.py | 6 ++--- mealie/routes/groups/__init__.py | 2 ++ .../groups/controller_group_households.py | 27 +++++++++++++++++++ .../groups/controller_group_self_service.py | 8 ------ 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 mealie/routes/groups/controller_group_households.py diff --git a/mealie/routes/explore/controller_public_households.py b/mealie/routes/explore/controller_public_households.py index 3422096f43b..2edead21f8d 100644 --- a/mealie/routes/explore/controller_public_households.py +++ b/mealie/routes/explore/controller_public_households.py @@ -17,9 +17,7 @@ def households(self): @router.get("", response_model=PaginationBase[HouseholdSummary]) def get_all( - self, - q: PaginationQuery = Depends(make_dependable(PaginationQuery)), - search: str | None = None, + self, q: PaginationQuery = Depends(make_dependable(PaginationQuery)) ) -> PaginationBase[HouseholdSummary]: public_filter = "(preferences.private_household = FALSE)" if q.query_filter: @@ -27,7 +25,7 @@ def get_all( else: q.query_filter = public_filter - response = self.households.page_all(pagination=q, override=HouseholdSummary, search=search) + response = self.households.page_all(pagination=q, override=HouseholdSummary) response.set_pagination_guides(self.get_explore_url_path(router.url_path_for("get_all")), q.model_dump()) return response diff --git a/mealie/routes/groups/__init__.py b/mealie/routes/groups/__init__.py index c42b7915b77..93514285030 100644 --- a/mealie/routes/groups/__init__.py +++ b/mealie/routes/groups/__init__.py @@ -1,6 +1,7 @@ from fastapi import APIRouter from . import ( + controller_group_households, controller_group_reports, controller_group_self_service, controller_labels, @@ -10,6 +11,7 @@ router = APIRouter() +router.include_router(controller_group_households.router) router.include_router(controller_group_self_service.router) router.include_router(controller_migrations.router) router.include_router(controller_group_reports.router) diff --git a/mealie/routes/groups/controller_group_households.py b/mealie/routes/groups/controller_group_households.py new file mode 100644 index 00000000000..3fea64a69ff --- /dev/null +++ b/mealie/routes/groups/controller_group_households.py @@ -0,0 +1,27 @@ +from fastapi import Depends, HTTPException + +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 HouseholdSummary +from mealie.schema.response.pagination import PaginationBase, PaginationQuery + +router = UserAPIRouter(prefix="/groups/households", tags=["Groups: Households"]) + + +@controller(router) +class GroupHouseholdsController(BaseUserController): + @router.get("", response_model=PaginationBase[HouseholdSummary]) + def get_all_households(self, q: PaginationQuery = Depends(PaginationQuery)): + response = self.repos.households.page_all(pagination=q, override=HouseholdSummary) + + response.set_pagination_guides(router.url_path_for("get_all_households"), q.model_dump()) + return response + + @router.get("/{household_slug}", response_model=HouseholdSummary) + def get_one_household(self, household_slug: str): + household = self.repos.households.get_by_slug_or_id(household_slug) + + if not household: + raise HTTPException(status_code=404, detail="Household not found") + return household.cast(HouseholdSummary) diff --git a/mealie/routes/groups/controller_group_self_service.py b/mealie/routes/groups/controller_group_self_service.py index 0d6db70bfc5..2866329501c 100644 --- a/mealie/routes/groups/controller_group_self_service.py +++ b/mealie/routes/groups/controller_group_self_service.py @@ -8,7 +8,6 @@ from mealie.routes._base.routers import UserAPIRouter from mealie.schema.group.group_preferences import ReadGroupPreferences, UpdateGroupPreferences 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 @@ -35,13 +34,6 @@ def get_group_members(self, household_id: UUID4 | None = Query(None, alias="hous 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): return self.group.preferences From 4791f6303fe5cc984e58820d622c6a2a6f37a154 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:26:20 +0000 Subject: [PATCH 09/45] dev:generate --- tests/utils/api_routes/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/utils/api_routes/__init__.py b/tests/utils/api_routes/__init__.py index 09ddeb1af46..f28fcc95d5c 100644 --- a/tests/utils/api_routes/__init__.py +++ b/tests/utils/api_routes/__init__.py @@ -302,6 +302,11 @@ def foods_item_id(item_id): return f"{prefix}/foods/{item_id}" +def groups_households_household_slug(household_slug): + """`/api/groups/households/{household_slug}`""" + return f"{prefix}/groups/households/{household_slug}" + + def groups_labels_item_id(item_id): """`/api/groups/labels/{item_id}`""" return f"{prefix}/groups/labels/{item_id}" From d7ee9376cf938e7c42d21dfe95dfd2ccfcf25816 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:33:05 +0000 Subject: [PATCH 10/45] re-organized frontend household routes and migrated household CRUD to admin api --- frontend/composables/use-households.ts | 6 ++--- frontend/lib/api/admin/admin-households.ts | 13 +++++++++++ frontend/lib/api/client-admin.ts | 3 +++ frontend/lib/api/user/groups.ts | 6 ----- frontend/lib/api/user/households.ts | 9 ++++---- .../pages/admin/manage/households/_id.vue | 8 +++---- .../pages/admin/manage/households/index.vue | 4 ++-- frontend/pages/admin/manage/users/_id.vue | 4 ++-- frontend/pages/admin/manage/users/create.vue | 4 ++-- frontend/pages/admin/setup.vue | 23 ++++++++++--------- 10 files changed, 45 insertions(+), 35 deletions(-) create mode 100644 frontend/lib/api/admin/admin-households.ts diff --git a/frontend/composables/use-households.ts b/frontend/composables/use-households.ts index 618ab75636f..638e24a09df 100644 --- a/frontend/composables/use-households.ts +++ b/frontend/composables/use-households.ts @@ -1,5 +1,5 @@ import { computed, ref, Ref, useAsync } from "@nuxtjs/composition-api"; -import { useUserApi } from "~/composables/api"; +import { useAdminApi, useUserApi } from "~/composables/api"; import { HouseholdCreate, HouseholdInDB } from "~/lib/api/types/household"; const householdSelfRef = ref(null); @@ -44,8 +44,8 @@ export const useHouseholdSelf = function () { return { actions, household }; }; -export const useHouseholds = function () { - const api = useUserApi(); +export const useAdminHouseholds = function () { + const api = useAdminApi(); const loading = ref(false); function getAllHouseholds() { diff --git a/frontend/lib/api/admin/admin-households.ts b/frontend/lib/api/admin/admin-households.ts new file mode 100644 index 00000000000..1e49723b760 --- /dev/null +++ b/frontend/lib/api/admin/admin-households.ts @@ -0,0 +1,13 @@ +import { BaseCRUDAPI } from "../base/base-clients"; +import { HouseholdCreate, HouseholdInDB, UpdateHouseholdAdmin } from "~/lib/api/types/household"; +const prefix = "/api"; + +const routes = { + adminHouseholds: `${prefix}/admin/households`, + adminHouseholdsId: (id: string) => `${prefix}/admin/households/${id}`, +}; + +export class AdminHouseholdsApi extends BaseCRUDAPI { + baseRoute: string = routes.adminHouseholds; + itemRoute = routes.adminHouseholdsId; +} diff --git a/frontend/lib/api/client-admin.ts b/frontend/lib/api/client-admin.ts index a0bbca8f80f..bf151d390ed 100644 --- a/frontend/lib/api/client-admin.ts +++ b/frontend/lib/api/client-admin.ts @@ -1,5 +1,6 @@ import { AdminAboutAPI } from "./admin/admin-about"; import { AdminUsersApi } from "./admin/admin-users"; +import { AdminHouseholdsApi } from "./admin/admin-households"; import { AdminGroupsApi } from "./admin/admin-groups"; import { AdminBackupsApi } from "./admin/admin-backups"; import { AdminMaintenanceApi } from "./admin/admin-maintenance"; @@ -9,6 +10,7 @@ import { ApiRequestInstance } from "~/lib/api/types/non-generated"; export class AdminAPI { public about: AdminAboutAPI; public users: AdminUsersApi; + public households: AdminHouseholdsApi; public groups: AdminGroupsApi; public backups: AdminBackupsApi; public maintenance: AdminMaintenanceApi; @@ -17,6 +19,7 @@ export class AdminAPI { constructor(requests: ApiRequestInstance) { this.about = new AdminAboutAPI(requests); this.users = new AdminUsersApi(requests); + this.households = new AdminHouseholdsApi(requests); this.groups = new AdminGroupsApi(requests); this.backups = new AdminBackupsApi(requests); this.maintenance = new AdminMaintenanceApi(requests); diff --git a/frontend/lib/api/user/groups.ts b/frontend/lib/api/user/groups.ts index a43063988ce..fcd0a70d093 100644 --- a/frontend/lib/api/user/groups.ts +++ b/frontend/lib/api/user/groups.ts @@ -1,6 +1,5 @@ import { BaseCRUDAPI } from "../base/base-clients"; import { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user"; -import { HouseholdSummary } from "~/lib/api/types/household"; import { GroupAdminUpdate, GroupStorage, @@ -15,7 +14,6 @@ const routes = { groupsSelf: `${prefix}/groups/self`, preferences: `${prefix}/groups/preferences`, storage: `${prefix}/groups/storage`, - households: `${prefix}/households`, membersHouseholdId: (householdId: string | number | null) => { return householdId ? `${prefix}/households/members?householdId=${householdId}` : @@ -46,10 +44,6 @@ export class GroupAPI extends BaseCRUDAPI(routes.membersHouseholdId(householdId)); } - async fetchHouseholds() { - return await this.requests.get(routes.households); - } - async storage() { return await this.requests.get(routes.storage); } diff --git a/frontend/lib/api/user/households.ts b/frontend/lib/api/user/households.ts index b1909e78b08..861225102e3 100644 --- a/frontend/lib/api/user/households.ts +++ b/frontend/lib/api/user/households.ts @@ -1,15 +1,14 @@ -import { BaseCRUDAPI } from "../base/base-clients"; +import { BaseCRUDAPIReadOnly } from "../base/base-clients"; import { UserOut } from "~/lib/api/types/user"; import { - HouseholdCreate, HouseholdInDB, - UpdateHouseholdAdmin, HouseholdStatistics, ReadHouseholdPreferences, SetPermissions, UpdateHouseholdPreferences, CreateInviteToken, ReadInviteToken, + HouseholdSummary, } from "~/lib/api/types/household"; const prefix = "/api"; @@ -27,10 +26,10 @@ const routes = { householdsId: (id: string | number) => `${prefix}/admin/households/${id}`, }; -export class HouseholdAPI extends BaseCRUDAPI { +export class HouseholdAPI extends BaseCRUDAPIReadOnly { baseRoute = routes.households; itemRoute = routes.householdsId; - /** Returns the Group Data for the Current User + /** Returns the Household Data for the Current User */ async getCurrentUserHousehold() { return await this.requests.get(routes.householdsSelf); diff --git a/frontend/pages/admin/manage/households/_id.vue b/frontend/pages/admin/manage/households/_id.vue index 174ab7c1b10..1e6598df320 100644 --- a/frontend/pages/admin/manage/households/_id.vue +++ b/frontend/pages/admin/manage/households/_id.vue @@ -45,7 +45,7 @@ import { defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api"; import HouseholdPreferencesEditor from "~/components/Domain/Household/HouseholdPreferencesEditor.vue"; import { useGroups } from "~/composables/use-groups"; -import { useUserApi } from "~/composables/api"; +import { useAdminApi } from "~/composables/api"; import { alert } from "~/composables/use-toast"; import { validators } from "~/composables/use-validators"; import { HouseholdInDB } from "~/lib/api/types/household"; @@ -68,14 +68,14 @@ export default defineComponent({ const refHouseholdEditForm = ref(null); - const userApi = useUserApi(); + const adminApi = useAdminApi(); const household = ref(null); const userError = ref(false); onMounted(async () => { - const { data, error } = await userApi.households.getOne(householdId); + const { data, error } = await adminApi.households.getOne(householdId); if (error?.response?.status === 404) { alert.error(i18n.tc("user.user-not-found")); @@ -92,7 +92,7 @@ export default defineComponent({ return; } - const { response, data } = await userApi.households.updateOne(household.value.id, household.value); + const { response, data } = await adminApi.households.updateOne(household.value.id, household.value); if (response?.status === 200 && data) { if (household.value.slug !== data.slug) { // the slug updated, which invalidates the nav URLs diff --git a/frontend/pages/admin/manage/households/index.vue b/frontend/pages/admin/manage/households/index.vue index 02c36914c80..122ba20b192 100644 --- a/frontend/pages/admin/manage/households/index.vue +++ b/frontend/pages/admin/manage/households/index.vue @@ -88,7 +88,7 @@ import { defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api"; import { fieldTypes } from "~/composables/forms"; import { useGroups } from "~/composables/use-groups"; -import { useHouseholds } from "~/composables/use-households"; +import { useAdminHouseholds } from "~/composables/use-households"; import { validators } from "~/composables/use-validators"; import { HouseholdInDB } from "~/lib/api/types/household"; @@ -97,7 +97,7 @@ export default defineComponent({ setup() { const { i18n } = useContext(); const { groups } = useGroups(); - const { households, refreshAllHouseholds, deleteHousehold, createHousehold } = useHouseholds(); + const { households, refreshAllHouseholds, deleteHousehold, createHousehold } = useAdminHouseholds(); const state = reactive({ createDialog: false, diff --git a/frontend/pages/admin/manage/users/_id.vue b/frontend/pages/admin/manage/users/_id.vue index 6982658a668..eac8b5bf9a6 100644 --- a/frontend/pages/admin/manage/users/_id.vue +++ b/frontend/pages/admin/manage/users/_id.vue @@ -80,7 +80,7 @@ import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api"; import { useAdminApi, useUserApi } from "~/composables/api"; import { useGroups } from "~/composables/use-groups"; -import { useHouseholds } from "~/composables/use-households"; +import { useAdminHouseholds } from "~/composables/use-households"; import { alert } from "~/composables/use-toast"; import { useUserForm } from "~/composables/use-users"; import { validators } from "~/composables/use-validators"; @@ -92,7 +92,7 @@ export default defineComponent({ setup() { const { userForm } = useUserForm(); const { groups } = useGroups(); - const { useHouseholdsInGroup } = useHouseholds(); + const { useHouseholdsInGroup } = useAdminHouseholds(); const { i18n } = useContext(); const route = useRoute(); diff --git a/frontend/pages/admin/manage/users/create.vue b/frontend/pages/admin/manage/users/create.vue index 0d0e380e239..2dc0cc19af1 100644 --- a/frontend/pages/admin/manage/users/create.vue +++ b/frontend/pages/admin/manage/users/create.vue @@ -50,7 +50,7 @@ import { computed, defineComponent, useRouter, reactive, ref, toRefs, watch } from "@nuxtjs/composition-api"; import { useAdminApi } from "~/composables/api"; import { useGroups } from "~/composables/use-groups"; -import { useHouseholds } from "~/composables/use-households"; +import { useAdminHouseholds } from "~/composables/use-households"; import { useUserForm } from "~/composables/use-users"; import { validators } from "~/composables/use-validators"; import { VForm } from "~/types/vuetify"; @@ -60,7 +60,7 @@ export default defineComponent({ setup() { const { userForm } = useUserForm(); const { groups } = useGroups(); - const { useHouseholdsInGroup } = useHouseholds(); + const { useHouseholdsInGroup } = useAdminHouseholds(); const router = useRouter(); // ============================================== diff --git a/frontend/pages/admin/setup.vue b/frontend/pages/admin/setup.vue index b244dcdf13e..ef8e25c2a10 100644 --- a/frontend/pages/admin/setup.vue +++ b/frontend/pages/admin/setup.vue @@ -94,7 +94,7 @@ diff --git a/frontend/components/Domain/Household/GroupMealPlanRuleForm.vue b/frontend/components/Domain/Household/GroupMealPlanRuleForm.vue index cf71112a3ee..1f263eb3621 100644 --- a/frontend/components/Domain/Household/GroupMealPlanRuleForm.vue +++ b/frontend/components/Domain/Household/GroupMealPlanRuleForm.vue @@ -5,8 +5,15 @@ - - +
+ + + +
{{ $t('meal-plan.this-rule-will-apply', { @@ -18,11 +25,13 @@