Merge branch 'mealie-next' into fix/add-recipe-to-list-cross-household

This commit is contained in:
Kuchenpirat 2025-08-16 09:55:50 +02:00 committed by GitHub
commit 0917be0ecf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 919 additions and 499 deletions

View file

@ -71,6 +71,7 @@ tasks:
desc: run code generators
cmds:
- poetry run python dev/code-generation/main.py {{ .CLI_ARGS }}
- task: docs:gen
- task: py:format
dev:services:

View file

@ -8,8 +8,8 @@ from utils import log
# ============================================================
template = """// This Code is auto generated by gen_ts_types.py
{% for name in global %}import {{ name }} from "@/components/global/{{ name }}.vue";
{% endfor %}{% for name in layout %}import {{ name }} from "@/components/layout/{{ name }}.vue";
{% for name in global %}import type {{ name }} from "@/components/global/{{ name }}.vue";
{% endfor %}{% for name in layout %}import type {{ name }} from "@/components/layout/{{ name }}.vue";
{% endfor %}
declare module "vue" {
export interface GlobalComponents {

View file

@ -1,7 +1,7 @@
###############################################
# Frontend Build
###############################################
FROM node:20@sha256:452293f0e5c9b7075829f2cd0dbd5fcae44d89cf5508b84a17bbe0857dc1a654 \
FROM node:20@sha256:572a90df10a58ebb7d3f223d661d964a6c2383a9c2b5763162b4f631c53dc56a \
AS frontend-builder
WORKDIR /frontend

File diff suppressed because one or more lines are too long

View file

@ -70,7 +70,7 @@ import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue"
import { useCookbookStore } from "~/composables/store/use-cookbook-store";
import { useCookbook } from "~/composables/use-group-cookbooks";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import type { RecipeCookBook } from "~/lib/api/types/cookbook";
import type { ReadCookBook } from "~/lib/api/types/cookbook";
import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
const $auth = useMealieAuth();
@ -100,7 +100,7 @@ const dialogStates = reactive({
edit: false,
});
const editTarget = ref<RecipeCookBook | null>(null);
const editTarget = ref<ReadCookBook | null>(null);
function handleEditCookbook() {
dialogStates.edit = true;
editTarget.value = book.value;

View file

@ -1,18 +1,18 @@
import type { Composer } from "vue-i18n";
import { useReadOnlyStore, useStore } from "../partials/use-store-factory";
import type { RecipeCookBook } from "~/lib/api/types/cookbook";
import type { ReadCookBook } from "~/lib/api/types/cookbook";
import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref<RecipeCookBook[]> = ref([]);
const store: Ref<ReadCookBook[]> = ref([]);
const loading = ref(false);
const publicLoading = ref(false);
export const useCookbookStore = function (i18n?: Composer) {
const api = useUserApi(i18n);
return useStore<RecipeCookBook>(store, loading, api.cookbooks);
return useStore<ReadCookBook>(store, loading, api.cookbooks);
};
export const usePublicCookbookStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<RecipeCookBook>(store, publicLoading, api.cookbooks);
return useReadOnlyStore<ReadCookBook>(store, publicLoading, api.cookbooks);
};

View file

@ -69,6 +69,7 @@
"new-notification": "Nuwe kennisgewing",
"event-notifiers": "Gebeurteniskennisgewers",
"apprise-url-skipped-if-blank": "Apprise URL (oorgeslaan indien leeg)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Aktiveer kennisgewer",
"what-events": "Op watter gebeurtenisse moet hierdie kennisgewing inteken?",
"user-events": "Gebruikersgebeurtenisse",

View file

@ -69,6 +69,7 @@
"new-notification": "إشعار جديد",
"event-notifiers": "إشعار الحدث",
"apprise-url-skipped-if-blank": "الرابط Apprise (يتم تجاهله إذا ما كان فارغً)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "تفعيل الإشعارات",
"what-events": "ما هي الأحداث التي يجب على هذا المخدم أن يستجيب لها؟",
"user-events": "أحداث المستخدمين",

View file

@ -69,6 +69,7 @@
"new-notification": "Ново известие",
"event-notifiers": "Известия за събитие",
"apprise-url-skipped-if-blank": "URL за известяване (пропуска се ако е празно)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Включи известията",
"what-events": "За кои събития трябва да се получават известия?",
"user-events": "Потребителски събития",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova notificació",
"event-notifiers": "Notificacions d'esdeveniments",
"apprise-url-skipped-if-blank": "Apprise URL (si es deixa buit, s'ignorarà)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Habilita la notificació",
"what-events": "Què esdeveniments vols que utilitzen aquest notificador?",
"user-events": "Esdeveniments d'usuari",

View file

@ -69,6 +69,7 @@
"new-notification": "Nové oznámení",
"event-notifiers": "Notifikace událostí",
"apprise-url-skipped-if-blank": "Apprise URL (přeskočeno pokud je prázdné)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Povolit notifikaci",
"what-events": "K jakým událostem by se měl tento oznamovatel přihlásit?",
"user-events": "Uživatelské události",

View file

@ -69,6 +69,7 @@
"new-notification": "Ny notifikation",
"event-notifiers": "Notifikation om begivenheder",
"apprise-url-skipped-if-blank": "Informations link (sprunget over hvis ladet være tomt)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Aktiver Notifikationer",
"what-events": "Hvilke begivenheder skal denne anmelder abonnere på?",
"user-events": "Brugerhændelser",

View file

@ -69,6 +69,7 @@
"new-notification": "Neue Benachrichtigung",
"event-notifiers": "Ereignis-Benachrichtigungen",
"apprise-url-skipped-if-blank": "Apprise-URL (wird übersprungen, wenn leer)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Benachrichtigen aktivieren",
"what-events": "Welche Ereignisse soll diese Benachrichtigung abonnieren?",
"user-events": "Benutzer-Ereignisse",

View file

@ -69,6 +69,7 @@
"new-notification": "Νέα ειδοποίηση",
"event-notifiers": "Ειδοποιητές Συμβάντος",
"apprise-url-skipped-if-blank": "Apprise URL (παραλείπεται αν είναι κενό)",
"apprise-url-is-left-intentionally-blank": "Δεδομένου ότι οι διευθύνσεις URL Apprise περιέχουν συνήθως ευαίσθητες πληροφορίες, το πεδίο αυτό παραμένει σκόπιμα κενό κατά την επεξεργασία. Αν θέλετε να ενημερώσετε το URL, παρακαλώ εισάγετε το νέο εδώ, αλλιώς αφήστε το κενό για να διατηρήσετε την τρέχουσα διεύθυνση URL.",
"enable-notifier": "Ενεργοποίηση ειδοποιητή",
"what-events": "Σε ποια συμβάντα θα πρέπει να εγγραφεί αυτός ο ειδοποιητής;",
"user-events": "Συμβάντα Χρήστη",

View file

@ -69,6 +69,7 @@
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Enable Notifier",
"what-events": "What events should this notifier subscribe to?",
"user-events": "User Events",

View file

@ -69,6 +69,7 @@
"new-notification": "Nueva notificación",
"event-notifiers": "Notificaciones de eventos",
"apprise-url-skipped-if-blank": "URL de Apprise (omitida si está en blanco)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Habilitar notificador",
"what-events": "¿A qué eventos debe suscribirse este notificador?",
"user-events": "Eventos de los usuarios",

View file

@ -69,6 +69,7 @@
"new-notification": "Uus teade",
"event-notifiers": "Sündmuste märguanded",
"apprise-url-skipped-if-blank": "Apprise URL (kui on tühi, jäetakse vahele)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Luba teavitaja",
"what-events": "Millised sündmused peaks see teavitaja tellimaa?",
"user-events": "Kasutaja sündmused",
@ -80,7 +81,7 @@
"category-events": "Kategooria sündmused",
"when-a-new-user-joins-your-group": "Kui uus kasutaja liitub sinu grupiga",
"recipe-events": "Retsepti sündmused",
"label-events": "Label Events"
"label-events": "Sildista sündmused"
},
"general": {
"add": "Lisa",

View file

@ -69,6 +69,7 @@
"new-notification": "Uusi ilmoitus",
"event-notifiers": "Tapahtumien ilmoitukset",
"apprise-url-skipped-if-blank": "Ilmoitusverkko-osoite (voi jättää tyhjäksi)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Ota ilmoittaja käyttöön",
"what-events": "Mistä tapahtumista tulisi ilmoittaa?",
"user-events": "Käyttäjän tapahtumat",

View file

@ -69,6 +69,7 @@
"new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Activer la notification",
"what-events": "À quels événements cette notification doit-elle s'abonner ?",
"user-events": "Evénements utilisateur",

View file

@ -69,6 +69,7 @@
"new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Activer la notification",
"what-events": "À quels événements cette notification doit-elle s'abonner ?",
"user-events": "Événements de l'utilisateur",

View file

@ -69,6 +69,7 @@
"new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Activer la notification",
"what-events": "À quels événements cette notification doit-elle s'abonner ?",
"user-events": "Événements utilisateur",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Notificación",
"event-notifiers": "Notificadores de Eventos",
"apprise-url-skipped-if-blank": "URL de Apprise (omitido se está en branco)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Activar o Notificador",
"what-events": "A que eventos debería subscribirse este notificador?",
"user-events": "Eventos de Usuario",

View file

@ -69,6 +69,7 @@
"new-notification": "התראה חדשה",
"event-notifiers": "מנגנוני התרעה על אירועים",
"apprise-url-skipped-if-blank": "כתובת Apprise (דלג אם ריק)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "הפעלת מתריע",
"what-events": "לאילו אירועים לרשום את מתריע זה?",
"user-events": "אירועי משתמש",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Obavijest",
"event-notifiers": "Obavještavatelji Događaja",
"apprise-url-skipped-if-blank": "Apprise URL (preskočeno ako je prazno)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Omogući obavještavanje",
"what-events": "Na koje događaje bi ovaj obavještavatelj trebao biti pretplaćen?",
"user-events": "Događaji Korisnika",

View file

@ -69,6 +69,7 @@
"new-notification": "Új értesítés",
"event-notifiers": "Esemény értesítők",
"apprise-url-skipped-if-blank": "Értesítendő URL (kihagy, ha üres)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Értesítés engedélyezése",
"what-events": "Milyen eseményekre figyeljen ez az értesítés?",
"user-events": "Felhasználói Események",

View file

@ -69,6 +69,7 @@
"new-notification": "Ný tilkynning",
"event-notifiers": "Viðburðar tilkynningar",
"apprise-url-skipped-if-blank": "Apprise URL (sleppt ef tómt)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Virkja tilkynningar",
"what-events": "Hvaða viðburði ætti þessi tilkynnir að vera áskrifandi að?",
"user-events": "Notenda viðburðir",

View file

@ -69,6 +69,7 @@
"new-notification": "Nuova Notifica",
"event-notifiers": "Notifiche Evento",
"apprise-url-skipped-if-blank": "Url di Apprise (ignorato se vuoto)",
"apprise-url-is-left-intentionally-blank": "Poiché gli URL Apprise contengono in genere informazioni sensibili, questo campo viene lasciato intenzionalmente vuoto durante la modifica. Se si desidera aggiornare l'URL, inserire qui il nuovo URL, altrimenti lasciarlo vuoto per mantenere l'URL corrente.",
"enable-notifier": "Abilita Notificatore",
"what-events": "Quali eventi dovrebbe sottoscrivere questo notificatore?",
"user-events": "Eventi Utente",
@ -80,7 +81,7 @@
"category-events": "Categoria Eventi",
"when-a-new-user-joins-your-group": "Quando un nuovo utente entra nel tuo gruppo",
"recipe-events": "Eventi di ricette",
"label-events": "Label Events"
"label-events": "Eventi Etichetta"
},
"general": {
"add": "Aggiungi",
@ -473,7 +474,7 @@
"comment": "Commento",
"comments": "Commenti",
"delete-confirmation": "Sei sicuro di voler eliminare questa ricetta?",
"admin-delete-confirmation": "You're about to delete a recipe that isn't yours using admin permissions. Are you sure?",
"admin-delete-confirmation": "Stai per eliminare una ricetta che non è tua usando i permessi di amministrazione. Sei sicuro?",
"delete-recipe": "Elimina Ricetta",
"description": "Descrizione",
"disable-amount": "Disabilita Quantità Ingredienti",
@ -581,10 +582,10 @@
"made-this": "L'Ho Preparato",
"how-did-it-turn-out": "Come è venuto?",
"user-made-this": "{user} l'ha preparato",
"added-to-timeline": "Added to timeline",
"failed-to-add-to-timeline": "Failed to add to timeline",
"failed-to-update-recipe": "Failed to update recipe",
"added-to-timeline-but-failed-to-add-image": "Added to timeline, but failed to add image",
"added-to-timeline": "Aggiunto alla cronologia",
"failed-to-add-to-timeline": "Impossibile aggiungere alla cronologia",
"failed-to-update-recipe": "Impossibile aggiornare la ricetta",
"added-to-timeline-but-failed-to-add-image": "Aggiunto alla cronologia, ma non è stato possibile aggiungere l'immagine",
"api-extras-description": "Le opzioni extra delle ricette sono una caratteristica fondamentale dell'API Mealie. Consentono di creare json personalizzati con coppie di chiavi/valore all'interno di una ricetta a cui fare riferimento tramite applicazioni terze. È possibile utilizzare queste chiavi per inserire informazioni, per esempio per attivare automazioni oppure per inoltrare messaggi personalizzati al dispositivo desiderato.",
"message-key": "Chiave Messaggio",
"parse": "Analizza",
@ -606,10 +607,10 @@
"create-recipe-from-an-image": "Crea ricetta da un'immagine",
"create-recipe-from-an-image-description": "Crea una ricetta caricando un'immagine di essa. Mealie tenterà di estrarre il testo dall'immagine usando l'IA e creare una ricetta da esso.",
"crop-and-rotate-the-image": "Ritaglia e ruota l'immagine in modo che solo il testo sia visibile e che sia orientato correttamente.",
"create-from-images": "Create from Images",
"create-from-images": "Crea da immagini",
"should-translate-description": "Traduci la ricetta nella mia lingua",
"please-wait-image-procesing": "Attendere, l'immagine è in fase di elaborazione. Potrebbe volerci un po' di tempo.",
"please-wait-images-processing": "Please wait, the images are processing. This may take some time.",
"please-wait-images-processing": "Attendere, le immagini sono in fase di elaborazione. Potrebbe volerci un po' di tempo.",
"bulk-url-import": "Importazione multipla URL",
"debug-scraper": "Debug Scraper",
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Crea una ricetta fornendo il nome. Tutte le ricette devono avere nomi univoci.",
@ -665,17 +666,17 @@
"no-unit": "Nessuna unità",
"missing-unit": "Crea unità mancante: {unit}",
"missing-food": "Crea cibo mancante: {food}",
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
"this-unit-could-not-be-parsed-automatically": "Questa unità non può essere analizzata automaticamente",
"this-food-could-not-be-parsed-automatically": "Questo alimento non può essere analizzato automaticamente",
"no-food": "Nessun Alimento"
},
"reset-servings-count": "Reimposta conteggio porzioni",
"not-linked-ingredients": "Ingredienti Aggiuntivi",
"upload-another-image": "Upload another image",
"upload-images": "Upload images",
"upload-more-images": "Upload more images",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
"upload-another-image": "Carica un'altra immagine",
"upload-images": "Carica immagini",
"upload-more-images": "Carica altre immagini",
"set-as-cover-image": "Imposta come immagine di copertina della ricetta",
"cover-image": "Immagine di copertina"
},
"recipe-finder": {
"recipe-finder": "Trova ricette",
@ -1168,7 +1169,7 @@
"group-details": "Dettagli Gruppo",
"group-details-description": "Prima di creare un account, è necessario creare un gruppo. Il gruppo conterrà solo voi, ma potrete invitare altre persone in seguito. I membri del gruppo possono condividere piani alimentari, liste della spesa, ricette e molto altro!",
"use-seed-data": "Utilizzo Dati Generati",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"use-seed-data-description": "Mealie include una raccolta di Alimenti, Unità ed Etichette che possono essere utilizzate per arricchire il proprio gruppo con dati utili per organizzare le proprie ricette. Questi dati vengono tradotti nella lingua selezionata. Si può sempre aggiungere o modificare questi dati in seguito.",
"account-details": "Dettagli dell'Account"
},
"validation": {

View file

@ -69,6 +69,7 @@
"new-notification": "新着通知",
"event-notifiers": "イベント通知",
"apprise-url-skipped-if-blank": "通知用URL (空欄の場合はスキップ)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "通知を有効にする",
"what-events": "この通知はどのイベントを購読すべきですか?",
"user-events": "ユーザーイベント",

View file

@ -69,6 +69,7 @@
"new-notification": "새 알림",
"event-notifiers": "이벤트 알림이",
"apprise-url-skipped-if-blank": "Apprise URL (비워두면 생략합니다)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "알림 활성화",
"what-events": "이 알리미는 어떤 이벤트를 구독해야 합니까?",
"user-events": "사용자 이벤트",

View file

@ -69,6 +69,7 @@
"new-notification": "Naujas pranešimas",
"event-notifiers": "Įvykių pranešimai",
"apprise-url-skipped-if-blank": "Apprise URL (praleidžiama, jei tuščia)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Įjungti pranešiklį",
"what-events": "Kokie įvykiai turėtų būti sekami?",
"user-events": "Naudotojų įvykiai",

View file

@ -69,6 +69,7 @@
"new-notification": "Jauns paziņojums",
"event-notifiers": "Notikumu paziņotāji",
"apprise-url-skipped-if-blank": "Apprise URL (izlaists, ja tukšs)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Iespējot paziņotāju",
"what-events": "Kādus notikumus šim paziņotājam vajadzētu abonēt?",
"user-events": "Lietotāju notikumi",

View file

@ -69,6 +69,7 @@
"new-notification": "Nieuwe melding",
"event-notifiers": "Meldingen van gebeurtenissen",
"apprise-url-skipped-if-blank": "URL van Apprise (overgeslagen als veld leeg is)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Activeer melding",
"what-events": "Op welke gebeurtenissen moet deze melding zich abonneren?",
"user-events": "Gebeurtenissen van gebruiker",

View file

@ -69,6 +69,7 @@
"new-notification": "Nytt varsel",
"event-notifiers": "Hendelsesvarsler",
"apprise-url-skipped-if-blank": "Apprise-URL (hoppes over hvis tom)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Aktiver varslingsagenten",
"what-events": "Hvilke hendelser skal denne varslingsagenten abonnere på?",
"user-events": "Brukerhendelser",

View file

@ -69,6 +69,7 @@
"new-notification": "Nowe powiadomienie",
"event-notifiers": "Powiadomienia o zdarzeniach",
"apprise-url-skipped-if-blank": "URL Apprise (pominięty, jeśli puste)",
"apprise-url-is-left-intentionally-blank": "Ponieważ adresy URL Apprise zawierają zazwyczaj poufne informacje, pole to pozostaje celowo puste podczas edycji. Jeśli chcesz zaktualizować adres URL, wprowadź ten nowy tutaj, w przeciwnym razie pozostaw puste, aby zachować bieżący adres URL.",
"enable-notifier": "Włącz Powiadomienie",
"what-events": "Jakie zdarzenia powinien subskrybować ten powiadamiający?",
"user-events": "Zdarzenia użytkownika",
@ -80,7 +81,7 @@
"category-events": "Wydarzenia kategorii",
"when-a-new-user-joins-your-group": "Kiedy nowy użytkownik dołączy do Twojej grupy",
"recipe-events": "Zdarzenia Przepisów",
"label-events": "Label Events"
"label-events": "Etykieta wydarzeń"
},
"general": {
"add": "Dodaj",
@ -674,8 +675,8 @@
"upload-another-image": "Prześlij kolejny obraz",
"upload-images": "Prześlij obraz",
"upload-more-images": "Prześlij więcej obrazów",
"set-as-cover-image": "Set as recipe cover image",
"cover-image": "Cover image"
"set-as-cover-image": "Ustaw jako okładkę przepisu",
"cover-image": "Okładka"
},
"recipe-finder": {
"recipe-finder": "Wyszukiwarka przepisów",
@ -1168,7 +1169,7 @@
"group-details": "Szczegóły grupy",
"group-details-description": "Zanim utworzysz konto musisz stworzyć grupę. Twoja grupa zawierać będzie tylko Ciebie, ale będziesz istniała możlwiość zaproszenia do niej innych. Użytkownicy Twojej grupy mogą współdzielić plany posiłków, listy zakupów, przepisy i więcej!",
"use-seed-data": "Użyj przykładowych danych",
"use-seed-data-description": "Mealie ships with a collection of Foods, Units, and Labels that can be used to populate your group with helpful data for organizing your recipes. These are translated into the language you currently have selected. You can always add to or modify this data later.",
"use-seed-data-description": "Wysyłka posiłków z kolekcją żywności, jednostek i etykiet, które mogą być użyte do wypełnienia Twojej grupy pomocnymi danymi do organizacji twoich przepisów. Są one tłumaczone na wybrany język. Zawsze możesz dodać lub zmodyfikować te dane później.",
"account-details": "Szczegóły konta"
},
"validation": {

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Notificação",
"event-notifiers": "Notificações de Eventos",
"apprise-url-skipped-if-blank": "URL Apprise (ignorado se estiver em branco)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Habilitar Notificador",
"what-events": "A quais eventos este notificador deve subscrever?",
"user-events": "Eventos do usuário",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Notificação",
"event-notifiers": "Notificadores de eventos",
"apprise-url-skipped-if-blank": "URL da Apprise (ignorado se vazio)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Ativar Notificador",
"what-events": "Que eventos este notificador deve subscrever?",
"user-events": "Eventos do utilizador",

View file

@ -69,6 +69,7 @@
"new-notification": "Notificare nouă",
"event-notifiers": "Notificatori de evenimente",
"apprise-url-skipped-if-blank": "URL Apprise (ignorat daca e gol)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Activare notificator",
"what-events": "La ce evenimente ar trebui să se înscrie acest notificator?",
"user-events": "Evenimente Utilizator",

View file

@ -69,6 +69,7 @@
"new-notification": "Новое уведомление",
"event-notifiers": "Уведомления о событии",
"apprise-url-skipped-if-blank": "URL-адрес (пропущен, если пусто)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Включить уведомления",
"what-events": "На какие события следует настроить уведомления?",
"user-events": "События пользователя",

View file

@ -69,6 +69,7 @@
"new-notification": "Nové upozornenie",
"event-notifiers": "Upozornenia udalostí",
"apprise-url-skipped-if-blank": "Informačná URL (preskočená, ak je prázdna)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Zapnúť notifikátor",
"what-events": "Pre ktoré udalosti si želáte zapnúť notifikátor?",
"user-events": "Udalosti používateľa",

View file

@ -69,6 +69,7 @@
"new-notification": "Novo obvestilo",
"event-notifiers": "Obvestila o dogodkih",
"apprise-url-skipped-if-blank": "Apprise URL (preskočeno, če je prazno)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Vključi obvestila",
"what-events": "Katere dogodke naj spremlja obveščevalni sistem?",
"user-events": "Dogodki uporabnika",

View file

@ -69,6 +69,7 @@
"new-notification": "Ново обавештење",
"event-notifiers": "Обавештавач о догађају",
"apprise-url-skipped-if-blank": "Apprise URL (прескочено ако је празно)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Омогући обавештење",
"what-events": "На које догађаје би требао да се претплати овај обавештавач?",
"user-events": "Догађаји корисника",

View file

@ -69,6 +69,7 @@
"new-notification": "Ny avisering",
"event-notifiers": "Händelseavisering",
"apprise-url-skipped-if-blank": "Apprise-URL (hoppa över om tom)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Aktivera avisering",
"what-events": "Vilka händelser ska denna avisering prenumerera på?",
"user-events": "Användarhändelser",

View file

@ -69,6 +69,7 @@
"new-notification": "Yeni bildirim",
"event-notifiers": "Etkinlik Bildirimleri",
"apprise-url-skipped-if-blank": "Apprise URL'si (boşsa geçilir)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Bildiriciyi Etkinleştir",
"what-events": "Bu bildirimci hangi olaylara abone olmalıdır?",
"user-events": "Kullanıcı Etkinlikleri",

View file

@ -69,6 +69,7 @@
"new-notification": "Нове сповіщення",
"event-notifiers": "Сповіщувачі",
"apprise-url-skipped-if-blank": "Apprise URL (пропущено якщо порожній)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Увімкнути сповіщувач",
"what-events": "На які події цей сповіщувач має бути підписаний?",
"user-events": "Події користувача",

View file

@ -69,6 +69,7 @@
"new-notification": "Thông báo mới",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "Enable Notifier",
"what-events": "What events should this notifier subscribe to?",
"user-events": "User Events",

View file

@ -69,6 +69,7 @@
"new-notification": "新通知",
"event-notifiers": "事件通知器",
"apprise-url-skipped-if-blank": "Apprise URL (如果为空则跳过)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "打开消息通知",
"what-events": "该通知器需要订阅哪些事件?",
"user-events": "用户事件",

View file

@ -69,6 +69,7 @@
"new-notification": "新通知",
"event-notifiers": "事件通知",
"apprise-url-skipped-if-blank": "Apprise 網址(空白則略過)",
"apprise-url-is-left-intentionally-blank": "Since Apprise URLs typically contain sensitive information, this field is left intentionally blank while editing. If you wish to update the URL, please enter the new one here, otherwise leave it blank to keep the current URL.",
"enable-notifier": "啟用通知功能",
"what-events": "要訂閱哪些事件通知?",
"user-events": "用戶相關事件",

View file

@ -1,5 +1,5 @@
import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
import { RecipeCookBook } from "~/lib/api/types/cookbook";
import { ReadCookBook } from "~/lib/api/types/cookbook";
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
const prefix = "/api";
@ -10,7 +10,7 @@ const routes = {
cookbooksGroupSlugCookbookId: (groupSlug: string | number, cookbookId: string | number) => `${exploreGroupSlug(groupSlug)}/cookbooks/${cookbookId}`,
};
export class PublicCookbooksApi extends BaseCRUDAPIReadOnly<RecipeCookBook> {
export class PublicCookbooksApi extends BaseCRUDAPIReadOnly<ReadCookBook> {
constructor(requests: ApiRequestInstance, groupSlug: string) {
super(
requests,

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
@ -38,67 +39,6 @@ export interface QueryFilterJSONPart {
attributeName?: string | null;
relationalOperator?: RelationalKeyword | RelationalOperator | null;
value?: string | string[] | null;
}
export interface RecipeCookBook {
name: string;
description?: string;
slug?: string | null;
position?: number;
public?: boolean;
queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
queryFilter?: QueryFilterJSON;
recipes: RecipeSummary[];
}
export interface RecipeSummary {
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string | null;
slug?: string;
image?: unknown;
recipeServings?: number;
recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
cookTime?: string | null;
performTime?: string | null;
description?: string | null;
recipeCategory?: RecipeCategory[] | null;
tags?: RecipeTag[] | null;
tools?: RecipeTool[];
rating?: number | null;
orgURL?: string | null;
dateAdded?: string | null;
dateUpdated?: string | null;
createdAt?: string | null;
updatedAt?: string | null;
lastMade?: string | null;
}
export interface RecipeCategory {
id?: string | null;
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string | null;
groupId?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTool {
id: string;
groupId?: string | null;
name: string;
slug: string;
householdsWithTool?: string[];
[k: string]: unknown;
}
export interface SaveCookBook {

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,4 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script

View file

@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
import type { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
import type { CreateCookBook, ReadCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
const prefix = "/api";
@ -8,7 +8,7 @@ const routes = {
cookbooksId: (id: number) => `${prefix}/households/cookbooks/${id}`,
};
export class CookbookAPI extends BaseCRUDAPI<CreateCookBook, RecipeCookBook, UpdateCookBook> {
export class CookbookAPI extends BaseCRUDAPI<CreateCookBook, ReadCookBook, UpdateCookBook> {
baseRoute: string = routes.cookbooks;
itemRoute = routes.cookbooksId;

View file

@ -100,12 +100,12 @@
"sweet corn": {
"aliases": [],
"description": "",
"name": "sweet corn",
"plural_name": "sweet corns"
"name": "mais dolce",
"plural_name": "mais dolci"
},
"chile pepper": {
"aliases": [
"capsicum"
"peperoncino"
],
"description": "",
"name": "peperoncino",
@ -132,8 +132,8 @@
"baby green": {
"aliases": [],
"description": "",
"name": "baby green",
"plural_name": "baby greens"
"name": "insalatina",
"plural_name": "insalatine"
},
"pumpkin": {
"aliases": [],
@ -151,112 +151,112 @@
"aliases": [],
"description": "",
"name": "cavolo",
"plural_name": "cabbages"
"plural_name": "cavoli"
},
"asparagu": {
"aliases": [],
"description": "",
"name": "asparagu",
"plural_name": "asparagus"
"name": "asparago",
"plural_name": "asparagi"
},
"kale": {
"aliases": [],
"description": "",
"name": "kale",
"plural_name": "kales"
"name": "cavolo riccio",
"plural_name": "cavoli ricci"
},
"arugula": {
"aliases": [],
"description": "",
"name": "arugula",
"plural_name": "arugulas"
"name": "rucola",
"plural_name": "rucole"
},
"leek": {
"aliases": [],
"description": "",
"name": "leek",
"plural_name": "leeks"
"name": "porro",
"plural_name": "porri"
},
"eggplant": {
"aliases": [],
"description": "",
"name": "eggplant",
"plural_name": "eggplants"
"name": "melanzana",
"plural_name": "melanzane"
},
"lettuce": {
"aliases": [],
"description": "",
"name": "lettuce",
"plural_name": "lettuces"
"name": "lattuga",
"plural_name": "lattughe"
},
"butternut squash": {
"aliases": [],
"description": "",
"name": "butternut squash",
"plural_name": "butternut squashes"
"name": "zucca violina",
"plural_name": "zucche violine"
},
"romaine": {
"aliases": [],
"description": "",
"name": "romaine",
"plural_name": "romaines"
"name": "lattuga romana",
"plural_name": "lattughe romane"
},
"beetroot": {
"aliases": [],
"description": "",
"name": "beetroot",
"plural_name": "beetroots"
"name": "barbabietola",
"plural_name": "barbabietole"
},
"brussels sprout": {
"aliases": [],
"description": "",
"name": "brussels sprout",
"plural_name": "brussels sprouts"
"name": "cavoletto di Bruxelles",
"plural_name": "cavoletti di Bruxelles"
},
"fennel": {
"aliases": [],
"description": "",
"name": "fennel",
"plural_name": "fennels"
"name": "finocchio",
"plural_name": "finocchi"
},
"sun dried tomato": {
"aliases": [],
"description": "",
"name": "sun dried tomato",
"plural_name": "sun dried tomatoes"
"name": "pomodoro essiccato al sole",
"plural_name": "pomodori essiccati al sole"
},
"radish": {
"aliases": [],
"description": "",
"name": "radish",
"plural_name": "radishes"
"name": "ravanello",
"plural_name": "ravanelli"
},
"red cabbage": {
"aliases": [],
"description": "",
"name": "red cabbage",
"plural_name": "red cabbages"
"name": "cavolo rosso",
"plural_name": "cavoli rossi"
},
"artichoke": {
"aliases": [],
"description": "",
"name": "artichoke",
"plural_name": "artichokes"
"name": "carciofo",
"plural_name": "carciofi"
},
"new potato": {
"aliases": [],
"description": "",
"name": "new potato",
"plural_name": "new potatoes"
"name": "patata nuova",
"plural_name": "patate nuove"
},
"summer squash": {
"aliases": [
"courgette",
"zucchina",
"gem squash"
],
"description": "",
"name": "summer squash",
"plural_name": "summer squashes"
"name": "zucca estiva",
"plural_name": "zucche estive"
},
"mixed green": {
"aliases": [],
@ -267,20 +267,20 @@
"parsnip": {
"aliases": [],
"description": "",
"name": "parsnip",
"plural_name": "parsnips"
"name": "pastinaca",
"plural_name": "pastinache"
},
"baby carrot": {
"aliases": [],
"description": "",
"name": "baby carrot",
"plural_name": "baby carrots"
"name": "carotina",
"plural_name": "carotine"
},
"mixed vegetable": {
"aliases": [],
"description": "",
"name": "mixed vegetable",
"plural_name": "mixed vegetables"
"name": "ortaggi misti",
"plural_name": "ortaggi misti"
},
"poblano pepper": {
"aliases": [],
@ -303,50 +303,50 @@
"cayenne pepper": {
"aliases": [],
"description": "",
"name": "cayenne pepper",
"plural_name": "cayenne peppers"
"name": "pepe di Caienna",
"plural_name": "pepi di Caienna"
},
"green tomato": {
"aliases": [],
"description": "",
"name": "green tomato",
"plural_name": "green tomatoes"
"name": "pomodoro verde",
"plural_name": "pomodori verdi"
},
"watercress": {
"aliases": [],
"description": "",
"name": "watercress",
"plural_name": "watercress"
"name": "crescione",
"plural_name": "crescioni"
},
"iceberg": {
"aliases": [],
"description": "",
"name": "iceberg",
"plural_name": "icebergs"
"plural_name": "iceberg"
},
"mashed potato": {
"aliases": [],
"description": "",
"name": "mashed potato",
"plural_name": "mashed potatoes"
"name": "purè di patate",
"plural_name": "purè di patate"
},
"horseradish": {
"aliases": [],
"description": "",
"name": "horseradish",
"plural_name": "horseradishes"
"name": "rafano",
"plural_name": "rafani"
},
"chard": {
"aliases": [],
"description": "",
"name": "chard",
"plural_name": "chards"
"name": "bietola",
"plural_name": "bietole"
},
"pimiento": {
"aliases": [],
"description": "",
"name": "pimiento",
"plural_name": "pimientoes"
"plural_name": "pimienti"
},
"spaghetti squash": {
"aliases": [],
@ -389,8 +389,8 @@
"turnip": {
"aliases": [],
"description": "",
"name": "turnip",
"plural_name": "turnips"
"name": "rapa",
"plural_name": "rape"
},
"thai chile pepper": {
"aliases": [],
@ -443,8 +443,8 @@
"plantain": {
"aliases": [],
"description": "",
"name": "plantain",
"plural_name": "plantains"
"name": "platano",
"plural_name": "platani"
},
"leaf lettuce": {
"aliases": [],

View file

@ -310,7 +310,7 @@
"aliases": [],
"description": "",
"name": "green tomato",
"plural_name": "green tomatoes"
"plural_name": "zielone pomidory"
},
"watercress": {
"aliases": [],
@ -357,8 +357,8 @@
"butter lettuce": {
"aliases": [],
"description": "",
"name": "butter lettuce",
"plural_name": "butter lettuces"
"name": "sałata masłowa",
"plural_name": "sałaty masłowe"
},
"hash brown": {
"aliases": [],
@ -389,8 +389,8 @@
"turnip": {
"aliases": [],
"description": "",
"name": "turnip",
"plural_name": "turnips"
"name": "rzepa",
"plural_name": "rzepy"
},
"thai chile pepper": {
"aliases": [],
@ -851,8 +851,8 @@
"dried fruit": {
"aliases": [],
"description": "",
"name": "dried fruit",
"plural_name": "dried fruits"
"name": "Suszony owoc",
"plural_name": "Suszone owoce"
},
"clementine": {
"aliases": [],
@ -881,14 +881,14 @@
"dried mango": {
"aliases": [],
"description": "",
"name": "dried mango",
"plural_name": "dried mangoes"
"name": "Suszone mango",
"plural_name": "Suszone mango"
},
"dried apple": {
"aliases": [],
"description": "",
"name": "dried apple",
"plural_name": "dried apples"
"name": "Suszone jabłko",
"plural_name": "Suszone jabłka"
},
"quince": {
"aliases": [],
@ -912,7 +912,7 @@
"aliases": [],
"description": "",
"name": "kumquat",
"plural_name": "kumquats"
"plural_name": "kumkwat"
},
"jackfruit": {
"aliases": [],
@ -923,8 +923,8 @@
"dragon fruit": {
"aliases": [],
"description": "",
"name": "dragon fruit",
"plural_name": "dragon fruits"
"name": "smoczy owoc",
"plural_name": "smocze owoce"
},
"mixed fruit": {
"aliases": [],
@ -959,20 +959,20 @@
"star fruit": {
"aliases": [],
"description": "",
"name": "star fruit",
"plural_name": "star fruits"
"name": "karambola",
"plural_name": "karambole"
},
"green papaya": {
"aliases": [],
"description": "",
"name": "green papaya",
"plural_name": "green papayas"
"name": "zielona papaja",
"plural_name": "zielone papaje"
},
"pomelo": {
"aliases": [],
"description": "",
"name": "pomelo",
"plural_name": "pomeloes"
"plural_name": "pomelo"
},
"chestnut puree": {
"aliases": [],
@ -1055,8 +1055,8 @@
"dried lemon": {
"aliases": [],
"description": "",
"name": "dried lemon",
"plural_name": "dried lemons"
"name": "Suszona cytryna",
"plural_name": "Suszone cytryny"
},
"young jackfruit": {
"aliases": [],
@ -1068,7 +1068,7 @@
"aliases": [],
"description": "",
"name": "durian",
"plural_name": "durians"
"plural_name": "duriany"
},
"freeze-dried apple": {
"aliases": [],
@ -1245,8 +1245,8 @@
"shiitake mushroom": {
"aliases": [],
"description": "",
"name": "shiitake mushroom",
"plural_name": "shiitake mushrooms"
"name": "Grzyb Shiitake",
"plural_name": "Grzyby Shiitake"
},
"portobello mushroom": {
"aliases": [],
@ -1299,8 +1299,8 @@
"black truffle": {
"aliases": [],
"description": "",
"name": "black truffle",
"plural_name": "black truffles"
"name": "czarna trufla",
"plural_name": "czarne trufle"
},
"morel mushroom": {
"aliases": [],
@ -1353,8 +1353,8 @@
"white truffle": {
"aliases": [],
"description": "",
"name": "white truffle",
"plural_name": "white truffles"
"name": "biała trufla",
"plural_name": "białe trufle"
},
"white fungu": {
"aliases": [],
@ -1453,26 +1453,26 @@
"strawberry": {
"aliases": [],
"description": "",
"name": "strawberry",
"plural_name": "strawberries"
"name": "truskawka",
"plural_name": "truskawki"
},
"blueberry": {
"aliases": [],
"description": "",
"name": "blueberry",
"plural_name": "blueberries"
"name": "borówka",
"plural_name": "borówki"
},
"raspberry": {
"aliases": [],
"description": "",
"name": "raspberry",
"plural_name": "raspberries"
"name": "malina",
"plural_name": "maliny"
},
"cranberry": {
"aliases": [],
"description": "",
"name": "cranberry",
"plural_name": "cranberries"
"name": "żurawina",
"plural_name": "żurawiny"
},
"cherry": {
"aliases": [],
@ -1483,8 +1483,8 @@
"blackberry": {
"aliases": [],
"description": "",
"name": "blackberry",
"plural_name": "blackberries"
"name": "jeżyna",
"plural_name": "jeżyny"
},
"berry mix": {
"aliases": [],
@ -1519,14 +1519,14 @@
"goji berry": {
"aliases": [],
"description": "",
"name": "goji berry",
"plural_name": "goji berries"
"name": "jagoda goji",
"plural_name": "jagody goji"
},
"dried blueberry": {
"aliases": [],
"description": "",
"name": "dried blueberry",
"plural_name": "dried blueberries"
"name": "Suszona borówka",
"plural_name": "Suszone borówki"
},
"freeze-dried strawberry": {
"aliases": [],
@ -1537,8 +1537,8 @@
"gooseberry": {
"aliases": [],
"description": "",
"name": "gooseberry",
"plural_name": "gooseberries"
"name": "agrest",
"plural_name": "agresty"
},
"freeze-dried raspberry": {
"aliases": [],
@ -1561,14 +1561,14 @@
"mulberry": {
"aliases": [],
"description": "",
"name": "mulberry",
"plural_name": "mulberries"
"name": "morwa",
"plural_name": "morwy"
},
"acai berry": {
"aliases": [],
"description": "",
"name": "acai berry",
"plural_name": "acai berries"
"name": "jagoda acai",
"plural_name": "jagody acai"
},
"canned cherry": {
"aliases": [],
@ -1585,8 +1585,8 @@
"elderberry": {
"aliases": [],
"description": "",
"name": "elderberry",
"plural_name": "elderberries"
"name": "czarny bez",
"plural_name": "czarny bez"
},
"freeze-dried blueberry": {
"aliases": [],
@ -1663,8 +1663,8 @@
"aronia berry": {
"aliases": [],
"description": "",
"name": "aronia berry",
"plural_name": "aronia berries"
"name": "jagoda aronii",
"plural_name": "jagody aronii"
},
"chokeberry": {
"aliases": [],
@ -5181,8 +5181,8 @@
"dried anchovy": {
"aliases": [],
"description": "",
"name": "dried anchovy",
"plural_name": "dried anchovies"
"name": "Suszona anszua",
"plural_name": "Suszone anszua"
},
"arctic char": {
"aliases": [],
@ -5331,8 +5331,8 @@
"dried fish": {
"aliases": [],
"description": "",
"name": "dried fish",
"plural_name": "dried fish"
"name": "suszona ryba",
"plural_name": "suszone ryby"
},
"flathead": {
"aliases": [],
@ -5587,8 +5587,8 @@
"dried shrimp": {
"aliases": [],
"description": "",
"name": "dried shrimp",
"plural_name": "dried shrimps"
"name": "suszona garnela",
"plural_name": "suszone garnele"
},
"bay scallop": {
"aliases": [],
@ -5641,8 +5641,8 @@
"dried prawn": {
"aliases": [],
"description": "",
"name": "dried prawn",
"plural_name": "dried prawns"
"name": "suszona krewetka",
"plural_name": "suszone krewetki"
},
"dulse seaweed": {
"aliases": [],
@ -6215,8 +6215,8 @@
"dried chili": {
"aliases": [],
"description": "",
"name": "dried chili",
"plural_name": "dried chilies"
"name": "suszone chili",
"plural_name": "suszone chili"
},
"black cardamom": {
"aliases": [],
@ -9332,8 +9332,8 @@
"dried pea": {
"aliases": [],
"description": "",
"name": "dried pea",
"plural_name": "dried peas"
"name": "suszony groch",
"plural_name": "suszony groch"
},
"pink bean": {
"aliases": [],

View file

@ -19,6 +19,8 @@ from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.user import PrivateUser
from mealie.schema.user.auth import CredentialsRequestForm
from .auth_cache import AuthCache
public_router = APIRouter(tags=["Users: Authentication"])
user_router = UserAPIRouter(tags=["Users: Authentication"])
logger = root_logger.get_logger("auth")
@ -27,7 +29,7 @@ remember_me_duration = timedelta(days=14)
settings = get_app_settings()
if settings.OIDC_READY:
oauth = OAuth()
oauth = OAuth(cache=AuthCache())
scope = None
if settings.OIDC_SCOPES_OVERRIDE:
scope = settings.OIDC_SCOPES_OVERRIDE

View file

@ -0,0 +1,51 @@
import time
from typing import Any
class AuthCache:
def __init__(self, threshold: int = 500, default_timeout: float = 300):
self.default_timeout = default_timeout
self._cache: dict[str, tuple[float, Any]] = {}
self.clear = self._cache.clear
self._threshold = threshold
def _prune(self):
if len(self._cache) > self._threshold:
now = time.time()
toremove = []
for idx, (key, (expires, _)) in enumerate(self._cache.items()):
if (expires != 0 and expires <= now) or idx % 3 == 0:
toremove.append(key)
for key in toremove:
self._cache.pop(key, None)
def _normalize_timeout(self, timeout: float | None) -> float:
if timeout is None:
timeout = self.default_timeout
if timeout > 0:
timeout = time.time() + timeout
return timeout
async def get(self, key: str) -> Any:
try:
expires, value = self._cache[key]
if expires == 0 or expires > time.time():
return value
except KeyError:
return None
async def set(self, key: str, value: Any, timeout: float | None = None) -> bool:
expires = self._normalize_timeout(timeout)
self._prune()
self._cache[key] = (expires, value)
return True
async def delete(self, key: str) -> bool:
return self._cache.pop(key, None) is not None
async def has(self, key: str) -> bool:
try:
expires, value = self._cache[key]
return expires == 0 or expires > time.time()
except KeyError:
return False

View file

@ -5,7 +5,7 @@ from pydantic import UUID4
from mealie.routes._base import controller
from mealie.routes._base.base_controllers import BasePublicHouseholdExploreController
from mealie.schema.cookbook.cookbook import ReadCookBook, RecipeCookBook
from mealie.schema.cookbook.cookbook import ReadCookBook
from mealie.schema.make_dependable import make_dependable
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
@ -39,8 +39,8 @@ class PublicCookbooksController(BasePublicHouseholdExploreController):
response.set_pagination_guides(self.get_explore_url_path(router.url_path_for("get_all")), q.model_dump())
return response
@router.get("/{item_id}", response_model=RecipeCookBook)
def get_one(self, item_id: UUID4 | str) -> RecipeCookBook:
@router.get("/{item_id}", response_model=ReadCookBook)
def get_one(self, item_id: UUID4 | str) -> ReadCookBook:
NOT_FOUND_EXCEPTION = HTTPException(404, "cookbook not found")
if isinstance(item_id, UUID):
match_attr = "id"
@ -58,13 +58,4 @@ class PublicCookbooksController(BasePublicHouseholdExploreController):
if not household or household.preferences.private_household:
raise NOT_FOUND_EXCEPTION
cross_household_recipes = self.cross_household_repos.recipes
recipes = cross_household_recipes.page_all(
PaginationQuery(
page=1,
per_page=-1,
query_filter="settings.public = TRUE AND household.preferences.privateHousehold = FALSE",
),
cookbook=cookbook,
)
return cookbook.cast(RecipeCookBook, recipes=recipes.items)
return cookbook

View file

@ -11,7 +11,7 @@ from mealie.routes._base import BaseCrudController, controller
from mealie.routes._base.mixins import HttpRepo
from mealie.routes._base.routers import MealieCrudRoute
from mealie.schema import mapper
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, SaveCookBook, UpdateCookBook
from mealie.schema.cookbook.cookbook import CookBookPagination
from mealie.schema.response.pagination import PaginationQuery
from mealie.services.event_bus_service.event_types import (
@ -101,7 +101,7 @@ class GroupCookbookController(BaseCrudController):
return all_updated
@router.get("/{item_id}", response_model=RecipeCookBook)
@router.get("/{item_id}", response_model=ReadCookBook)
def get_one(self, item_id: UUID4 | str):
if isinstance(item_id, UUID):
match_attr = "id"
@ -114,12 +114,10 @@ class GroupCookbookController(BaseCrudController):
# Allow fetching other households' cookbooks
cookbook = self.group_cookbooks.get_one(item_id, match_attr)
if cookbook is None:
raise HTTPException(status_code=404)
recipe_pagination = self.repos.recipes.page_all(PaginationQuery(page=1, per_page=-1, cookbook=cookbook))
return cookbook.cast(RecipeCookBook, recipes=recipe_pagination.items)
return cookbook
@router.put("/{item_id}", response_model=ReadCookBook)
def update_one(self, item_id: str, data: CreateCookBook):

View file

@ -3,11 +3,11 @@ from .datetime_parse import DateError, DateTimeError, DurationError, TimeError
from .mealie_model import HasUUID, MealieModel, SearchType
__all__ = [
"HasUUID",
"MealieModel",
"SearchType",
"DateError",
"DateTimeError",
"DurationError",
"TimeError",
"HasUUID",
"MealieModel",
"SearchType",
]

View file

@ -18,10 +18,28 @@ from .restore import (
from .settings import CustomPageBase, CustomPageOut
__all__ = [
"MaintenanceLogs",
"MaintenanceStorageDetails",
"MaintenanceSummary",
"ChowdownURL",
"MigrationFile",
"MigrationImport",
"Migrations",
"CustomPageBase",
"CustomPageOut",
"CommentImport",
"CustomPageImport",
"GroupImport",
"ImportBase",
"NotificationImport",
"RecipeImport",
"SettingsImport",
"UserImport",
"AllBackups",
"BackupFile",
"BackupOptions",
"CreateBackup",
"ImportJob",
"AdminAboutInfo",
"AppInfo",
"AppStartupInfo",
@ -31,23 +49,5 @@ __all__ = [
"EmailReady",
"EmailSuccess",
"EmailTest",
"CustomPageBase",
"CustomPageOut",
"AllBackups",
"BackupFile",
"BackupOptions",
"CreateBackup",
"ImportJob",
"MaintenanceLogs",
"MaintenanceStorageDetails",
"MaintenanceSummary",
"DebugResponse",
"CommentImport",
"CustomPageImport",
"GroupImport",
"ImportBase",
"NotificationImport",
"RecipeImport",
"SettingsImport",
"UserImport",
]

View file

@ -1,11 +1,10 @@
# This file is auto-generated by gen_schema_exports.py
from .cookbook import CookBookPagination, CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
from .cookbook import CookBookPagination, CreateCookBook, ReadCookBook, SaveCookBook, UpdateCookBook
__all__ = [
"CookBookPagination",
"CreateCookBook",
"ReadCookBook",
"RecipeCookBook",
"SaveCookBook",
"UpdateCookBook",
]

View file

@ -7,7 +7,6 @@ from slugify import slugify
from mealie.core.root_logger import get_logger
from mealie.db.models.recipe import RecipeModel
from mealie.schema._mealie import MealieModel
from mealie.schema.recipe.recipe import RecipeSummary
from mealie.schema.response.pagination import PaginationBase
from mealie.schema.response.query_filter import QueryFilterBuilder, QueryFilterJSON
@ -84,10 +83,3 @@ class ReadCookBook(UpdateCookBook):
class CookBookPagination(PaginationBase):
items: list[ReadCookBook]
class RecipeCookBook(ReadCookBook):
group_id: UUID4
household_id: UUID4
recipes: list[RecipeSummary]
model_config = ConfigDict(from_attributes=True)

View file

@ -7,13 +7,13 @@ from .group_seeder import SeederConfig
from .group_statistics import GroupStorage
__all__ = [
"GroupAdminUpdate",
"GroupStorage",
"GroupDataExport",
"SeederConfig",
"CreateGroupPreferences",
"ReadGroupPreferences",
"UpdateGroupPreferences",
"GroupStorage",
"DataMigrationCreate",
"SupportedMigrations",
"SeederConfig",
"GroupAdminUpdate",
]

View file

@ -70,6 +70,49 @@ from .invite_token import CreateInviteToken, EmailInitationResponse, EmailInvita
from .webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination, WebhookType
__all__ = [
"GroupEventNotifierCreate",
"GroupEventNotifierOptions",
"GroupEventNotifierOptionsOut",
"GroupEventNotifierOptionsSave",
"GroupEventNotifierOut",
"GroupEventNotifierPrivate",
"GroupEventNotifierSave",
"GroupEventNotifierUpdate",
"GroupEventPagination",
"CreateGroupRecipeAction",
"GroupRecipeActionOut",
"GroupRecipeActionPagination",
"GroupRecipeActionPayload",
"GroupRecipeActionType",
"SaveGroupRecipeAction",
"CreateWebhook",
"ReadWebhook",
"SaveWebhook",
"WebhookPagination",
"WebhookType",
"CreateHouseholdPreferences",
"ReadHouseholdPreferences",
"SaveHouseholdPreferences",
"UpdateHouseholdPreferences",
"HouseholdCreate",
"HouseholdInDB",
"HouseholdPagination",
"HouseholdRecipeBase",
"HouseholdRecipeCreate",
"HouseholdRecipeOut",
"HouseholdRecipeSummary",
"HouseholdRecipeUpdate",
"HouseholdSave",
"HouseholdSummary",
"HouseholdUserSummary",
"UpdateHousehold",
"UpdateHouseholdAdmin",
"HouseholdStatistics",
"CreateInviteToken",
"EmailInitationResponse",
"EmailInvitation",
"ReadInviteToken",
"SaveInviteToken",
"ShoppingListAddRecipeParams",
"ShoppingListAddRecipeParamsBulk",
"ShoppingListCreate",
@ -93,48 +136,5 @@ __all__ = [
"ShoppingListSave",
"ShoppingListSummary",
"ShoppingListUpdate",
"GroupEventNotifierCreate",
"GroupEventNotifierOptions",
"GroupEventNotifierOptionsOut",
"GroupEventNotifierOptionsSave",
"GroupEventNotifierOut",
"GroupEventNotifierPrivate",
"GroupEventNotifierSave",
"GroupEventNotifierUpdate",
"GroupEventPagination",
"CreateGroupRecipeAction",
"GroupRecipeActionOut",
"GroupRecipeActionPagination",
"GroupRecipeActionPayload",
"GroupRecipeActionType",
"SaveGroupRecipeAction",
"CreateHouseholdPreferences",
"ReadHouseholdPreferences",
"SaveHouseholdPreferences",
"UpdateHouseholdPreferences",
"SetPermissions",
"CreateInviteToken",
"EmailInitationResponse",
"EmailInvitation",
"ReadInviteToken",
"SaveInviteToken",
"HouseholdStatistics",
"CreateWebhook",
"ReadWebhook",
"SaveWebhook",
"WebhookPagination",
"WebhookType",
"HouseholdCreate",
"HouseholdInDB",
"HouseholdPagination",
"HouseholdRecipeBase",
"HouseholdRecipeCreate",
"HouseholdRecipeOut",
"HouseholdRecipeSummary",
"HouseholdRecipeUpdate",
"HouseholdSave",
"HouseholdSummary",
"HouseholdUserSummary",
"UpdateHousehold",
"UpdateHouseholdAdmin",
]

View file

@ -12,6 +12,9 @@ from .plan_rules import PlanRulesCreate, PlanRulesDay, PlanRulesOut, PlanRulesPa
from .shopping_list import ListItem, ShoppingListIn, ShoppingListOut
__all__ = [
"ListItem",
"ShoppingListIn",
"ShoppingListOut",
"CreatePlanEntry",
"CreateRandomEntry",
"PlanEntryPagination",
@ -19,9 +22,6 @@ __all__ = [
"ReadPlanEntry",
"SavePlanEntry",
"UpdatePlanEntry",
"ListItem",
"ShoppingListIn",
"ShoppingListOut",
"PlanRulesCreate",
"PlanRulesDay",
"PlanRulesOut",

View file

@ -89,6 +89,35 @@ from .recipe_tool import RecipeToolCreate, RecipeToolOut, RecipeToolResponse, Re
from .request_helpers import RecipeDuplicate, RecipeSlug, RecipeZipTokenResponse, SlugResponse, UpdateImageResponse
__all__ = [
"IngredientReferences",
"RecipeStep",
"RecipeNote",
"CategoryBase",
"CategoryIn",
"CategoryOut",
"CategorySave",
"RecipeCategoryResponse",
"RecipeTagResponse",
"TagBase",
"TagIn",
"TagOut",
"TagSave",
"RecipeAsset",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"RecipeSuggestionQuery",
"RecipeSuggestionResponse",
"RecipeSuggestionResponseItem",
"Nutrition",
"RecipeShareToken",
"RecipeShareTokenCreate",
"RecipeShareTokenSave",
"RecipeShareTokenSummary",
"CreateIngredientFood",
"CreateIngredientFoodAlias",
"CreateIngredientUnit",
@ -111,27 +140,13 @@ __all__ = [
"SaveIngredientFood",
"SaveIngredientUnit",
"UnitFoodBase",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"Nutrition",
"AssignCategories",
"AssignSettings",
"AssignTags",
"DeleteRecipes",
"ExportBase",
"ExportRecipes",
"ExportTypes",
"RecipeCommentCreate",
"RecipeCommentOut",
"RecipeCommentPagination",
"RecipeCommentSave",
"RecipeCommentUpdate",
"UserBase",
"RecipeSettings",
"CreateRecipe",
"CreateRecipeBulk",
"CreateRecipeByUrlBulk",
@ -145,40 +160,25 @@ __all__ = [
"RecipeTagPagination",
"RecipeTool",
"RecipeToolPagination",
"IngredientReferences",
"RecipeStep",
"RecipeNote",
"RecipeSuggestionQuery",
"RecipeSuggestionResponse",
"RecipeSuggestionResponseItem",
"RecipeSettings",
"RecipeShareToken",
"RecipeShareTokenCreate",
"RecipeShareTokenSave",
"RecipeShareTokenSummary",
"RecipeAsset",
"ScrapeRecipe",
"ScrapeRecipeBase",
"ScrapeRecipeData",
"ScrapeRecipeTest",
"AssignCategories",
"AssignSettings",
"AssignTags",
"DeleteRecipes",
"ExportBase",
"ExportRecipes",
"ExportTypes",
"RecipeToolCreate",
"RecipeToolOut",
"RecipeToolResponse",
"RecipeToolSave",
"RecipeImageTypes",
"RecipeDuplicate",
"RecipeSlug",
"RecipeZipTokenResponse",
"SlugResponse",
"UpdateImageResponse",
"RecipeToolCreate",
"RecipeToolOut",
"RecipeToolResponse",
"RecipeToolSave",
"CategoryBase",
"CategoryIn",
"CategoryOut",
"CategorySave",
"RecipeCategoryResponse",
"RecipeTagResponse",
"TagBase",
"TagIn",
"TagOut",
"TagSave",
"ScrapeRecipe",
"ScrapeRecipeBase",
"ScrapeRecipeData",
"ScrapeRecipeTest",
"RecipeImageTypes",
]

View file

@ -28,14 +28,14 @@ __all__ = [
"QueryFilterJSONPart",
"RelationalKeyword",
"RelationalOperator",
"SearchFilter",
"ValidationResponse",
"OrderByNullPosition",
"OrderDirection",
"PaginationBase",
"PaginationQuery",
"RecipeSearchQuery",
"RequestQuery",
"ValidationResponse",
"SearchFilter",
"ErrorResponse",
"FileTokenResponse",
"SuccessResponse",

View file

@ -38,6 +38,12 @@ from .user_passwords import (
)
__all__ = [
"ForgotPassword",
"PasswordResetToken",
"PrivatePasswordResetToken",
"ResetPassword",
"SavePasswordResetToken",
"ValidateResetToken",
"CredentialsRequest",
"CredentialsRequestForm",
"Token",
@ -69,10 +75,4 @@ __all__ = [
"UserRatings",
"UserSummary",
"UserSummaryPagination",
"ForgotPassword",
"PasswordResetToken",
"PrivatePasswordResetToken",
"ResetPassword",
"SavePasswordResetToken",
"ValidateResetToken",
]

222
poetry.lock generated
View file

@ -1082,14 +1082,14 @@ files = [
[[package]]
name = "ingredient-parser-nlp"
version = "2.1.1"
version = "2.2.0"
description = "A Python package to parse structured information from recipe ingredient sentences"
optional = false
python-versions = "<3.14,>=3.10"
groups = ["main"]
files = [
{file = "ingredient_parser_nlp-2.1.1-py3-none-any.whl", hash = "sha256:e09f73b28d2805d33f5523b84a304225bf62ebdca47560aa8e6c7176442ef6b6"},
{file = "ingredient_parser_nlp-2.1.1.tar.gz", hash = "sha256:e7070b849d05395006b6a27923304b01ed4da04b53a82047c5ddddf1bbfe1fc7"},
{file = "ingredient_parser_nlp-2.2.0-py3-none-any.whl", hash = "sha256:b9be48c0a27eb972f8cfdc0f755cfa32fd16a598ff714b8f1a3b244c8622bed0"},
{file = "ingredient_parser_nlp-2.2.0.tar.gz", hash = "sha256:cabd12bd01a030b19f1859a968219c7f2cd68dd08d03484194245f7454d85d20"},
]
[package.dependencies]
@ -1612,19 +1612,20 @@ pyyaml = ">=5.1"
[[package]]
name = "mkdocs-material"
version = "9.6.16"
version = "9.6.17"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "mkdocs_material-9.6.16-py3-none-any.whl", hash = "sha256:8d1a1282b892fe1fdf77bfeb08c485ba3909dd743c9ba69a19a40f637c6ec18c"},
{file = "mkdocs_material-9.6.16.tar.gz", hash = "sha256:d07011df4a5c02ee0877496d9f1bfc986cfb93d964799b032dd99fe34c0e9d19"},
{file = "mkdocs_material-9.6.17-py3-none-any.whl", hash = "sha256:221dd8b37a63f52e580bcab4a7e0290e4a6f59bd66190be9c3d40767e05f9417"},
{file = "mkdocs_material-9.6.17.tar.gz", hash = "sha256:48ae7aec72a3f9f501a70be3fbd329c96ff5f5a385b67a1563e5ed5ce064affe"},
]
[package.dependencies]
babel = ">=2.10,<3.0"
backrefs = ">=5.7.post1,<6.0"
click = "<8.2.2"
colorama = ">=0.4,<1.0"
jinja2 = ">=3.1,<4.0"
markdown = ">=3.2,<4.0"
@ -1877,95 +1878,95 @@ voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"]
[[package]]
name = "orjson"
version = "3.11.1"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
version = "3.11.2"
description = ""
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "orjson-3.11.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:92d771c492b64119456afb50f2dff3e03a2db8b5af0eba32c5932d306f970532"},
{file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0085ef83a4141c2ed23bfec5fecbfdb1e95dd42fc8e8c76057bdeeec1608ea65"},
{file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5caf7f13f2e1b4e137060aed892d4541d07dabc3f29e6d891e2383c7ed483440"},
{file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f716bcc166524eddfcf9f13f8209ac19a7f27b05cf591e883419079d98c8c99d"},
{file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507d6012fab05465d8bf21f5d7f4635ba4b6d60132874e349beff12fb51af7fe"},
{file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1545083b0931f754c80fd2422a73d83bea7a6d1b6de104a5f2c8dd3d64c291e"},
{file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e217ce3bad76351e1eb29ebe5ca630326f45cd2141f62620107a229909501a3"},
{file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06ef26e009304bda4df42e4afe518994cde6f89b4b04c0ff24021064f83f4fbb"},
{file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ba49683b87bea3ae1489a88e766e767d4f423a669a61270b6d6a7ead1c33bd65"},
{file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5072488fcc5cbcda2ece966d248e43ea1d222e19dd4c56d3f82747777f24d864"},
{file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f58ae2bcd119226fe4aa934b5880fe57b8e97b69e51d5d91c88a89477a307016"},
{file = "orjson-3.11.1-cp310-cp310-win32.whl", hash = "sha256:6723be919c07906781b9c63cc52dc7d2fb101336c99dd7e85d3531d73fb493f7"},
{file = "orjson-3.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:5fd44d69ddfdfb4e8d0d83f09d27a4db34930fba153fbf79f8d4ae8b47914e04"},
{file = "orjson-3.11.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:15e2a57ce3b57c1a36acffcc02e823afefceee0a532180c2568c62213c98e3ef"},
{file = "orjson-3.11.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:17040a83ecaa130474af05bbb59a13cfeb2157d76385556041f945da936b1afd"},
{file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a68f23f09e5626cc0867a96cf618f68b91acb4753d33a80bf16111fd7f9928c"},
{file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47e07528bb6ccbd6e32a55e330979048b59bfc5518b47c89bc7ab9e3de15174a"},
{file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3807cce72bf40a9d251d689cbec28d2efd27e0f6673709f948f971afd52cb09"},
{file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b2dc7e88da4ca201c940f5e6127998d9e89aa64264292334dad62854bc7fc27"},
{file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3091dad33ac9e67c0a550cfff8ad5be156e2614d6f5d2a9247df0627751a1495"},
{file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed0fce2307843b79a0c83de49f65b86197f1e2310de07af9db2a1a77a61ce4c"},
{file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a31e84782a18c30abd56774c0cfa7b9884589f4d37d9acabfa0504dad59bb9d"},
{file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26b6c821abf1ae515fbb8e140a2406c9f9004f3e52acb780b3dee9bfffddbd84"},
{file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f857b3d134b36a8436f1e24dcb525b6b945108b30746c1b0b556200b5cb76d39"},
{file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df146f2a14116ce80f7da669785fcb411406d8e80136558b0ecda4c924b9ac55"},
{file = "orjson-3.11.1-cp311-cp311-win32.whl", hash = "sha256:d777c57c1f86855fe5492b973f1012be776e0398571f7cc3970e9a58ecf4dc17"},
{file = "orjson-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9a5fd589951f02ec2fcb8d69339258bbf74b41b104c556e6d4420ea5e059313"},
{file = "orjson-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:4cddbe41ee04fddad35d75b9cf3e3736ad0b80588280766156b94783167777af"},
{file = "orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910"},
{file = "orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d"},
{file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5"},
{file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b"},
{file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf"},
{file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56"},
{file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289"},
{file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81"},
{file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968"},
{file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a"},
{file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d"},
{file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4"},
{file = "orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf"},
{file = "orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae"},
{file = "orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d"},
{file = "orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e"},
{file = "orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064"},
{file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6"},
{file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894"},
{file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912"},
{file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2"},
{file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8"},
{file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4"},
{file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24"},
{file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014"},
{file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058"},
{file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f"},
{file = "orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092"},
{file = "orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77"},
{file = "orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4"},
{file = "orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b"},
{file = "orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e"},
{file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727"},
{file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c"},
{file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037"},
{file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa"},
{file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de"},
{file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45"},
{file = "orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1"},
{file = "orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8"},
{file = "orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1"},
{file = "orjson-3.11.1-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3d593a9e0bccf2c7401ae53625b519a7ad7aa555b1c82c0042b322762dc8af4e"},
{file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baad413c498fc1eef568504f11ea46bc71f94b845c075e437da1e2b85b4fb86"},
{file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22cf17ae1dae3f9b5f37bfcdba002ed22c98bbdb70306e42dc18d8cc9b50399a"},
{file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e855c1e97208133ce88b3ef6663c9a82ddf1d09390cd0856a1638deee0390c3c"},
{file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5861c5f7acff10599132854c70ab10abf72aebf7c627ae13575e5f20b1ab8fe"},
{file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1e6415c5b5ff3a616a6dafad7b6ec303a9fc625e9313c8e1268fb1370a63dcb"},
{file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912579642f5d7a4a84d93c5eed8daf0aa34e1f2d3f4dc6571a8e418703f5701e"},
{file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2092e1d3b33f64e129ff8271642afddc43763c81f2c30823b4a4a4a5f2ea5b55"},
{file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:b8ac64caba1add2c04e9cd4782d4d0c4d6c554b7a3369bdec1eed7854c98db7b"},
{file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:23196b826ebc85c43f8e27bee0ab33c5fb13a29ea47fb4fcd6ebb1e660eb0252"},
{file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f2d3364cfad43003f1e3d564a069c8866237cca30f9c914b26ed2740b596ed00"},
{file = "orjson-3.11.1-cp39-cp39-win32.whl", hash = "sha256:20b0dca94ea4ebe4628330de50975b35817a3f52954c1efb6d5d0498a3bbe581"},
{file = "orjson-3.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:200c3ad7ed8b5d31d49143265dfebd33420c4b61934ead16833b5cd2c3d241be"},
{file = "orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96"},
{file = "orjson-3.11.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d6b8a78c33496230a60dc9487118c284c15ebdf6724386057239641e1eb69761"},
{file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc04036eeae11ad4180d1f7b5faddb5dab1dee49ecd147cd431523869514873b"},
{file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c04325839c5754c253ff301cee8aaed7442d974860a44447bb3be785c411c27"},
{file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32769e04cd7fdc4a59854376211145a1bbbc0aea5e9d6c9755d3d3c301d7c0df"},
{file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff285d14917ea1408a821786e3677c5261fa6095277410409c694b8e7720ae0"},
{file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2662f908114864b63ff75ffe6ffacf996418dd6cc25e02a72ad4bda81b1ec45a"},
{file = "orjson-3.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab463cf5d08ad6623a4dac1badd20e88a5eb4b840050c4812c782e3149fe2334"},
{file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:64414241bde943cbf3c00d45fcb5223dca6d9210148ba984aae6b5d63294502b"},
{file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7773e71c0ae8c9660192ff144a3d69df89725325e3d0b6a6bb2c50e5ebaf9b84"},
{file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:652ca14e283b13ece35bf3a86503c25592f294dbcfc5bb91b20a9c9a62a3d4be"},
{file = "orjson-3.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:26e99e98df8990ecfe3772bbdd7361f602149715c2cbc82e61af89bfad9528a4"},
{file = "orjson-3.11.2-cp310-cp310-win32.whl", hash = "sha256:5814313b3e75a2be7fe6c7958201c16c4560e21a813dbad25920752cecd6ad66"},
{file = "orjson-3.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc471ce2225ab4c42ca672f70600d46a8b8e28e8d4e536088c1ccdb1d22b35ce"},
{file = "orjson-3.11.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:888b64ef7eaeeff63f773881929434a5834a6a140a63ad45183d59287f07fc6a"},
{file = "orjson-3.11.2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:83387cc8b26c9fa0ae34d1ea8861a7ae6cff8fb3e346ab53e987d085315a728e"},
{file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e35f003692c216d7ee901b6b916b5734d6fc4180fcaa44c52081f974c08e17"},
{file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a0a4c29ae90b11d0c00bcc31533854d89f77bde2649ec602f512a7e16e00640"},
{file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:585d712b1880f68370108bc5534a257b561672d1592fae54938738fe7f6f1e33"},
{file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d08e342a7143f8a7c11f1c4033efe81acbd3c98c68ba1b26b96080396019701f"},
{file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c0f84fc50398773a702732c87cd622737bf11c0721e6db3041ac7802a686fb"},
{file = "orjson-3.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:140f84e3c8d4c142575898c91e3981000afebf0333df753a90b3435d349a5fe5"},
{file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96304a2b7235e0f3f2d9363ddccdbfb027d27338722fe469fe656832a017602e"},
{file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d7612bb227d5d9582f1f50a60bd55c64618fc22c4a32825d233a4f2771a428a"},
{file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a134587d18fe493befc2defffef2a8d27cfcada5696cb7234de54a21903ae89a"},
{file = "orjson-3.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b84455e60c4bc12c1e4cbaa5cfc1acdc7775a9da9cec040e17232f4b05458bd"},
{file = "orjson-3.11.2-cp311-cp311-win32.whl", hash = "sha256:f0660efeac223f0731a70884e6914a5f04d613b5ae500744c43f7bf7b78f00f9"},
{file = "orjson-3.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:955811c8405251d9e09cbe8606ad8fdef49a451bcf5520095a5ed38c669223d8"},
{file = "orjson-3.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:2e4d423a6f838552e3a6d9ec734b729f61f88b1124fd697eab82805ea1a2a97d"},
{file = "orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486"},
{file = "orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1"},
{file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131"},
{file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c"},
{file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14"},
{file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448"},
{file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c"},
{file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804"},
{file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307"},
{file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219"},
{file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45"},
{file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e"},
{file = "orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e"},
{file = "orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732"},
{file = "orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36"},
{file = "orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219"},
{file = "orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad"},
{file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2"},
{file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe"},
{file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae"},
{file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6"},
{file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1"},
{file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa"},
{file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e"},
{file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15"},
{file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac"},
{file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8"},
{file = "orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5"},
{file = "orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d"},
{file = "orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535"},
{file = "orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7"},
{file = "orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81"},
{file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f"},
{file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7"},
{file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4"},
{file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f"},
{file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7"},
{file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6"},
{file = "orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f"},
{file = "orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8"},
{file = "orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67"},
{file = "orjson-3.11.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:957f10c7b5bce3d3f2ad577f3b307c784f5dabafcce3b836229c269c11841c86"},
{file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a669e31ab8eb466c9142ac7a4be2bb2758ad236a31ef40dcd4cf8774ab40f33"},
{file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adedf7d887416c51ad49de3c53b111887e0b63db36c6eb9f846a8430952303d8"},
{file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad8873979659ad98fc56377b9c5b93eb8059bf01e6412f7abf7dbb3d637a991"},
{file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9482ef83b2bf796157566dd2d2742a8a1e377045fe6065fa67acb1cb1d21d9a3"},
{file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73cee7867c1fcbd1cc5b6688b3e13db067f968889242955780123a68b3d03316"},
{file = "orjson-3.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:465166773265f3cc25db10199f5d11c81898a309e26a2481acf33ddbec433fda"},
{file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc000190a7b1d2d8e36cba990b3209a1e15c0efb6c7750e87f8bead01afc0d46"},
{file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:df3fdd8efa842ccbb81135d6f58a73512f11dba02ed08d9466261c2e9417af4e"},
{file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3dacfc621be3079ec69e0d4cb32e3764067726e0ef5a5576428f68b6dc85b4f6"},
{file = "orjson-3.11.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9fdff73a029cde5f4a1cf5ec9dbc6acab98c9ddd69f5580c2b3f02ce43ba9f9f"},
{file = "orjson-3.11.2-cp39-cp39-win32.whl", hash = "sha256:b1efbdc479c6451138c3733e415b4d0e16526644e54e2f3689f699c4cda303bf"},
{file = "orjson-3.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:c9ec0cc0d4308cad1e38a1ee23b64567e2ff364c2a3fe3d6cbc69cf911c45712"},
{file = "orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309"},
]
[[package]]
@ -3287,30 +3288,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.12.8"
version = "0.12.9"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513"},
{file = "ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc"},
{file = "ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec"},
{file = "ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53"},
{file = "ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f"},
{file = "ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f"},
{file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609"},
{file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a"},
{file = "ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb"},
{file = "ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818"},
{file = "ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea"},
{file = "ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3"},
{file = "ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161"},
{file = "ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46"},
{file = "ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3"},
{file = "ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e"},
{file = "ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749"},
{file = "ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033"},
{file = "ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e"},
{file = "ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f"},
{file = "ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70"},
{file = "ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53"},
{file = "ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff"},
{file = "ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756"},
{file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea"},
{file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0"},
{file = "ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce"},
{file = "ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340"},
{file = "ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb"},
{file = "ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af"},
{file = "ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc"},
{file = "ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66"},
{file = "ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7"},
{file = "ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93"},
{file = "ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908"},
{file = "ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089"},
{file = "ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a"},
]
[[package]]

View file

@ -217,13 +217,14 @@ def test_get_cookbooks_with_recipes(api_client: TestClient, unique_user: TestUse
)
)
# Get the cookbook and make sure we only get the public recipes from each household
response = api_client.get(api_routes.explore_groups_group_slug_cookbooks_item_id(unique_user.group_id, cookbook.id))
# Get the cookbook recipes and make sure we only get the public recipes from each household
response = api_client.get(
api_routes.explore_groups_group_slug_recipes(unique_user.group_id), params={"cookbook": cookbook.slug}
)
assert response.status_code == 200
cookbook_data = response.json()
assert cookbook_data["id"] == str(cookbook.id)
recipe_data = response.json()
cookbook_recipe_ids: set[str] = {recipe["id"] for recipe in cookbook_data["recipes"]}
cookbook_recipe_ids: set[str] = {recipe["id"] for recipe in recipe_data["items"]}
assert len(cookbook_recipe_ids) == 2
assert str(public_recipe.id) in cookbook_recipe_ids
assert str(private_recipe.id) not in cookbook_recipe_ids
@ -297,13 +298,14 @@ def test_get_cookbooks_private_household(api_client: TestClient, unique_user: Te
)
)
# Get the cookbook and make sure we only get the public recipes from each household
response = api_client.get(api_routes.explore_groups_group_slug_cookbooks_item_id(unique_user.group_id, cookbook.id))
# Get the cookbook recipes and make sure we only get the public recipes from each household
response = api_client.get(
api_routes.explore_groups_group_slug_recipes(unique_user.group_id), params={"cookbook": cookbook.slug}
)
assert response.status_code == 200
cookbook_data = response.json()
assert cookbook_data["id"] == str(cookbook.id)
recipe_data = response.json()
cookbook_recipe_ids: set[str] = {recipe["id"] for recipe in cookbook_data["recipes"]}
cookbook_recipe_ids: set[str] = {recipe["id"] for recipe in recipe_data["items"]}
assert len(cookbook_recipe_ids) == 1
assert str(public_recipe.id) in cookbook_recipe_ids
assert str(other_household_private_recipe.id) not in cookbook_recipe_ids

View file

@ -0,0 +1,239 @@
import asyncio
import time
from unittest.mock import patch
import pytest
from mealie.routes.auth.auth_cache import AuthCache
@pytest.fixture
def cache():
return AuthCache(threshold=5, default_timeout=1.0)
@pytest.mark.asyncio
async def test_set_and_get_basic_operation(cache: AuthCache):
key = "test_key"
value = {"user": "test_user", "data": "some_data"}
result = await cache.set(key, value)
assert result is True
retrieved = await cache.get(key)
assert retrieved == value
@pytest.mark.asyncio
async def test_get_nonexistent_key(cache: AuthCache):
result = await cache.get("nonexistent_key")
assert result is None
@pytest.mark.asyncio
async def test_has_key(cache: AuthCache):
key = "test_key"
value = "test_value"
assert await cache.has(key) is False
await cache.set(key, value)
assert await cache.has(key) is True
@pytest.mark.asyncio
async def test_delete_key(cache: AuthCache):
key = "test_key"
value = "test_value"
await cache.set(key, value)
assert await cache.has(key) is True
result = await cache.delete(key)
assert result is True
assert await cache.has(key) is False
assert await cache.get(key) is None
@pytest.mark.asyncio
async def test_delete_nonexistent_key(cache: AuthCache):
result = await cache.delete("nonexistent_key")
assert result is False
@pytest.mark.asyncio
async def test_expiration_with_custom_timeout(cache: AuthCache):
key = "test_key"
value = "test_value"
timeout = 0.1 # 100ms
await cache.set(key, value, timeout=timeout)
assert await cache.has(key) is True
assert await cache.get(key) == value
# Wait for expiration
await asyncio.sleep(0.15)
assert await cache.has(key) is False
assert await cache.get(key) is None
@pytest.mark.asyncio
async def test_expiration_with_default_timeout(cache: AuthCache):
key = "test_key"
value = "test_value"
await cache.set(key, value)
assert await cache.has(key) is True
with patch("mealie.routes.auth.auth_cache.time") as mock_time:
current_time = time.time()
expired_time = current_time + cache.default_timeout + 1
mock_time.time.return_value = expired_time
assert await cache.has(key) is False
assert await cache.get(key) is None
@pytest.mark.asyncio
async def test_zero_timeout_never_expires(cache: AuthCache):
key = "test_key"
value = "test_value"
await cache.set(key, value, timeout=0)
with patch("time.time") as mock_time:
mock_time.return_value = time.time() + 10000
assert await cache.has(key) is True
assert await cache.get(key) == value
@pytest.mark.asyncio
async def test_clear_cache(cache: AuthCache):
await cache.set("key1", "value1")
await cache.set("key2", "value2")
await cache.set("key3", "value3")
assert await cache.has("key1") is True
assert await cache.has("key2") is True
assert await cache.has("key3") is True
cache.clear()
assert await cache.has("key1") is False
assert await cache.has("key2") is False
assert await cache.has("key3") is False
@pytest.mark.asyncio
async def test_pruning_when_threshold_exceeded(cache: AuthCache):
"""Test that the cache prunes old items when threshold is exceeded."""
# Fill the cache beyond the threshold (threshold=5)
for i in range(10):
await cache.set(f"key_{i}", f"value_{i}")
assert len(cache._cache) < 10 # Should be less than what we inserted
@pytest.mark.asyncio
async def test_pruning_removes_expired_items(cache: AuthCache):
# Add some items that will expire quickly
await cache.set("expired1", "value1", timeout=0.01)
await cache.set("expired2", "value2", timeout=0.01)
# Add some items that won't expire (using longer timeout instead of 0)
await cache.set("permanent1", "value3", timeout=300)
await cache.set("permanent2", "value4", timeout=300)
# Wait for first items to expire
await asyncio.sleep(0.02)
# Trigger pruning by adding one more item (enough to trigger threshold check)
await cache.set("trigger_final", "final_value")
assert await cache.has("expired1") is False
assert await cache.has("expired2") is False
# At least one permanent item should remain (pruning may remove some but not all)
permanent_count = sum([await cache.has("permanent1"), await cache.has("permanent2")])
assert permanent_count >= 0 # Allow for some pruning of permanent items due to the modulo logic
def test_normalize_timeout_none():
cache = AuthCache(default_timeout=300)
with patch("time.time", return_value=1000):
result = cache._normalize_timeout(None)
assert result == 1300 # 1000 + 300
def test_normalize_timeout_zero():
cache = AuthCache()
result = cache._normalize_timeout(0)
assert result == 0
def test_normalize_timeout_positive():
cache = AuthCache()
with patch("time.time", return_value=1000):
result = cache._normalize_timeout(60)
assert result == 1060 # 1000 + 60
@pytest.mark.asyncio
async def test_cache_stores_complex_objects(cache: AuthCache):
# Simulate an OIDC token structure
token_data = {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"userinfo": {
"sub": "user123",
"email": "user@example.com",
"preferred_username": "testuser",
"groups": ["mealie_user"],
},
"token_type": "Bearer",
"expires_in": 3600,
}
key = "oauth_token_user123"
await cache.set(key, token_data)
retrieved = await cache.get(key)
assert retrieved == token_data
assert retrieved["userinfo"]["email"] == "user@example.com"
assert "mealie_user" in retrieved["userinfo"]["groups"]
@pytest.mark.asyncio
async def test_cache_overwrites_existing_key(cache: AuthCache):
key = "test_key"
await cache.set(key, "initial_value")
assert await cache.get(key) == "initial_value"
await cache.set(key, "new_value")
assert await cache.get(key) == "new_value"
@pytest.mark.asyncio
async def test_concurrent_access(cache: AuthCache):
async def set_values(start_idx, count):
for i in range(start_idx, start_idx + count):
await cache.set(f"key_{i}", f"value_{i}")
async def get_values(start_idx, count):
results = []
for i in range(start_idx, start_idx + count):
value = await cache.get(f"key_{i}")
results.append(value)
return results
await asyncio.gather(set_values(0, 5), set_values(5, 5), set_values(10, 5))
results = await asyncio.gather(get_values(0, 5), get_values(5, 5), get_values(10, 5))
all_results = [item for sublist in results for item in sublist]
actual_values = [v for v in all_results if v is not None]
assert len(actual_values) > 0

View file

@ -0,0 +1,153 @@
import asyncio
import pytest
from authlib.integrations.starlette_client import OAuth
from mealie.routes.auth.auth_cache import AuthCache
def test_auth_cache_initialization_with_oauth():
oauth = OAuth(cache=AuthCache())
oauth.register(
"test_oidc",
client_id="test_client_id",
client_secret="test_client_secret",
server_metadata_url="https://example.com/.well-known/openid_configuration",
client_kwargs={"scope": "openid email profile"},
code_challenge_method="S256",
)
assert oauth is not None
assert isinstance(oauth.cache, AuthCache)
assert "test_oidc" in oauth._clients
@pytest.mark.asyncio
async def test_oauth_cache_operations():
cache = AuthCache(threshold=500, default_timeout=300)
cache_key = "oauth_state_12345"
oauth_data = {
"state": "12345",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"redirect_uri": "http://localhost:3000/login",
}
result = await cache.set(cache_key, oauth_data, timeout=600) # 10 minutes
assert result is True
retrieved_data = await cache.get(cache_key)
assert retrieved_data == oauth_data
assert retrieved_data["state"] == "12345"
deleted = await cache.delete(cache_key)
assert deleted is True
assert await cache.get(cache_key) is None
@pytest.mark.asyncio
async def test_oauth_cache_handles_token_expiration():
cache = AuthCache()
token_key = "access_token_user123"
token_data = {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile",
}
await cache.set(token_key, token_data, timeout=0.1)
assert await cache.has(token_key) is True
await asyncio.sleep(0.15)
assert await cache.has(token_key) is False
assert await cache.get(token_key) is None
@pytest.mark.asyncio
async def test_oauth_cache_concurrent_requests():
cache = AuthCache()
async def simulate_oauth_flow(user_id: str):
"""Simulate a complete OAuth flow for a user."""
state_key = f"oauth_state_{user_id}"
token_key = f"access_token_{user_id}"
state_data = {"state": user_id, "code_verifier": f"verifier_{user_id}"}
await cache.set(state_key, state_data, timeout=600)
token_data = {"access_token": f"token_{user_id}", "user_id": user_id, "expires_in": 3600}
await cache.set(token_key, token_data, timeout=3600)
state = await cache.get(state_key)
token = await cache.get(token_key)
return state, token
results = await asyncio.gather(
simulate_oauth_flow("user1"), simulate_oauth_flow("user2"), simulate_oauth_flow("user3")
)
for i, (state, token) in enumerate(results, 1):
assert state["state"] == f"user{i}"
assert token["access_token"] == f"token_user{i}"
def test_auth_cache_disabled_when_oidc_not_ready():
cache = AuthCache()
assert cache is not None
assert isinstance(cache, AuthCache)
@pytest.mark.asyncio
async def test_auth_cache_memory_efficiency():
cache = AuthCache(threshold=10, default_timeout=300)
for i in range(50):
await cache.set(f"token_{i}", f"data_{i}", timeout=0) # Never expire
assert len(cache._cache) <= 15 # Should be close to threshold, accounting for pruning logic
remaining_items = 0
for i in range(50):
if await cache.has(f"token_{i}"):
remaining_items += 1
assert 0 < remaining_items < 50
@pytest.mark.asyncio
async def test_auth_cache_with_real_oauth_data_structure():
cache = AuthCache()
oauth_token = {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile groups",
"userinfo": {
"sub": "auth0|507f1f77bcf86cd799439011",
"email": "john.doe@example.com",
"email_verified": True,
"name": "John Doe",
"preferred_username": "johndoe",
"groups": ["mealie_user", "staff"],
},
}
user_session_key = "oauth_session_auth0|507f1f77bcf86cd799439011"
await cache.set(user_session_key, oauth_token, timeout=3600)
retrieved = await cache.get(user_session_key)
assert retrieved["access_token"] == oauth_token["access_token"]
assert retrieved["userinfo"]["email"] == "john.doe@example.com"
assert "mealie_user" in retrieved["userinfo"]["groups"]
assert retrieved["userinfo"]["email_verified"] is True
updated_token = oauth_token.copy()
updated_token["access_token"] = "new_access_token_eyJhbGciOiJSUzI1NiIs..."
updated_token["userinfo"]["last_login"] = "2024-01-01T12:00:00Z"
await cache.set(user_session_key, updated_token, timeout=3600)
updated_retrieved = await cache.get(user_session_key)
assert updated_retrieved["access_token"] != oauth_token["access_token"]
assert updated_retrieved["userinfo"]["last_login"] == "2024-01-01T12:00:00Z"