diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f55202b5a3..6bab6e36317 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: exclude: ^tests/data/ - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.7.1 + rev: v0.7.2 hooks: - id: ruff - id: ruff-format diff --git a/docs/docs/documentation/getting-started/installation/backend-config.md b/docs/docs/documentation/getting-started/installation/backend-config.md index fdad87700a1..bd82a09a4db 100644 --- a/docs/docs/documentation/getting-started/installation/backend-config.md +++ b/docs/docs/documentation/getting-started/installation/backend-config.md @@ -61,6 +61,15 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea | --------------- | :-----: | ----------------------------------------------------------------------------- | | UVICORN_WORKERS | 1 | Sets the number of workers for the web server. [More info here][unicorn_workers] | +### TLS + +Use this only when mealie is run without a webserver or reverse proxy. + +| Variables | Default | Description | +| -------------------- | :-----: | ------------------------ | +| TLS_CERTIFICATE_PATH | None | File path to Certificate | +| TLS_PRIVATE_KEY_PATH | None | File path to private key | + ### LDAP | Variables | Default | Description | diff --git a/frontend/components/Domain/Cookbook/CookbookPage.vue b/frontend/components/Domain/Cookbook/CookbookPage.vue index 44486e70dba..5d771ad5289 100644 --- a/frontend/components/Domain/Cookbook/CookbookPage.vue +++ b/frontend/components/Domain/Cookbook/CookbookPage.vue @@ -7,7 +7,7 @@ width="100%" max-width="1100px" :icon="$globals.icons.pages" - :title="$t('general.edit')" + :title="$tc('general.edit')" :submit-icon="$globals.icons.save" :submit-text="$tc('general.save')" :submit-disabled="!editTarget.queryFilterString" @@ -25,7 +25,7 @@ {{ book.name }} { + if (!($auth.user && book.value?.householdId)) { + return false; + } + + return $auth.user.householdId === book.value.householdId; + }) + const canEdit = computed(() => isOwnGroup.value && isOwnHousehold.value); + const dialogStates = reactive({ edit: false, }); @@ -118,7 +127,7 @@ recipes, removeRecipe, replaceRecipes, - isOwnGroup, + canEdit, dialogStates, editTarget, handleEditCookbook, diff --git a/frontend/components/Layout/DefaultLayout.vue b/frontend/components/Layout/DefaultLayout.vue index 062a70ed350..5e1ea6ecadc 100644 --- a/frontend/components/Layout/DefaultLayout.vue +++ b/frontend/components/Layout/DefaultLayout.vue @@ -82,12 +82,17 @@ import { computed, defineComponent, onMounted, ref, useContext, useRoute } from import { useLoggedInState } from "~/composables/use-logged-in-state"; import AppHeader from "@/components/Layout/LayoutParts/AppHeader.vue"; import AppSidebar from "@/components/Layout/LayoutParts/AppSidebar.vue"; -import { SidebarLinks } from "~/types/application-types"; +import { SideBarLink } from "~/types/application-types"; import LanguageDialog from "~/components/global/LanguageDialog.vue"; import TheSnackbar from "@/components/Layout/LayoutParts/TheSnackbar.vue"; import { useAppInfo } from "~/composables/api"; import { useCookbooks, usePublicCookbooks } from "~/composables/use-group-cookbooks"; +import { useCookbookPreferences } from "~/composables/use-users/preferences"; +import { useHouseholdStore, usePublicHouseholdStore } from "~/composables/store/use-household-store"; import { useToggleDarkMode } from "~/composables/use-utils"; +import { ReadCookBook } from "~/lib/api/types/cookbook"; +import { HouseholdSummary } from "~/lib/api/types/household"; + export default defineComponent({ components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar }, @@ -99,6 +104,15 @@ export default defineComponent({ const route = useRoute(); const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); const { cookbooks } = isOwnGroup.value ? useCookbooks() : usePublicCookbooks(groupSlug.value || ""); + const cookbookPreferences = useCookbookPreferences(); + const { store: households } = isOwnGroup.value ? useHouseholdStore() : usePublicHouseholdStore(groupSlug.value || ""); + + const householdsById = computed(() => { + return households.value.reduce((acc, household) => { + acc[household.id] = household; + return acc; + }, {} as { [key: string]: HouseholdSummary }); + }); const appInfo = useAppInfo(); const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices); @@ -113,29 +127,57 @@ export default defineComponent({ sidebar.value = !$vuetify.breakpoint.md; }); - const cookbookLinks = computed(() => { - if (!cookbooks.value) return []; - return cookbooks.value.map((cookbook) => { - return { - key: cookbook.slug, - icon: $globals.icons.pages, - title: cookbook.name, - to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug as string}`, - }; + function cookbookAsLink(cookbook: ReadCookBook): SideBarLink { + return { + key: cookbook.slug || "", + icon: $globals.icons.pages, + title: cookbook.name, + to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug || ""}`, + restricted: false, + }; + } + + const currentUserHouseholdId = computed(() => $auth.user?.householdId); + const cookbookLinks = computed(() => { + if (!cookbooks.value) { + return []; + } + cookbooks.value.sort((a, b) => (a.position || 0) - (b.position || 0)); + + const ownLinks: SideBarLink[] = []; + const links: SideBarLink[] = []; + const cookbooksByHousehold = cookbooks.value.reduce((acc, cookbook) => { + const householdName = householdsById.value[cookbook.householdId]?.name || ""; + if (!acc[householdName]) { + acc[householdName] = []; + } + acc[householdName].push(cookbook); + return acc; + }, {} as Record); + + Object.entries(cookbooksByHousehold).forEach(([householdName, cookbooks]) => { + if (cookbooks[0].householdId === currentUserHouseholdId.value) { + ownLinks.push(...cookbooks.map(cookbookAsLink)); + } else { + links.push({ + key: householdName, + icon: $globals.icons.book, + title: householdName, + children: cookbooks.map(cookbookAsLink), + restricted: false, + }); + } }); - }); - interface Link { - insertDivider: boolean; - icon: string; - title: string; - subtitle: string | null; - to: string; - restricted: boolean; - hide: boolean; - } + links.sort((a, b) => a.title.localeCompare(b.title)); + if ($auth.user && cookbookPreferences.value.hideOtherHouseholds) { + return ownLinks; + } else { + return [...ownLinks, ...links]; + } + }); - const createLinks = computed(() => [ + const createLinks = computed(() => [ { insertDivider: false, icon: $globals.icons.link, @@ -165,7 +207,7 @@ export default defineComponent({ }, ]); - const bottomLinks = computed(() => [ + const bottomLinks = computed(() => [ { icon: $globals.icons.cog, title: i18n.tc("general.settings"), @@ -174,7 +216,7 @@ export default defineComponent({ }, ]); - const topLinks = computed(() => [ + const topLinks = computed(() => [ { icon: $globals.icons.silverwareForkKnife, to: `/g/${groupSlug.value}`, diff --git a/frontend/components/Layout/LayoutParts/AppSidebar.vue b/frontend/components/Layout/LayoutParts/AppSidebar.vue index d6c5597f919..ab3e3fb8365 100644 --- a/frontend/components/Layout/LayoutParts/AppSidebar.vue +++ b/frontend/components/Layout/LayoutParts/AppSidebar.vue @@ -135,7 +135,7 @@