mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-23 14:55:24 -07:00
Merge branch 'mealie-next' into mealie-next
This commit is contained in:
commit
782cdcfc77
22 changed files with 471 additions and 147 deletions
|
@ -12,7 +12,7 @@ repos:
|
||||||
exclude: ^tests/data/
|
exclude: ^tests/data/
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.7.1
|
rev: v0.7.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
|
|
|
@ -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] |
|
| 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
|
### LDAP
|
||||||
|
|
||||||
| Variables | Default | Description |
|
| Variables | Default | Description |
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
width="100%"
|
width="100%"
|
||||||
max-width="1100px"
|
max-width="1100px"
|
||||||
:icon="$globals.icons.pages"
|
:icon="$globals.icons.pages"
|
||||||
:title="$t('general.edit')"
|
:title="$tc('general.edit')"
|
||||||
:submit-icon="$globals.icons.save"
|
:submit-icon="$globals.icons.save"
|
||||||
:submit-text="$tc('general.save')"
|
:submit-text="$tc('general.save')"
|
||||||
:submit-disabled="!editTarget.queryFilterString"
|
:submit-disabled="!editTarget.queryFilterString"
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<v-toolbar-title class="headline"> {{ book.name }} </v-toolbar-title>
|
<v-toolbar-title class="headline"> {{ book.name }} </v-toolbar-title>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="isOwnGroup"
|
v-if="canEdit"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
:edit="true"
|
:edit="true"
|
||||||
@click="handleEditCookbook"
|
@click="handleEditCookbook"
|
||||||
|
@ -79,6 +79,15 @@
|
||||||
const tab = ref(null);
|
const tab = ref(null);
|
||||||
const book = getOne(slug);
|
const book = getOne(slug);
|
||||||
|
|
||||||
|
const isOwnHousehold = computed(() => {
|
||||||
|
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({
|
const dialogStates = reactive({
|
||||||
edit: false,
|
edit: false,
|
||||||
});
|
});
|
||||||
|
@ -118,7 +127,7 @@
|
||||||
recipes,
|
recipes,
|
||||||
removeRecipe,
|
removeRecipe,
|
||||||
replaceRecipes,
|
replaceRecipes,
|
||||||
isOwnGroup,
|
canEdit,
|
||||||
dialogStates,
|
dialogStates,
|
||||||
editTarget,
|
editTarget,
|
||||||
handleEditCookbook,
|
handleEditCookbook,
|
||||||
|
|
|
@ -82,12 +82,17 @@ import { computed, defineComponent, onMounted, ref, useContext, useRoute } from
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import AppHeader from "@/components/Layout/LayoutParts/AppHeader.vue";
|
import AppHeader from "@/components/Layout/LayoutParts/AppHeader.vue";
|
||||||
import AppSidebar from "@/components/Layout/LayoutParts/AppSidebar.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 LanguageDialog from "~/components/global/LanguageDialog.vue";
|
||||||
import TheSnackbar from "@/components/Layout/LayoutParts/TheSnackbar.vue";
|
import TheSnackbar from "@/components/Layout/LayoutParts/TheSnackbar.vue";
|
||||||
import { useAppInfo } from "~/composables/api";
|
import { useAppInfo } from "~/composables/api";
|
||||||
import { useCookbooks, usePublicCookbooks } from "~/composables/use-group-cookbooks";
|
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 { useToggleDarkMode } from "~/composables/use-utils";
|
||||||
|
import { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||||
|
import { HouseholdSummary } from "~/lib/api/types/household";
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar },
|
components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar },
|
||||||
|
@ -99,6 +104,15 @@ export default defineComponent({
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
const { cookbooks } = isOwnGroup.value ? useCookbooks() : usePublicCookbooks(groupSlug.value || "");
|
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 appInfo = useAppInfo();
|
||||||
const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices);
|
const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices);
|
||||||
|
@ -113,29 +127,57 @@ export default defineComponent({
|
||||||
sidebar.value = !$vuetify.breakpoint.md;
|
sidebar.value = !$vuetify.breakpoint.md;
|
||||||
});
|
});
|
||||||
|
|
||||||
const cookbookLinks = computed(() => {
|
function cookbookAsLink(cookbook: ReadCookBook): SideBarLink {
|
||||||
if (!cookbooks.value) return [];
|
return {
|
||||||
return cookbooks.value.map((cookbook) => {
|
key: cookbook.slug || "",
|
||||||
return {
|
icon: $globals.icons.pages,
|
||||||
key: cookbook.slug,
|
title: cookbook.name,
|
||||||
icon: $globals.icons.pages,
|
to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug || ""}`,
|
||||||
title: cookbook.name,
|
restricted: false,
|
||||||
to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug as string}`,
|
};
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Link {
|
|
||||||
insertDivider: boolean;
|
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string | null;
|
|
||||||
to: string;
|
|
||||||
restricted: boolean;
|
|
||||||
hide: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createLinks = computed<Link[]>(() => [
|
const currentUserHouseholdId = computed(() => $auth.user?.householdId);
|
||||||
|
const cookbookLinks = computed<SideBarLink[]>(() => {
|
||||||
|
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<string, ReadCookBook[]>);
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
links.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
if ($auth.user && cookbookPreferences.value.hideOtherHouseholds) {
|
||||||
|
return ownLinks;
|
||||||
|
} else {
|
||||||
|
return [...ownLinks, ...links];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const createLinks = computed<SideBarLink[]>(() => [
|
||||||
{
|
{
|
||||||
insertDivider: false,
|
insertDivider: false,
|
||||||
icon: $globals.icons.link,
|
icon: $globals.icons.link,
|
||||||
|
@ -165,7 +207,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const bottomLinks = computed<SidebarLinks>(() => [
|
const bottomLinks = computed<SideBarLink[]>(() => [
|
||||||
{
|
{
|
||||||
icon: $globals.icons.cog,
|
icon: $globals.icons.cog,
|
||||||
title: i18n.tc("general.settings"),
|
title: i18n.tc("general.settings"),
|
||||||
|
@ -174,7 +216,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const topLinks = computed<SidebarLinks>(() => [
|
const topLinks = computed<SideBarLink[]>(() => [
|
||||||
{
|
{
|
||||||
icon: $globals.icons.silverwareForkKnife,
|
icon: $globals.icons.silverwareForkKnife,
|
||||||
to: `/g/${groupSlug.value}`,
|
to: `/g/${groupSlug.value}`,
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, reactive, toRefs, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, toRefs, useContext, watch } from "@nuxtjs/composition-api";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { SidebarLinks } from "~/types/application-types";
|
import { SidebarLinks } from "~/types/application-types";
|
||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||||
|
@ -192,13 +192,29 @@ export default defineComponent({
|
||||||
const userProfileLink = computed(() => $auth.user ? "/user/profile" : undefined);
|
const userProfileLink = computed(() => $auth.user ? "/user/profile" : undefined);
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dropDowns: {},
|
dropDowns: {} as Record<string, boolean>,
|
||||||
topSelected: null as string[] | null,
|
topSelected: null as string[] | null,
|
||||||
secondarySelected: null as string[] | null,
|
secondarySelected: null as string[] | null,
|
||||||
bottomSelected: null as string[] | null,
|
bottomSelected: null as string[] | null,
|
||||||
hasOpenedBefore: false as boolean,
|
hasOpenedBefore: false as boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const allLinks = computed(() => [...props.topLink, ...(props.secondaryLinks || []), ...(props.bottomLinks || [])]);
|
||||||
|
function initDropdowns() {
|
||||||
|
allLinks.value.forEach((link) => {
|
||||||
|
state.dropDowns[link.title] = link.childrenStartExpanded || false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => allLinks,
|
||||||
|
() => {
|
||||||
|
initDropdowns();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
userFavoritesLink,
|
userFavoritesLink,
|
||||||
|
|
|
@ -99,10 +99,10 @@ export const useCookbooks = function () {
|
||||||
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
},
|
},
|
||||||
async createOne() {
|
async createOne(name: string | null = null) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await api.cookbooks.createOne({
|
const { data } = await api.cookbooks.createOne({
|
||||||
name: i18n.t("cookbook.household-cookbook-name", [household.value?.name || "", String((cookbookStore?.value?.length ?? 0) + 1)]) as string,
|
name: name || i18n.t("cookbook.household-cookbook-name", [household.value?.name || "", String((cookbookStore?.value?.length ?? 0) + 1)]) as string,
|
||||||
position: (cookbookStore?.value?.length ?? 0) + 1,
|
position: (cookbookStore?.value?.length ?? 0) + 1,
|
||||||
queryFilterString: "",
|
queryFilterString: "",
|
||||||
});
|
});
|
||||||
|
@ -129,18 +129,18 @@ export const useCookbooks = function () {
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateOrder() {
|
async updateOrder(cookbooks: ReadCookBook[]) {
|
||||||
if (!cookbookStore?.value) {
|
if (!cookbooks?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
cookbookStore.value.forEach((element, index) => {
|
cookbooks.forEach((element, index) => {
|
||||||
element.position = index + 1;
|
element.position = index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data } = await api.cookbooks.updateAll(cookbookStore.value);
|
const { data } = await api.cookbooks.updateAll(cookbooks);
|
||||||
|
|
||||||
if (data && cookbookStore?.value) {
|
if (data && cookbookStore?.value) {
|
||||||
this.refreshAll();
|
this.refreshAll();
|
||||||
|
|
|
@ -45,6 +45,10 @@ export interface UserParsingPreferences {
|
||||||
parser: RegisteredParser;
|
parser: RegisteredParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserCookbooksPreferences {
|
||||||
|
hideOtherHouseholds: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function useUserMealPlanPreferences(): Ref<UserMealPlanPreferences> {
|
export function useUserMealPlanPreferences(): Ref<UserMealPlanPreferences> {
|
||||||
const fromStorage = useLocalStorage(
|
const fromStorage = useLocalStorage(
|
||||||
"meal-planner-preferences",
|
"meal-planner-preferences",
|
||||||
|
@ -153,3 +157,17 @@ export function useParsingPreferences(): Ref<UserParsingPreferences> {
|
||||||
|
|
||||||
return fromStorage;
|
return fromStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useCookbookPreferences(): Ref<UserCookbooksPreferences> {
|
||||||
|
const fromStorage = useLocalStorage(
|
||||||
|
"cookbook-preferences",
|
||||||
|
{
|
||||||
|
hideOtherHouseholds: false,
|
||||||
|
},
|
||||||
|
{ mergeDefaults: true }
|
||||||
|
// we cast to a Ref because by default it will return an optional type ref
|
||||||
|
// but since we pass defaults we know all properties are set.
|
||||||
|
) as unknown as Ref<UserCookbooksPreferences>;
|
||||||
|
|
||||||
|
return fromStorage;
|
||||||
|
}
|
||||||
|
|
|
@ -1327,6 +1327,8 @@
|
||||||
"cookbook": {
|
"cookbook": {
|
||||||
"cookbooks": "Cookbooks",
|
"cookbooks": "Cookbooks",
|
||||||
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes, organizers, and other filters. Creating a cookbook will add an entry to the side-bar and all the recipes with the filters chosen will be displayed in the cookbook.",
|
"description": "Cookbooks are another way to organize recipes by creating cross sections of recipes, organizers, and other filters. Creating a cookbook will add an entry to the side-bar and all the recipes with the filters chosen will be displayed in the cookbook.",
|
||||||
|
"hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households",
|
||||||
|
"hide-cookbooks-from-other-households-description": "When enabled, only cookbooks from your household will appear on the sidebar",
|
||||||
"public-cookbook": "Public Cookbook",
|
"public-cookbook": "Public Cookbook",
|
||||||
"public-cookbook-description": "Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.",
|
"public-cookbook-description": "Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.",
|
||||||
"filter-options": "Filter Options",
|
"filter-options": "Filter Options",
|
||||||
|
|
|
@ -48,20 +48,33 @@
|
||||||
{{ $t('cookbook.description') }}
|
{{ $t('cookbook.description') }}
|
||||||
</BasePageTitle>
|
</BasePageTitle>
|
||||||
|
|
||||||
|
<div class="my-6">
|
||||||
|
<v-checkbox
|
||||||
|
v-model="cookbookPreferences.hideOtherHouseholds"
|
||||||
|
:label="$tc('cookbook.hide-cookbooks-from-other-households')"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
<div class="ml-8">
|
||||||
|
<p class="text-subtitle-2 my-0 py-0">
|
||||||
|
{{ $tc("cookbook.hide-cookbooks-from-other-households-description") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Create New -->
|
<!-- Create New -->
|
||||||
<BaseButton create @click="createCookbook" />
|
<BaseButton create @click="createCookbook" />
|
||||||
|
|
||||||
<!-- Cookbook List -->
|
<!-- Cookbook List -->
|
||||||
<v-expansion-panels class="mt-2">
|
<v-expansion-panels class="mt-2">
|
||||||
<draggable
|
<draggable
|
||||||
v-model="cookbooks"
|
v-model="myCookbooks"
|
||||||
handle=".handle"
|
handle=".handle"
|
||||||
delay="250"
|
delay="250"
|
||||||
:delay-on-touch-only="true"
|
:delay-on-touch-only="true"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="actions.updateOrder()"
|
@change="actions.updateOrder(myCookbooks)"
|
||||||
>
|
>
|
||||||
<v-expansion-panel v-for="cookbook in cookbooks" :key="cookbook.id" class="my-2 left-border rounded">
|
<v-expansion-panel v-for="cookbook in myCookbooks" :key="cookbook.id" class="my-2 left-border rounded">
|
||||||
<v-expansion-panel-header disable-icon-rotate class="headline">
|
<v-expansion-panel-header disable-icon-rotate class="headline">
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<v-icon large left>
|
<v-icon large left>
|
||||||
|
@ -110,11 +123,13 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent, onBeforeUnmount, onMounted, reactive, ref } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, onBeforeUnmount, onMounted, reactive, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
||||||
|
import { useHouseholdSelf } from "@/composables/use-households";
|
||||||
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
|
||||||
import { ReadCookBook } from "~/lib/api/types/cookbook";
|
import { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||||
|
import { useCookbookPreferences } from "~/composables/use-users/preferences";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { CookbookEditor, draggable },
|
components: { CookbookEditor, draggable },
|
||||||
|
@ -124,13 +139,28 @@ export default defineComponent({
|
||||||
create: false,
|
create: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
});
|
});
|
||||||
const { cookbooks, actions } = useCookbooks();
|
|
||||||
|
const { $auth, i18n } = useContext();
|
||||||
|
const { cookbooks: allCookbooks, actions } = useCookbooks();
|
||||||
|
const myCookbooks = computed<ReadCookBook[]>({
|
||||||
|
get: () => {
|
||||||
|
return allCookbooks.value?.filter((cookbook) => {
|
||||||
|
return cookbook.householdId === $auth.user?.householdId;
|
||||||
|
}) || [];
|
||||||
|
},
|
||||||
|
set: (value: ReadCookBook[]) => {
|
||||||
|
actions.updateOrder(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { household } = useHouseholdSelf();
|
||||||
|
const cookbookPreferences = useCookbookPreferences()
|
||||||
|
|
||||||
// create
|
// create
|
||||||
const createTargetKey = ref(0);
|
const createTargetKey = ref(0);
|
||||||
const createTarget = ref<ReadCookBook | null>(null);
|
const createTarget = ref<ReadCookBook | null>(null);
|
||||||
async function createCookbook() {
|
async function createCookbook() {
|
||||||
await actions.createOne().then((cookbook) => {
|
const name = i18n.t("cookbook.household-cookbook-name", [household.value?.name || "", String((myCookbooks.value?.length ?? 0) + 1)]) as string
|
||||||
|
await actions.createOne(name).then((cookbook) => {
|
||||||
createTarget.value = cookbook as ReadCookBook;
|
createTarget.value = cookbook as ReadCookBook;
|
||||||
createTargetKey.value++;
|
createTargetKey.value++;
|
||||||
});
|
});
|
||||||
|
@ -177,7 +207,8 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cookbooks,
|
myCookbooks,
|
||||||
|
cookbookPreferences,
|
||||||
actions,
|
actions,
|
||||||
dialogStates,
|
dialogStates,
|
||||||
// create
|
// create
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
<!-- View By Label -->
|
<!-- View By Label -->
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-for="(value, key) in itemsByLabel" :key="key" class="mb-6">
|
<div v-for="(value, key) in itemsByLabel" :key="key" class="pb-4">
|
||||||
<v-btn
|
<v-btn
|
||||||
:color="getLabelColor(value[0]) ? getLabelColor(value[0]) : '#959595'"
|
:color="getLabelColor(value[0]) ? getLabelColor(value[0]) : '#959595'"
|
||||||
:style="{
|
:style="{
|
||||||
|
@ -73,20 +73,20 @@
|
||||||
<v-divider/>
|
<v-divider/>
|
||||||
<v-expand-transition group>
|
<v-expand-transition group>
|
||||||
<div v-show="labelOpenState[key]">
|
<div v-show="labelOpenState[key]">
|
||||||
<draggable :value="value" handle=".handle" delay="250" :delay-on-touch-only="true" @start="loadingCounter += 1" @end="loadingCounter -= 1" @input="updateIndexUncheckedByLabel(key, $event)">
|
<draggable :value="value" handle=".handle" delay="250" :delay-on-touch-only="true" @start="loadingCounter += 1" @end="loadingCounter -= 1" @input="updateIndexUncheckedByLabel(key, $event)">
|
||||||
<v-lazy v-for="(item, index) in value" :key="item.id" class="ml-2 my-2">
|
<v-lazy v-for="(item, index) in value" :key="item.id" class="ml-2 my-2">
|
||||||
<ShoppingListItem
|
<ShoppingListItem
|
||||||
v-model="value[index]"
|
v-model="value[index]"
|
||||||
:show-label=false
|
:show-label=false
|
||||||
:labels="allLabels || []"
|
:labels="allLabels || []"
|
||||||
:units="allUnits || []"
|
:units="allUnits || []"
|
||||||
:foods="allFoods || []"
|
:foods="allFoods || []"
|
||||||
:recipes="recipeMap"
|
:recipes="recipeMap"
|
||||||
@checked="saveListItem"
|
@checked="saveListItem"
|
||||||
@save="saveListItem"
|
@save="saveListItem"
|
||||||
@delete="deleteListItem(item)"
|
@delete="deleteListItem(item)"
|
||||||
/>
|
/>
|
||||||
</v-lazy>
|
</v-lazy>
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
</v-expand-transition>
|
</v-expand-transition>
|
||||||
|
@ -470,7 +470,7 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
// =====================================
|
// =====================================
|
||||||
// Collapsables
|
// Collapsable Labels
|
||||||
const labelOpenState = ref<{ [key: string]: boolean }>({});
|
const labelOpenState = ref<{ [key: string]: boolean }>({});
|
||||||
|
|
||||||
const initializeLabelOpenStates = () => {
|
const initializeLabelOpenStates = () => {
|
||||||
|
@ -480,8 +480,8 @@ export default defineComponent({
|
||||||
let hasChanges = false;
|
let hasChanges = false;
|
||||||
|
|
||||||
for (const item of shoppingList.value.listItems) {
|
for (const item of shoppingList.value.listItems) {
|
||||||
const labelName = item.label?.name;
|
const labelName = item.label?.name || i18n.tc("shopping-list.no-label");
|
||||||
if (labelName && !existingLabels.has(labelName) && !(labelName in labelOpenState.value)) {
|
if (!existingLabels.has(labelName) && !(labelName in labelOpenState.value)) {
|
||||||
labelOpenState.value[labelName] = true;
|
labelOpenState.value[labelName] = true;
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
|
@ -492,9 +492,13 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const labelNames = computed(() =>
|
const labelNames = computed(() => {
|
||||||
new Set(shoppingList.value?.listItems?.map(item => item.label?.name).filter(Boolean) ?? [])
|
return new Set(
|
||||||
);
|
shoppingList.value?.listItems
|
||||||
|
?.map(item => item.label?.name || i18n.tc("shopping-list.no-label"))
|
||||||
|
.filter(Boolean) ?? []
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
watch(labelNames, initializeLabelOpenStates, { immediate: true });
|
watch(labelNames, initializeLabelOpenStates, { immediate: true });
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ export interface SideBarLink {
|
||||||
href?: string;
|
href?: string;
|
||||||
title: string;
|
title: string;
|
||||||
children?: SideBarLink[];
|
children?: SideBarLink[];
|
||||||
|
childrenStartExpanded?: boolean;
|
||||||
restricted: boolean;
|
restricted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -353,6 +353,15 @@ class AppSettings(AppLoggingSettings):
|
||||||
|
|
||||||
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow")
|
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow")
|
||||||
|
|
||||||
|
# ===============================================
|
||||||
|
# TLS
|
||||||
|
|
||||||
|
TLS_CERTIFICATE_PATH: str | os.PathLike[str] | None = None
|
||||||
|
"""Path where the certificate resides."""
|
||||||
|
|
||||||
|
TLS_PRIVATE_KEY_PATH: str | os.PathLike[str] | None = None
|
||||||
|
"""Path where the private key resides."""
|
||||||
|
|
||||||
|
|
||||||
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -151,6 +151,14 @@ class User(SqlAlchemyBase, BaseMixins):
|
||||||
else:
|
else:
|
||||||
self.household = None
|
self.household = None
|
||||||
|
|
||||||
|
if self.group is None:
|
||||||
|
raise ValueError(f"Group {group} does not exist; cannot create user")
|
||||||
|
if self.household is None:
|
||||||
|
raise ValueError(
|
||||||
|
f'Household "{household}" does not exist on group '
|
||||||
|
f'"{self.group.name}" ({self.group.id}); cannot create user'
|
||||||
|
)
|
||||||
|
|
||||||
self.rated_recipes = []
|
self.rated_recipes = []
|
||||||
|
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
|
@ -13,6 +13,8 @@ def main():
|
||||||
log_config=log_config(),
|
log_config=log_config(),
|
||||||
workers=settings.WORKERS,
|
workers=settings.WORKERS,
|
||||||
forwarded_allow_ips=settings.HOST_IP,
|
forwarded_allow_ips=settings.HOST_IP,
|
||||||
|
ssl_keyfile=settings.TLS_PRIVATE_KEY_PATH,
|
||||||
|
ssl_certfile=settings.TLS_CERTIFICATE_PATH,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from collections.abc import Callable
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
from typing import Generic, TypeVar
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
|
import sqlalchemy.exc
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from pydantic import UUID4, BaseModel
|
from pydantic import UUID4, BaseModel
|
||||||
|
|
||||||
|
@ -57,10 +58,16 @@ class HttpRepo(Generic[C, R, U]):
|
||||||
# Respond
|
# Respond
|
||||||
msg = self.get_exception_message(ex)
|
msg = self.get_exception_message(ex)
|
||||||
|
|
||||||
raise HTTPException(
|
if isinstance(ex, sqlalchemy.exc.NoResultFound):
|
||||||
status.HTTP_400_BAD_REQUEST,
|
raise HTTPException(
|
||||||
detail=ErrorResponse.respond(message=msg, exception=str(ex)),
|
status.HTTP_404_NOT_FOUND,
|
||||||
)
|
detail=ErrorResponse.respond(message=msg, exception=str(ex)),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ErrorResponse.respond(message=msg, exception=str(ex)),
|
||||||
|
)
|
||||||
|
|
||||||
def create_one(self, data: C) -> R | None:
|
def create_one(self, data: C) -> R | None:
|
||||||
item: R | None = None
|
item: R | None = None
|
||||||
|
|
|
@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.core.exceptions import mealie_registered_exceptions
|
from mealie.core.exceptions import mealie_registered_exceptions
|
||||||
|
from mealie.repos.all_repositories import get_repositories
|
||||||
from mealie.routes._base import BaseCrudController, controller
|
from mealie.routes._base import BaseCrudController, controller
|
||||||
from mealie.routes._base.mixins import HttpRepo
|
from mealie.routes._base.mixins import HttpRepo
|
||||||
from mealie.routes._base.routers import MealieCrudRoute
|
from mealie.routes._base.routers import MealieCrudRoute
|
||||||
|
@ -26,9 +27,13 @@ router = APIRouter(prefix="/households/cookbooks", tags=["Households: Cookbooks"
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class GroupCookbookController(BaseCrudController):
|
class GroupCookbookController(BaseCrudController):
|
||||||
@cached_property
|
@cached_property
|
||||||
def repo(self):
|
def cookbooks(self):
|
||||||
return self.repos.cookbooks
|
return self.repos.cookbooks
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def group_cookbooks(self):
|
||||||
|
return get_repositories(self.session, group_id=self.group_id, household_id=None).cookbooks
|
||||||
|
|
||||||
def registered_exceptions(self, ex: type[Exception]) -> str:
|
def registered_exceptions(self, ex: type[Exception]) -> str:
|
||||||
registered = {
|
registered = {
|
||||||
**mealie_registered_exceptions(self.translator),
|
**mealie_registered_exceptions(self.translator),
|
||||||
|
@ -38,14 +43,15 @@ class GroupCookbookController(BaseCrudController):
|
||||||
@cached_property
|
@cached_property
|
||||||
def mixins(self):
|
def mixins(self):
|
||||||
return HttpRepo[CreateCookBook, ReadCookBook, UpdateCookBook](
|
return HttpRepo[CreateCookBook, ReadCookBook, UpdateCookBook](
|
||||||
self.repo,
|
self.cookbooks,
|
||||||
self.logger,
|
self.logger,
|
||||||
self.registered_exceptions,
|
self.registered_exceptions,
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get("", response_model=CookBookPagination)
|
@router.get("", response_model=CookBookPagination)
|
||||||
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
||||||
response = self.repo.page_all(
|
# Fetch all cookbooks for the group, rather than the household
|
||||||
|
response = self.group_cookbooks.page_all(
|
||||||
pagination=q,
|
pagination=q,
|
||||||
override=ReadCookBook,
|
override=ReadCookBook,
|
||||||
)
|
)
|
||||||
|
@ -106,7 +112,8 @@ class GroupCookbookController(BaseCrudController):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
match_attr = "slug"
|
match_attr = "slug"
|
||||||
|
|
||||||
cookbook = self.repo.get_one(item_id, match_attr)
|
# Allow fetching other households' cookbooks
|
||||||
|
cookbook = self.group_cookbooks.get_one(item_id, match_attr)
|
||||||
|
|
||||||
if cookbook is None:
|
if cookbook is None:
|
||||||
raise HTTPException(status_code=404)
|
raise HTTPException(status_code=404)
|
||||||
|
|
|
@ -105,8 +105,8 @@ class BaseRecipeController(BaseCrudController):
|
||||||
return get_repositories(self.session, group_id=self.group_id, household_id=None).recipes
|
return get_repositories(self.session, group_id=self.group_id, household_id=None).recipes
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def cookbooks_repo(self) -> RepositoryGeneric[ReadCookBook, CookBook]:
|
def group_cookbooks(self) -> RepositoryGeneric[ReadCookBook, CookBook]:
|
||||||
return self.repos.cookbooks
|
return get_repositories(self.session, group_id=self.group_id, household_id=None).cookbooks
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service(self) -> RecipeService:
|
def service(self) -> RecipeService:
|
||||||
|
@ -354,7 +354,7 @@ class RecipeController(BaseRecipeController):
|
||||||
cb_match_attr = "id"
|
cb_match_attr = "id"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
cb_match_attr = "slug"
|
cb_match_attr = "slug"
|
||||||
cookbook_data = self.cookbooks_repo.get_one(search_query.cookbook, cb_match_attr)
|
cookbook_data = self.group_cookbooks.get_one(search_query.cookbook, cb_match_attr)
|
||||||
|
|
||||||
if cookbook_data is None:
|
if cookbook_data is None:
|
||||||
raise HTTPException(status_code=404, detail="cookbook not found")
|
raise HTTPException(status_code=404, detail="cookbook not found")
|
||||||
|
|
138
poetry.lock
generated
138
poetry.lock
generated
|
@ -1464,13 +1464,13 @@ pyyaml = ">=5.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-material"
|
name = "mkdocs-material"
|
||||||
version = "9.5.43"
|
version = "9.5.44"
|
||||||
description = "Documentation that simply works"
|
description = "Documentation that simply works"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocs_material-9.5.43-py3-none-any.whl", hash = "sha256:4aae0664c456fd12837a3192e0225c17960ba8bf55d7f0a7daef7e4b0b914a34"},
|
{file = "mkdocs_material-9.5.44-py3-none-any.whl", hash = "sha256:47015f9c167d58a5ff5e682da37441fc4d66a1c79334bfc08d774763cacf69ca"},
|
||||||
{file = "mkdocs_material-9.5.43.tar.gz", hash = "sha256:83be7ff30b65a1e4930dfa4ab911e75780a3afc9583d162692e434581cb46979"},
|
{file = "mkdocs_material-9.5.44.tar.gz", hash = "sha256:f3a6c968e524166b3f3ed1fb97d3ed3e0091183b0545cedf7156a2a6804c56c0"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1598,13 +1598,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openai"
|
name = "openai"
|
||||||
version = "1.53.0"
|
version = "1.54.1"
|
||||||
description = "The official Python library for the openai API"
|
description = "The official Python library for the openai API"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7.1"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "openai-1.53.0-py3-none-any.whl", hash = "sha256:20f408c32fc5cb66e60c6882c994cdca580a5648e10045cd840734194f033418"},
|
{file = "openai-1.54.1-py3-none-any.whl", hash = "sha256:3cb49ccb6bfdc724ad01cc397d323ef8314fc7d45e19e9de2afdd6484a533324"},
|
||||||
{file = "openai-1.53.0.tar.gz", hash = "sha256:be2c4e77721b166cce8130e544178b7d579f751b4b074ffbaade3854b6f85ec5"},
|
{file = "openai-1.54.1.tar.gz", hash = "sha256:5b832bf82002ba8c4f6e5e25c1c0f5d468c22f043711544c716eaffdb30dd6f1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1622,69 +1622,69 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orjson"
|
name = "orjson"
|
||||||
version = "3.10.10"
|
version = "3.10.11"
|
||||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "orjson-3.10.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b788a579b113acf1c57e0a68e558be71d5d09aa67f62ca1f68e01117e550a998"},
|
{file = "orjson-3.10.11-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6dade64687f2bd7c090281652fe18f1151292d567a9302b34c2dbb92a3872f1f"},
|
||||||
{file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:804b18e2b88022c8905bb79bd2cbe59c0cd014b9328f43da8d3b28441995cda4"},
|
{file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f07c550a6ccd2b9290849b22316a609023ed851a87ea888c0456485a7d196a"},
|
||||||
{file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9972572a1d042ec9ee421b6da69f7cc823da5962237563fa548ab17f152f0b9b"},
|
{file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd9a187742d3ead9df2e49240234d728c67c356516cf4db018833a86f20ec18c"},
|
||||||
{file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc6993ab1c2ae7dd0711161e303f1db69062955ac2668181bfdf2dd410e65258"},
|
{file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77b0fed6f209d76c1c39f032a70df2d7acf24b1812ca3e6078fd04e8972685a3"},
|
||||||
{file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d78e4cacced5781b01d9bc0f0cd8b70b906a0e109825cb41c1b03f9c41e4ce86"},
|
{file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63fc9d5fe1d4e8868f6aae547a7b8ba0a2e592929245fff61d633f4caccdcdd6"},
|
||||||
{file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6eb2598df518281ba0cbc30d24c5b06124ccf7e19169e883c14e0831217a0bc"},
|
{file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65cd3e3bb4fbb4eddc3c1e8dce10dc0b73e808fcb875f9fab40c81903dd9323e"},
|
||||||
{file = "orjson-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23776265c5215ec532de6238a52707048401a568f0fa0d938008e92a147fe2c7"},
|
{file = "orjson-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f67c570602300c4befbda12d153113b8974a3340fdcf3d6de095ede86c06d92"},
|
||||||
{file = "orjson-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cc2a654c08755cef90b468ff17c102e2def0edd62898b2486767204a7f5cc9c"},
|
{file = "orjson-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f39728c7f7d766f1f5a769ce4d54b5aaa4c3f92d5b84817053cc9995b977acc"},
|
||||||
{file = "orjson-3.10.10-cp310-none-win32.whl", hash = "sha256:081b3fc6a86d72efeb67c13d0ea7c030017bd95f9868b1e329a376edc456153b"},
|
{file = "orjson-3.10.11-cp310-none-win32.whl", hash = "sha256:1789d9db7968d805f3d94aae2c25d04014aae3a2fa65b1443117cd462c6da647"},
|
||||||
{file = "orjson-3.10.10-cp310-none-win_amd64.whl", hash = "sha256:ff38c5fb749347768a603be1fb8a31856458af839f31f064c5aa74aca5be9efe"},
|
{file = "orjson-3.10.11-cp310-none-win_amd64.whl", hash = "sha256:5576b1e5a53a5ba8f8df81872bb0878a112b3ebb1d392155f00f54dd86c83ff6"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:879e99486c0fbb256266c7c6a67ff84f46035e4f8749ac6317cc83dacd7f993a"},
|
{file = "orjson-3.10.11-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1444f9cb7c14055d595de1036f74ecd6ce15f04a715e73f33bb6326c9cef01b6"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019481fa9ea5ff13b5d5d95e6fd5ab25ded0810c80b150c2c7b1cc8660b662a7"},
|
{file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdec57fe3b4bdebcc08a946db3365630332dbe575125ff3d80a3272ebd0ddafe"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0dd57eff09894938b4c86d4b871a479260f9e156fa7f12f8cad4b39ea8028bb5"},
|
{file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4eed32f33a0ea6ef36ccc1d37f8d17f28a1d6e8eefae5928f76aff8f1df85e67"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbde6d70cd95ab4d11ea8ac5e738e30764e510fc54d777336eec09bb93b8576c"},
|
{file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80df27dd8697242b904f4ea54820e2d98d3f51f91e97e358fc13359721233e4b"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2625cb37b8fb42e2147404e5ff7ef08712099197a9cd38895006d7053e69d6"},
|
{file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:705f03cee0cb797256d54de6695ef219e5bc8c8120b6654dd460848d57a9af3d"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbf3c20c6a7db69df58672a0d5815647ecf78c8e62a4d9bd284e8621c1fe5ccb"},
|
{file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03246774131701de8e7059b2e382597da43144a9a7400f178b2a32feafc54bd5"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:75c38f5647e02d423807d252ce4528bf6a95bd776af999cb1fb48867ed01d1f6"},
|
{file = "orjson-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8b5759063a6c940a69c728ea70d7c33583991c6982915a839c8da5f957e0103a"},
|
||||||
{file = "orjson-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23458d31fa50ec18e0ec4b0b4343730928296b11111df5f547c75913714116b2"},
|
{file = "orjson-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:677f23e32491520eebb19c99bb34675daf5410c449c13416f7f0d93e2cf5f981"},
|
||||||
{file = "orjson-3.10.10-cp311-none-win32.whl", hash = "sha256:2787cd9dedc591c989f3facd7e3e86508eafdc9536a26ec277699c0aa63c685b"},
|
{file = "orjson-3.10.11-cp311-none-win32.whl", hash = "sha256:a11225d7b30468dcb099498296ffac36b4673a8398ca30fdaec1e6c20df6aa55"},
|
||||||
{file = "orjson-3.10.10-cp311-none-win_amd64.whl", hash = "sha256:6514449d2c202a75183f807bc755167713297c69f1db57a89a1ef4a0170ee269"},
|
{file = "orjson-3.10.11-cp311-none-win_amd64.whl", hash = "sha256:df8c677df2f9f385fcc85ab859704045fa88d4668bc9991a527c86e710392bec"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8564f48f3620861f5ef1e080ce7cd122ee89d7d6dacf25fcae675ff63b4d6e05"},
|
{file = "orjson-3.10.11-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:360a4e2c0943da7c21505e47cf6bd725588962ff1d739b99b14e2f7f3545ba51"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bf161a32b479034098c5b81f2608f09167ad2fa1c06abd4e527ea6bf4837a9"},
|
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:496e2cb45de21c369079ef2d662670a4892c81573bcc143c4205cae98282ba97"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b65c93617bcafa7f04b74ae8bc2cc214bd5cb45168a953256ff83015c6747d"},
|
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7dfa8db55c9792d53c5952900c6a919cfa377b4f4534c7a786484a6a4a350c19"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8e28406f97fc2ea0c6150f4c1b6e8261453318930b334abc419214c82314f85"},
|
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f3382415747e0dbda9dade6f1e1a01a9d37f630d8c9049a8ed0e385b7a90c0"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4d0d9fe174cc7a5bdce2e6c378bcdb4c49b2bf522a8f996aa586020e1b96cee"},
|
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f35a1b9f50a219f470e0e497ca30b285c9f34948d3c8160d5ad3a755d9299433"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3be81c42f1242cbed03cbb3973501fcaa2675a0af638f8be494eaf37143d999"},
|
{file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f3b7c5803138e67028dde33450e054c87e0703afbe730c105f1fcd873496d5"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65f9886d3bae65be026219c0a5f32dbbe91a9e6272f56d092ab22561ad0ea33b"},
|
{file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f91d9eb554310472bd09f5347950b24442600594c2edc1421403d7610a0998fd"},
|
||||||
{file = "orjson-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:730ed5350147db7beb23ddaf072f490329e90a1d059711d364b49fe352ec987b"},
|
{file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dfbb2d460a855c9744bbc8e36f9c3a997c4b27d842f3d5559ed54326e6911f9b"},
|
||||||
{file = "orjson-3.10.10-cp312-none-win32.whl", hash = "sha256:a8f4bf5f1c85bea2170800020d53a8877812892697f9c2de73d576c9307a8a5f"},
|
{file = "orjson-3.10.11-cp312-none-win32.whl", hash = "sha256:d4a62c49c506d4d73f59514986cadebb7e8d186ad510c518f439176cf8d5359d"},
|
||||||
{file = "orjson-3.10.10-cp312-none-win_amd64.whl", hash = "sha256:384cd13579a1b4cd689d218e329f459eb9ddc504fa48c5a83ef4889db7fd7a4f"},
|
{file = "orjson-3.10.11-cp312-none-win_amd64.whl", hash = "sha256:f1eec3421a558ff7a9b010a6c7effcfa0ade65327a71bb9b02a1c3b77a247284"},
|
||||||
{file = "orjson-3.10.10-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44bffae68c291f94ff5a9b4149fe9d1bdd4cd0ff0fb575bcea8351d48db629a1"},
|
{file = "orjson-3.10.11-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c46294faa4e4d0eb73ab68f1a794d2cbf7bab33b1dda2ac2959ffb7c61591899"},
|
||||||
{file = "orjson-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e27b4c6437315df3024f0835887127dac2a0a3ff643500ec27088d2588fa5ae1"},
|
{file = "orjson-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52e5834d7d6e58a36846e059d00559cb9ed20410664f3ad156cd2cc239a11230"},
|
||||||
{file = "orjson-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca84df16d6b49325a4084fd8b2fe2229cb415e15c46c529f868c3387bb1339d"},
|
{file = "orjson-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2fc947e5350fdce548bfc94f434e8760d5cafa97fb9c495d2fef6757aa02ec0"},
|
||||||
{file = "orjson-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c14ce70e8f39bd71f9f80423801b5d10bf93d1dceffdecd04df0f64d2c69bc01"},
|
{file = "orjson-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0efabbf839388a1dab5b72b5d3baedbd6039ac83f3b55736eb9934ea5494d258"},
|
||||||
{file = "orjson-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:24ac62336da9bda1bd93c0491eff0613003b48d3cb5d01470842e7b52a40d5b4"},
|
{file = "orjson-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3f29634260708c200c4fe148e42b4aae97d7b9fee417fbdd74f8cfc265f15b0"},
|
||||||
{file = "orjson-3.10.10-cp313-none-win32.whl", hash = "sha256:eb0a42831372ec2b05acc9ee45af77bcaccbd91257345f93780a8e654efc75db"},
|
{file = "orjson-3.10.11-cp313-none-win32.whl", hash = "sha256:1a1222ffcee8a09476bbdd5d4f6f33d06d0d6642df2a3d78b7a195ca880d669b"},
|
||||||
{file = "orjson-3.10.10-cp313-none-win_amd64.whl", hash = "sha256:f0c4f37f8bf3f1075c6cc8dd8a9f843689a4b618628f8812d0a71e6968b95ffd"},
|
{file = "orjson-3.10.11-cp313-none-win_amd64.whl", hash = "sha256:bc274ac261cc69260913b2d1610760e55d3c0801bb3457ba7b9004420b6b4270"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:829700cc18503efc0cf502d630f612884258020d98a317679cd2054af0259568"},
|
{file = "orjson-3.10.11-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:19b3763e8bbf8ad797df6b6b5e0fc7c843ec2e2fc0621398534e0c6400098f87"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0ceb5e0e8c4f010ac787d29ae6299846935044686509e2f0f06ed441c1ca949"},
|
{file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be83a13312e5e58d633580c5eb8d0495ae61f180da2722f20562974188af205"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c25908eb86968613216f3db4d3003f1c45d78eb9046b71056ca327ff92bdbd4"},
|
{file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:afacfd1ab81f46dedd7f6001b6d4e8de23396e4884cd3c3436bd05defb1a6446"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:218cb0bc03340144b6328a9ff78f0932e642199ac184dd74b01ad691f42f93ff"},
|
{file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb4d0bea56bba596723d73f074c420aec3b2e5d7d30698bc56e6048066bd560c"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2277ec2cea3775640dc81ab5195bb5b2ada2fe0ea6eee4677474edc75ea6785"},
|
{file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96ed1de70fcb15d5fed529a656df29f768187628727ee2788344e8a51e1c1350"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:848ea3b55ab5ccc9d7bbd420d69432628b691fba3ca8ae3148c35156cbd282aa"},
|
{file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bfb30c891b530f3f80e801e3ad82ef150b964e5c38e1fb8482441c69c35c61c"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e3e67b537ac0c835b25b5f7d40d83816abd2d3f4c0b0866ee981a045287a54f3"},
|
{file = "orjson-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d496c74fc2b61341e3cefda7eec21b7854c5f672ee350bc55d9a4997a8a95204"},
|
||||||
{file = "orjson-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:7948cfb909353fce2135dcdbe4521a5e7e1159484e0bb024c1722f272488f2b8"},
|
{file = "orjson-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:655a493bac606655db9a47fe94d3d84fc7f3ad766d894197c94ccf0c5408e7d3"},
|
||||||
{file = "orjson-3.10.10-cp38-none-win32.whl", hash = "sha256:78bee66a988f1a333dc0b6257503d63553b1957889c17b2c4ed72385cd1b96ae"},
|
{file = "orjson-3.10.11-cp38-none-win32.whl", hash = "sha256:b9546b278c9fb5d45380f4809e11b4dd9844ca7aaf1134024503e134ed226161"},
|
||||||
{file = "orjson-3.10.10-cp38-none-win_amd64.whl", hash = "sha256:f1d647ca8d62afeb774340a343c7fc023efacfd3a39f70c798991063f0c681dd"},
|
{file = "orjson-3.10.11-cp38-none-win_amd64.whl", hash = "sha256:b592597fe551d518f42c5a2eb07422eb475aa8cfdc8c51e6da7054b836b26782"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5a059afddbaa6dd733b5a2d76a90dbc8af790b993b1b5cb97a1176ca713b5df8"},
|
{file = "orjson-3.10.11-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c95f2ecafe709b4e5c733b5e2768ac569bed308623c85806c395d9cca00e08af"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f9b5c59f7e2a1a410f971c5ebc68f1995822837cd10905ee255f96074537ee6"},
|
{file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80c00d4acded0c51c98754fe8218cb49cb854f0f7eb39ea4641b7f71732d2cb7"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d5ef198bafdef4aa9d49a4165ba53ffdc0a9e1c7b6f76178572ab33118afea25"},
|
{file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:461311b693d3d0a060439aa669c74f3603264d4e7a08faa68c47ae5a863f352d"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf29ce0bb5d3320824ec3d1508652421000ba466abd63bdd52c64bcce9eb1fa"},
|
{file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52ca832f17d86a78cbab86cdc25f8c13756ebe182b6fc1a97d534051c18a08de"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dddd5516bcc93e723d029c1633ae79c4417477b4f57dad9bfeeb6bc0315e654a"},
|
{file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c57ea78a753812f528178aa2f1c57da633754c91d2124cb28991dab4c79a54"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12f2003695b10817f0fa8b8fca982ed7f5761dcb0d93cff4f2f9f6709903fd7"},
|
{file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7fcfc6f7ca046383fb954ba528587e0f9336828b568282b27579c49f8e16aad"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:672f9874a8a8fb9bb1b771331d31ba27f57702c8106cdbadad8bda5d10bc1019"},
|
{file = "orjson-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:86b9dd983857970c29e4c71bb3e95ff085c07d3e83e7c46ebe959bac07ebd80b"},
|
||||||
{file = "orjson-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dcbb0ca5fafb2b378b2c74419480ab2486326974826bbf6588f4dc62137570a"},
|
{file = "orjson-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4d83f87582d223e54efb2242a79547611ba4ebae3af8bae1e80fa9a0af83bb7f"},
|
||||||
{file = "orjson-3.10.10-cp39-none-win32.whl", hash = "sha256:d9bbd3a4b92256875cb058c3381b782649b9a3c68a4aa9a2fff020c2f9cfc1be"},
|
{file = "orjson-3.10.11-cp39-none-win32.whl", hash = "sha256:9fd0ad1c129bc9beb1154c2655f177620b5beaf9a11e0d10bac63ef3fce96950"},
|
||||||
{file = "orjson-3.10.10-cp39-none-win_amd64.whl", hash = "sha256:766f21487a53aee8524b97ca9582d5c6541b03ab6210fbaf10142ae2f3ced2aa"},
|
{file = "orjson-3.10.11-cp39-none-win_amd64.whl", hash = "sha256:10f416b2a017c8bd17f325fb9dee1fb5cdd7a54e814284896b7c3f2763faa017"},
|
||||||
{file = "orjson-3.10.10.tar.gz", hash = "sha256:37949383c4df7b4337ce82ee35b6d7471e55195efa7dcb45ab8226ceadb0fe3b"},
|
{file = "orjson-3.10.11.tar.gz", hash = "sha256:e35b6d730de6384d5b2dab5fd23f0d76fae8bbc8c353c2f78210aa5fa4beb3ef"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2796,13 +2796,13 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rich"
|
name = "rich"
|
||||||
version = "13.9.3"
|
version = "13.9.4"
|
||||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"},
|
{file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
|
||||||
{file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"},
|
{file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
|
@ -60,6 +60,8 @@ def test_create_cookbook(api_client: TestClient, unique_user: TestUser):
|
||||||
page_data = get_page_data(unique_user.group_id, unique_user.household_id)
|
page_data = get_page_data(unique_user.group_id, unique_user.household_id)
|
||||||
response = api_client.post(api_routes.households_cookbooks, json=page_data, headers=unique_user.token)
|
response = api_client.post(api_routes.households_cookbooks, json=page_data, headers=unique_user.token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
assert response.json()["groupId"] == unique_user.group_id
|
||||||
|
assert response.json()["householdId"] == unique_user.household_id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("name_input", ["", " ", "@"])
|
@pytest.mark.parametrize("name_input", ["", " ", "@"])
|
||||||
|
@ -78,9 +80,22 @@ def test_create_cookbook_bad_name(api_client: TestClient, unique_user: TestUser,
|
||||||
assert response.status_code == 422
|
assert response.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
def test_read_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
@pytest.mark.parametrize("use_other_household", [True, False])
|
||||||
|
def test_read_cookbook(
|
||||||
|
api_client: TestClient,
|
||||||
|
unique_user: TestUser,
|
||||||
|
h2_user: TestUser,
|
||||||
|
cookbooks: list[TestCookbook],
|
||||||
|
use_other_household: bool,
|
||||||
|
):
|
||||||
sample = random.choice(cookbooks)
|
sample = random.choice(cookbooks)
|
||||||
response = api_client.get(api_routes.households_cookbooks_item_id(sample.id), headers=unique_user.token)
|
if use_other_household:
|
||||||
|
headers = h2_user.token
|
||||||
|
else:
|
||||||
|
headers = unique_user.token
|
||||||
|
|
||||||
|
# all households should be able to fetch all cookbooks
|
||||||
|
response = api_client.get(api_routes.households_cookbooks_item_id(sample.id), headers=headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
page_data = response.json()
|
page_data = response.json()
|
||||||
|
@ -111,6 +126,28 @@ def test_update_cookbook(api_client: TestClient, unique_user: TestUser, cookbook
|
||||||
assert page_data["slug"] == update_data["name"]
|
assert page_data["slug"] == update_data["name"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_cookbook_other_household(
|
||||||
|
api_client: TestClient, unique_user: TestUser, h2_user: TestUser, cookbooks: list[TestCookbook]
|
||||||
|
):
|
||||||
|
cookbook = random.choice(cookbooks)
|
||||||
|
|
||||||
|
update_data = get_page_data(unique_user.group_id, unique_user.household_id)
|
||||||
|
|
||||||
|
update_data["name"] = random_string(10)
|
||||||
|
|
||||||
|
response = api_client.put(
|
||||||
|
api_routes.households_cookbooks_item_id(cookbook.id), json=update_data, headers=h2_user.token
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
response = api_client.get(api_routes.households_cookbooks_item_id(cookbook.id), headers=unique_user.token)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
page_data = response.json()
|
||||||
|
assert page_data["name"] != update_data["name"]
|
||||||
|
assert page_data["slug"] != update_data["name"]
|
||||||
|
|
||||||
|
|
||||||
def test_update_cookbooks_many(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
def test_update_cookbooks_many(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
||||||
pages = [x.data for x in cookbooks]
|
pages = [x.data for x in cookbooks]
|
||||||
|
|
||||||
|
@ -135,6 +172,20 @@ def test_update_cookbooks_many(api_client: TestClient, unique_user: TestUser, co
|
||||||
assert str(know) in server_ids
|
assert str(know) in server_ids
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_cookbooks_many_other_household(
|
||||||
|
api_client: TestClient, unique_user: TestUser, h2_user: TestUser, cookbooks: list[TestCookbook]
|
||||||
|
):
|
||||||
|
pages = [x.data for x in cookbooks]
|
||||||
|
|
||||||
|
reverse_order = sorted(pages, key=lambda x: x["position"], reverse=True)
|
||||||
|
for x, page in enumerate(reverse_order):
|
||||||
|
page["position"] = x
|
||||||
|
page["group_id"] = str(unique_user.group_id)
|
||||||
|
|
||||||
|
response = api_client.put(api_routes.households_cookbooks, json=utils.jsonify(reverse_order), headers=h2_user.token)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_delete_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
def test_delete_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
||||||
sample = random.choice(cookbooks)
|
sample = random.choice(cookbooks)
|
||||||
response = api_client.delete(api_routes.households_cookbooks_item_id(sample.id), headers=unique_user.token)
|
response = api_client.delete(api_routes.households_cookbooks_item_id(sample.id), headers=unique_user.token)
|
||||||
|
@ -145,6 +196,18 @@ def test_delete_cookbook(api_client: TestClient, unique_user: TestUser, cookbook
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_cookbook_other_household(
|
||||||
|
api_client: TestClient, unique_user: TestUser, h2_user: TestUser, cookbooks: list[TestCookbook]
|
||||||
|
):
|
||||||
|
sample = random.choice(cookbooks)
|
||||||
|
response = api_client.delete(api_routes.households_cookbooks_item_id(sample.id), headers=h2_user.token)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
response = api_client.get(api_routes.households_cookbooks_item_id(sample.slug), headers=unique_user.token)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"qf_string, expected_code",
|
"qf_string, expected_code",
|
||||||
[
|
[
|
||||||
|
|
|
@ -299,3 +299,16 @@ def test_cookbook_recipes_includes_all_households(api_client: TestClient, unique
|
||||||
assert recipe.id in fetched_recipe_ids
|
assert recipe.id in fetched_recipe_ids
|
||||||
for recipe in other_recipes:
|
for recipe in other_recipes:
|
||||||
assert recipe.id in fetched_recipe_ids
|
assert recipe.id in fetched_recipe_ids
|
||||||
|
|
||||||
|
|
||||||
|
def test_cookbooks_from_other_households(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
|
||||||
|
h2_cookbook = h2_user.repos.cookbooks.create(
|
||||||
|
SaveCookBook(
|
||||||
|
name=random_string(),
|
||||||
|
group_id=h2_user.group_id,
|
||||||
|
household_id=h2_user.household_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
response = api_client.get(api_routes.recipes, params={"cookbook": h2_cookbook.slug}, headers=unique_user.token)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import pytest
|
||||||
from pytest import MonkeyPatch, Session
|
from pytest import MonkeyPatch, Session
|
||||||
|
|
||||||
from mealie.core.config import get_app_settings
|
from mealie.core.config import get_app_settings
|
||||||
from mealie.core.security.providers.openid_provider import OpenIDProvider
|
from mealie.core.security.providers.openid_provider import OpenIDProvider
|
||||||
from mealie.repos.all_repositories import get_repositories
|
from mealie.repos.all_repositories import get_repositories
|
||||||
|
from tests.utils.factories import random_email, random_string
|
||||||
from tests.utils.fixture_schemas import TestUser
|
from tests.utils.fixture_schemas import TestUser
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,3 +127,38 @@ def test_has_admin_group_new_user(monkeypatch: MonkeyPatch, session: Session):
|
||||||
user = db.users.get_one("dude2", "username")
|
user = db.users.get_one("dude2", "username")
|
||||||
assert user is not None
|
assert user is not None
|
||||||
assert user.admin
|
assert user.admin
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("valid_group", [True, False])
|
||||||
|
@pytest.mark.parametrize("valid_household", [True, False])
|
||||||
|
def test_ldap_user_creation_invalid_group_or_household(
|
||||||
|
monkeypatch: MonkeyPatch, session: Session, valid_group: bool, valid_household: bool
|
||||||
|
):
|
||||||
|
monkeypatch.setenv("OIDC_USER_GROUP", "mealie_user")
|
||||||
|
monkeypatch.setenv("OIDC_ADMIN_GROUP", "mealie_admin")
|
||||||
|
if not valid_group:
|
||||||
|
monkeypatch.setenv("DEFAULT_GROUP", random_string())
|
||||||
|
if not valid_household:
|
||||||
|
monkeypatch.setenv("DEFAULT_HOUSEHOLD", random_string())
|
||||||
|
get_app_settings.cache_clear()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"preferred_username": random_string(),
|
||||||
|
"email": random_email(),
|
||||||
|
"name": random_string(),
|
||||||
|
"groups": ["mealie_user"],
|
||||||
|
}
|
||||||
|
auth_provider = OpenIDProvider(session, data)
|
||||||
|
|
||||||
|
if valid_group and valid_household:
|
||||||
|
assert auth_provider.authenticate() is not None
|
||||||
|
else:
|
||||||
|
assert auth_provider.authenticate() is None
|
||||||
|
|
||||||
|
db = get_repositories(session, group_id=None, household_id=None)
|
||||||
|
user = db.users.get_one(data["preferred_username"], "username")
|
||||||
|
|
||||||
|
if valid_group and valid_household:
|
||||||
|
assert user is not None
|
||||||
|
else:
|
||||||
|
assert user is None
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import ldap
|
import ldap
|
||||||
|
import pytest
|
||||||
from pytest import MonkeyPatch
|
from pytest import MonkeyPatch
|
||||||
|
|
||||||
from mealie.core import security
|
from mealie.core import security
|
||||||
|
@ -13,6 +14,7 @@ from mealie.core.security.providers.credentials_provider import (
|
||||||
from mealie.core.security.providers.ldap_provider import LDAPProvider
|
from mealie.core.security.providers.ldap_provider import LDAPProvider
|
||||||
from mealie.db.db_setup import session_context
|
from mealie.db.db_setup import session_context
|
||||||
from mealie.db.models.users.users import AuthMethod
|
from mealie.db.models.users.users import AuthMethod
|
||||||
|
from mealie.repos.repository_factory import AllRepositories
|
||||||
from mealie.schema.user.auth import CredentialsRequestForm
|
from mealie.schema.user.auth import CredentialsRequestForm
|
||||||
from mealie.schema.user.user import PrivateUser
|
from mealie.schema.user.user import PrivateUser
|
||||||
from tests.utils import random_string
|
from tests.utils import random_string
|
||||||
|
@ -92,7 +94,7 @@ class LdapConnMock:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def setup_env(monkeypatch: MonkeyPatch):
|
def setup_env(monkeypatch: MonkeyPatch, **kwargs):
|
||||||
user = random_string(10)
|
user = random_string(10)
|
||||||
mail = random_string(10)
|
mail = random_string(10)
|
||||||
name = random_string(10)
|
name = random_string(10)
|
||||||
|
@ -140,11 +142,55 @@ def test_ldap_user_creation(monkeypatch: MonkeyPatch):
|
||||||
provider = get_provider(session, user, password)
|
provider = get_provider(session, user, password)
|
||||||
result = provider.get_user()
|
result = provider.get_user()
|
||||||
|
|
||||||
|
app_settings = get_app_settings()
|
||||||
|
|
||||||
assert result
|
assert result
|
||||||
assert result.username == user
|
assert result.username == user
|
||||||
assert result.email == mail
|
assert result.email == mail
|
||||||
assert result.full_name == name
|
assert result.full_name == name
|
||||||
assert result.admin is False
|
assert result.admin is False
|
||||||
|
assert result.group == app_settings.DEFAULT_GROUP
|
||||||
|
assert result.household == app_settings.DEFAULT_HOUSEHOLD
|
||||||
|
assert result.auth_method == AuthMethod.LDAP
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("valid_group", [True, False])
|
||||||
|
@pytest.mark.parametrize("valid_household", [True, False])
|
||||||
|
def test_ldap_user_creation_invalid_group_or_household(
|
||||||
|
unfiltered_database: AllRepositories, monkeypatch: MonkeyPatch, valid_group: bool, valid_household: bool
|
||||||
|
):
|
||||||
|
user, mail, name, password, query_bind, query_password = setup_env(monkeypatch)
|
||||||
|
if not valid_group:
|
||||||
|
monkeypatch.setenv("DEFAULT_GROUP", random_string())
|
||||||
|
if not valid_household:
|
||||||
|
monkeypatch.setenv("DEFAULT_HOUSEHOLD", random_string())
|
||||||
|
|
||||||
|
def ldap_initialize_mock(url):
|
||||||
|
assert url == ""
|
||||||
|
return LdapConnMock(user, password, False, query_bind, query_password, mail, name)
|
||||||
|
|
||||||
|
monkeypatch.setattr(ldap, "initialize", ldap_initialize_mock)
|
||||||
|
|
||||||
|
get_app_settings.cache_clear()
|
||||||
|
|
||||||
|
with session_context() as session:
|
||||||
|
provider = get_provider(session, user, password)
|
||||||
|
try:
|
||||||
|
result = provider.get_user()
|
||||||
|
except ValueError:
|
||||||
|
result = None
|
||||||
|
|
||||||
|
if valid_group and valid_household:
|
||||||
|
assert result
|
||||||
|
else:
|
||||||
|
assert not result
|
||||||
|
|
||||||
|
# check if the user exists in the db
|
||||||
|
user = unfiltered_database.users.get_by_username(user)
|
||||||
|
if valid_group and valid_household:
|
||||||
|
assert user
|
||||||
|
else:
|
||||||
|
assert not user
|
||||||
|
|
||||||
|
|
||||||
def test_ldap_user_creation_fail(monkeypatch: MonkeyPatch):
|
def test_ldap_user_creation_fail(monkeypatch: MonkeyPatch):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue