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 desc: run code generators
cmds: cmds:
- poetry run python dev/code-generation/main.py {{ .CLI_ARGS }} - poetry run python dev/code-generation/main.py {{ .CLI_ARGS }}
- task: docs:gen
- task: py:format - task: py:format
dev:services: dev:services:

View file

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

View file

@ -1,7 +1,7 @@
############################################### ###############################################
# Frontend Build # Frontend Build
############################################### ###############################################
FROM node:20@sha256:452293f0e5c9b7075829f2cd0dbd5fcae44d89cf5508b84a17bbe0857dc1a654 \ FROM node:20@sha256:572a90df10a58ebb7d3f223d661d964a6c2383a9c2b5763162b4f631c53dc56a \
AS frontend-builder AS frontend-builder
WORKDIR /frontend 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 { useCookbookStore } from "~/composables/store/use-cookbook-store";
import { useCookbook } from "~/composables/use-group-cookbooks"; import { useCookbook } from "~/composables/use-group-cookbooks";
import { useLoggedInState } from "~/composables/use-logged-in-state"; 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"; import CookbookEditor from "~/components/Domain/Cookbook/CookbookEditor.vue";
const $auth = useMealieAuth(); const $auth = useMealieAuth();
@ -100,7 +100,7 @@ const dialogStates = reactive({
edit: false, edit: false,
}); });
const editTarget = ref<RecipeCookBook | null>(null); const editTarget = ref<ReadCookBook | null>(null);
function handleEditCookbook() { function handleEditCookbook() {
dialogStates.edit = true; dialogStates.edit = true;
editTarget.value = book.value; editTarget.value = book.value;

View file

@ -1,18 +1,18 @@
import type { Composer } from "vue-i18n"; import type { Composer } from "vue-i18n";
import { useReadOnlyStore, useStore } from "../partials/use-store-factory"; 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"; import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref<RecipeCookBook[]> = ref([]); const store: Ref<ReadCookBook[]> = ref([]);
const loading = ref(false); const loading = ref(false);
const publicLoading = ref(false); const publicLoading = ref(false);
export const useCookbookStore = function (i18n?: Composer) { export const useCookbookStore = function (i18n?: Composer) {
const api = useUserApi(i18n); 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) { export const usePublicCookbookStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore; 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", "new-notification": "Nuwe kennisgewing",
"event-notifiers": "Gebeurteniskennisgewers", "event-notifiers": "Gebeurteniskennisgewers",
"apprise-url-skipped-if-blank": "Apprise URL (oorgeslaan indien leeg)", "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", "enable-notifier": "Aktiveer kennisgewer",
"what-events": "Op watter gebeurtenisse moet hierdie kennisgewing inteken?", "what-events": "Op watter gebeurtenisse moet hierdie kennisgewing inteken?",
"user-events": "Gebruikersgebeurtenisse", "user-events": "Gebruikersgebeurtenisse",

View file

@ -69,6 +69,7 @@
"new-notification": "إشعار جديد", "new-notification": "إشعار جديد",
"event-notifiers": "إشعار الحدث", "event-notifiers": "إشعار الحدث",
"apprise-url-skipped-if-blank": "الرابط Apprise (يتم تجاهله إذا ما كان فارغً)", "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": "تفعيل الإشعارات", "enable-notifier": "تفعيل الإشعارات",
"what-events": "ما هي الأحداث التي يجب على هذا المخدم أن يستجيب لها؟", "what-events": "ما هي الأحداث التي يجب على هذا المخدم أن يستجيب لها؟",
"user-events": "أحداث المستخدمين", "user-events": "أحداث المستخدمين",

View file

@ -69,6 +69,7 @@
"new-notification": "Ново известие", "new-notification": "Ново известие",
"event-notifiers": "Известия за събитие", "event-notifiers": "Известия за събитие",
"apprise-url-skipped-if-blank": "URL за известяване (пропуска се ако е празно)", "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": "Включи известията", "enable-notifier": "Включи известията",
"what-events": "За кои събития трябва да се получават известия?", "what-events": "За кои събития трябва да се получават известия?",
"user-events": "Потребителски събития", "user-events": "Потребителски събития",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova notificació", "new-notification": "Nova notificació",
"event-notifiers": "Notificacions d'esdeveniments", "event-notifiers": "Notificacions d'esdeveniments",
"apprise-url-skipped-if-blank": "Apprise URL (si es deixa buit, s'ignorarà)", "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ó", "enable-notifier": "Habilita la notificació",
"what-events": "Què esdeveniments vols que utilitzen aquest notificador?", "what-events": "Què esdeveniments vols que utilitzen aquest notificador?",
"user-events": "Esdeveniments d'usuari", "user-events": "Esdeveniments d'usuari",

View file

@ -69,6 +69,7 @@
"new-notification": "Nové oznámení", "new-notification": "Nové oznámení",
"event-notifiers": "Notifikace událostí", "event-notifiers": "Notifikace událostí",
"apprise-url-skipped-if-blank": "Apprise URL (přeskočeno pokud je prázdné)", "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", "enable-notifier": "Povolit notifikaci",
"what-events": "K jakým událostem by se měl tento oznamovatel přihlásit?", "what-events": "K jakým událostem by se měl tento oznamovatel přihlásit?",
"user-events": "Uživatelské události", "user-events": "Uživatelské události",

View file

@ -69,6 +69,7 @@
"new-notification": "Ny notifikation", "new-notification": "Ny notifikation",
"event-notifiers": "Notifikation om begivenheder", "event-notifiers": "Notifikation om begivenheder",
"apprise-url-skipped-if-blank": "Informations link (sprunget over hvis ladet være tomt)", "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", "enable-notifier": "Aktiver Notifikationer",
"what-events": "Hvilke begivenheder skal denne anmelder abonnere på?", "what-events": "Hvilke begivenheder skal denne anmelder abonnere på?",
"user-events": "Brugerhændelser", "user-events": "Brugerhændelser",

View file

@ -69,6 +69,7 @@
"new-notification": "Neue Benachrichtigung", "new-notification": "Neue Benachrichtigung",
"event-notifiers": "Ereignis-Benachrichtigungen", "event-notifiers": "Ereignis-Benachrichtigungen",
"apprise-url-skipped-if-blank": "Apprise-URL (wird übersprungen, wenn leer)", "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", "enable-notifier": "Benachrichtigen aktivieren",
"what-events": "Welche Ereignisse soll diese Benachrichtigung abonnieren?", "what-events": "Welche Ereignisse soll diese Benachrichtigung abonnieren?",
"user-events": "Benutzer-Ereignisse", "user-events": "Benutzer-Ereignisse",

View file

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

View file

@ -69,6 +69,7 @@
"new-notification": "New Notification", "new-notification": "New Notification",
"event-notifiers": "Event Notifiers", "event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)", "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", "enable-notifier": "Enable Notifier",
"what-events": "What events should this notifier subscribe to?", "what-events": "What events should this notifier subscribe to?",
"user-events": "User Events", "user-events": "User Events",

View file

@ -69,6 +69,7 @@
"new-notification": "Nueva notificación", "new-notification": "Nueva notificación",
"event-notifiers": "Notificaciones de eventos", "event-notifiers": "Notificaciones de eventos",
"apprise-url-skipped-if-blank": "URL de Apprise (omitida si está en blanco)", "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", "enable-notifier": "Habilitar notificador",
"what-events": "¿A qué eventos debe suscribirse este notificador?", "what-events": "¿A qué eventos debe suscribirse este notificador?",
"user-events": "Eventos de los usuarios", "user-events": "Eventos de los usuarios",

View file

@ -69,6 +69,7 @@
"new-notification": "Uus teade", "new-notification": "Uus teade",
"event-notifiers": "Sündmuste märguanded", "event-notifiers": "Sündmuste märguanded",
"apprise-url-skipped-if-blank": "Apprise URL (kui on tühi, jäetakse vahele)", "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", "enable-notifier": "Luba teavitaja",
"what-events": "Millised sündmused peaks see teavitaja tellimaa?", "what-events": "Millised sündmused peaks see teavitaja tellimaa?",
"user-events": "Kasutaja sündmused", "user-events": "Kasutaja sündmused",
@ -80,7 +81,7 @@
"category-events": "Kategooria sündmused", "category-events": "Kategooria sündmused",
"when-a-new-user-joins-your-group": "Kui uus kasutaja liitub sinu grupiga", "when-a-new-user-joins-your-group": "Kui uus kasutaja liitub sinu grupiga",
"recipe-events": "Retsepti sündmused", "recipe-events": "Retsepti sündmused",
"label-events": "Label Events" "label-events": "Sildista sündmused"
}, },
"general": { "general": {
"add": "Lisa", "add": "Lisa",

View file

@ -69,6 +69,7 @@
"new-notification": "Uusi ilmoitus", "new-notification": "Uusi ilmoitus",
"event-notifiers": "Tapahtumien ilmoitukset", "event-notifiers": "Tapahtumien ilmoitukset",
"apprise-url-skipped-if-blank": "Ilmoitusverkko-osoite (voi jättää tyhjäksi)", "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", "enable-notifier": "Ota ilmoittaja käyttöön",
"what-events": "Mistä tapahtumista tulisi ilmoittaa?", "what-events": "Mistä tapahtumista tulisi ilmoittaa?",
"user-events": "Käyttäjän tapahtumat", "user-events": "Käyttäjän tapahtumat",

View file

@ -69,6 +69,7 @@
"new-notification": "Nouvelle notification", "new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements", "event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)", "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", "enable-notifier": "Activer la notification",
"what-events": "À quels événements cette notification doit-elle s'abonner ?", "what-events": "À quels événements cette notification doit-elle s'abonner ?",
"user-events": "Evénements utilisateur", "user-events": "Evénements utilisateur",

View file

@ -69,6 +69,7 @@
"new-notification": "Nouvelle notification", "new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements", "event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)", "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", "enable-notifier": "Activer la notification",
"what-events": "À quels événements cette notification doit-elle s'abonner ?", "what-events": "À quels événements cette notification doit-elle s'abonner ?",
"user-events": "Événements de l'utilisateur", "user-events": "Événements de l'utilisateur",

View file

@ -69,6 +69,7 @@
"new-notification": "Nouvelle notification", "new-notification": "Nouvelle notification",
"event-notifiers": "Notifications d'événements", "event-notifiers": "Notifications d'événements",
"apprise-url-skipped-if-blank": "URL Apprise (ignoré si vide)", "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", "enable-notifier": "Activer la notification",
"what-events": "À quels événements cette notification doit-elle s'abonner ?", "what-events": "À quels événements cette notification doit-elle s'abonner ?",
"user-events": "Événements utilisateur", "user-events": "Événements utilisateur",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Notificación", "new-notification": "Nova Notificación",
"event-notifiers": "Notificadores de Eventos", "event-notifiers": "Notificadores de Eventos",
"apprise-url-skipped-if-blank": "URL de Apprise (omitido se está en branco)", "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", "enable-notifier": "Activar o Notificador",
"what-events": "A que eventos debería subscribirse este notificador?", "what-events": "A que eventos debería subscribirse este notificador?",
"user-events": "Eventos de Usuario", "user-events": "Eventos de Usuario",

View file

@ -69,6 +69,7 @@
"new-notification": "התראה חדשה", "new-notification": "התראה חדשה",
"event-notifiers": "מנגנוני התרעה על אירועים", "event-notifiers": "מנגנוני התרעה על אירועים",
"apprise-url-skipped-if-blank": "כתובת Apprise (דלג אם ריק)", "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": "הפעלת מתריע", "enable-notifier": "הפעלת מתריע",
"what-events": "לאילו אירועים לרשום את מתריע זה?", "what-events": "לאילו אירועים לרשום את מתריע זה?",
"user-events": "אירועי משתמש", "user-events": "אירועי משתמש",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Obavijest", "new-notification": "Nova Obavijest",
"event-notifiers": "Obavještavatelji Događaja", "event-notifiers": "Obavještavatelji Događaja",
"apprise-url-skipped-if-blank": "Apprise URL (preskočeno ako je prazno)", "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", "enable-notifier": "Omogući obavještavanje",
"what-events": "Na koje događaje bi ovaj obavještavatelj trebao biti pretplaćen?", "what-events": "Na koje događaje bi ovaj obavještavatelj trebao biti pretplaćen?",
"user-events": "Događaji Korisnika", "user-events": "Događaji Korisnika",

View file

@ -69,6 +69,7 @@
"new-notification": "Új értesítés", "new-notification": "Új értesítés",
"event-notifiers": "Esemény értesítők", "event-notifiers": "Esemény értesítők",
"apprise-url-skipped-if-blank": "Értesítendő URL (kihagy, ha üres)", "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", "enable-notifier": "Értesítés engedélyezése",
"what-events": "Milyen eseményekre figyeljen ez az értesítés?", "what-events": "Milyen eseményekre figyeljen ez az értesítés?",
"user-events": "Felhasználói Események", "user-events": "Felhasználói Események",

View file

@ -69,6 +69,7 @@
"new-notification": "Ný tilkynning", "new-notification": "Ný tilkynning",
"event-notifiers": "Viðburðar tilkynningar", "event-notifiers": "Viðburðar tilkynningar",
"apprise-url-skipped-if-blank": "Apprise URL (sleppt ef tómt)", "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", "enable-notifier": "Virkja tilkynningar",
"what-events": "Hvaða viðburði ætti þessi tilkynnir að vera áskrifandi að?", "what-events": "Hvaða viðburði ætti þessi tilkynnir að vera áskrifandi að?",
"user-events": "Notenda viðburðir", "user-events": "Notenda viðburðir",

View file

@ -69,6 +69,7 @@
"new-notification": "Nuova Notifica", "new-notification": "Nuova Notifica",
"event-notifiers": "Notifiche Evento", "event-notifiers": "Notifiche Evento",
"apprise-url-skipped-if-blank": "Url di Apprise (ignorato se vuoto)", "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", "enable-notifier": "Abilita Notificatore",
"what-events": "Quali eventi dovrebbe sottoscrivere questo notificatore?", "what-events": "Quali eventi dovrebbe sottoscrivere questo notificatore?",
"user-events": "Eventi Utente", "user-events": "Eventi Utente",
@ -80,7 +81,7 @@
"category-events": "Categoria Eventi", "category-events": "Categoria Eventi",
"when-a-new-user-joins-your-group": "Quando un nuovo utente entra nel tuo gruppo", "when-a-new-user-joins-your-group": "Quando un nuovo utente entra nel tuo gruppo",
"recipe-events": "Eventi di ricette", "recipe-events": "Eventi di ricette",
"label-events": "Label Events" "label-events": "Eventi Etichetta"
}, },
"general": { "general": {
"add": "Aggiungi", "add": "Aggiungi",
@ -473,7 +474,7 @@
"comment": "Commento", "comment": "Commento",
"comments": "Commenti", "comments": "Commenti",
"delete-confirmation": "Sei sicuro di voler eliminare questa ricetta?", "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", "delete-recipe": "Elimina Ricetta",
"description": "Descrizione", "description": "Descrizione",
"disable-amount": "Disabilita Quantità Ingredienti", "disable-amount": "Disabilita Quantità Ingredienti",
@ -581,10 +582,10 @@
"made-this": "L'Ho Preparato", "made-this": "L'Ho Preparato",
"how-did-it-turn-out": "Come è venuto?", "how-did-it-turn-out": "Come è venuto?",
"user-made-this": "{user} l'ha preparato", "user-made-this": "{user} l'ha preparato",
"added-to-timeline": "Added to timeline", "added-to-timeline": "Aggiunto alla cronologia",
"failed-to-add-to-timeline": "Failed to add to timeline", "failed-to-add-to-timeline": "Impossibile aggiungere alla cronologia",
"failed-to-update-recipe": "Failed to update recipe", "failed-to-update-recipe": "Impossibile aggiornare la ricetta",
"added-to-timeline-but-failed-to-add-image": "Added to timeline, but failed to add image", "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.", "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", "message-key": "Chiave Messaggio",
"parse": "Analizza", "parse": "Analizza",
@ -606,10 +607,10 @@
"create-recipe-from-an-image": "Crea ricetta da un'immagine", "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.", "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.", "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", "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-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", "bulk-url-import": "Importazione multipla URL",
"debug-scraper": "Debug Scraper", "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.", "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à", "no-unit": "Nessuna unità",
"missing-unit": "Crea unità mancante: {unit}", "missing-unit": "Crea unità mancante: {unit}",
"missing-food": "Crea cibo mancante: {food}", "missing-food": "Crea cibo mancante: {food}",
"this-unit-could-not-be-parsed-automatically": "This unit 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": "This food could not be parsed automatically", "this-food-could-not-be-parsed-automatically": "Questo alimento non può essere analizzato automaticamente",
"no-food": "Nessun Alimento" "no-food": "Nessun Alimento"
}, },
"reset-servings-count": "Reimposta conteggio porzioni", "reset-servings-count": "Reimposta conteggio porzioni",
"not-linked-ingredients": "Ingredienti Aggiuntivi", "not-linked-ingredients": "Ingredienti Aggiuntivi",
"upload-another-image": "Upload another image", "upload-another-image": "Carica un'altra immagine",
"upload-images": "Upload images", "upload-images": "Carica immagini",
"upload-more-images": "Upload more images", "upload-more-images": "Carica altre immagini",
"set-as-cover-image": "Set as recipe cover image", "set-as-cover-image": "Imposta come immagine di copertina della ricetta",
"cover-image": "Cover image" "cover-image": "Immagine di copertina"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Trova ricette", "recipe-finder": "Trova ricette",
@ -1168,7 +1169,7 @@
"group-details": "Dettagli Gruppo", "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!", "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": "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" "account-details": "Dettagli dell'Account"
}, },
"validation": { "validation": {

View file

@ -69,6 +69,7 @@
"new-notification": "新着通知", "new-notification": "新着通知",
"event-notifiers": "イベント通知", "event-notifiers": "イベント通知",
"apprise-url-skipped-if-blank": "通知用URL (空欄の場合はスキップ)", "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": "通知を有効にする", "enable-notifier": "通知を有効にする",
"what-events": "この通知はどのイベントを購読すべきですか?", "what-events": "この通知はどのイベントを購読すべきですか?",
"user-events": "ユーザーイベント", "user-events": "ユーザーイベント",

View file

@ -69,6 +69,7 @@
"new-notification": "새 알림", "new-notification": "새 알림",
"event-notifiers": "이벤트 알림이", "event-notifiers": "이벤트 알림이",
"apprise-url-skipped-if-blank": "Apprise URL (비워두면 생략합니다)", "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": "알림 활성화", "enable-notifier": "알림 활성화",
"what-events": "이 알리미는 어떤 이벤트를 구독해야 합니까?", "what-events": "이 알리미는 어떤 이벤트를 구독해야 합니까?",
"user-events": "사용자 이벤트", "user-events": "사용자 이벤트",

View file

@ -69,6 +69,7 @@
"new-notification": "Naujas pranešimas", "new-notification": "Naujas pranešimas",
"event-notifiers": "Įvykių pranešimai", "event-notifiers": "Įvykių pranešimai",
"apprise-url-skipped-if-blank": "Apprise URL (praleidžiama, jei tuščia)", "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į", "enable-notifier": "Įjungti pranešiklį",
"what-events": "Kokie įvykiai turėtų būti sekami?", "what-events": "Kokie įvykiai turėtų būti sekami?",
"user-events": "Naudotojų įvykiai", "user-events": "Naudotojų įvykiai",

View file

@ -69,6 +69,7 @@
"new-notification": "Jauns paziņojums", "new-notification": "Jauns paziņojums",
"event-notifiers": "Notikumu paziņotāji", "event-notifiers": "Notikumu paziņotāji",
"apprise-url-skipped-if-blank": "Apprise URL (izlaists, ja tukšs)", "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", "enable-notifier": "Iespējot paziņotāju",
"what-events": "Kādus notikumus šim paziņotājam vajadzētu abonēt?", "what-events": "Kādus notikumus šim paziņotājam vajadzētu abonēt?",
"user-events": "Lietotāju notikumi", "user-events": "Lietotāju notikumi",

View file

@ -69,6 +69,7 @@
"new-notification": "Nieuwe melding", "new-notification": "Nieuwe melding",
"event-notifiers": "Meldingen van gebeurtenissen", "event-notifiers": "Meldingen van gebeurtenissen",
"apprise-url-skipped-if-blank": "URL van Apprise (overgeslagen als veld leeg is)", "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", "enable-notifier": "Activeer melding",
"what-events": "Op welke gebeurtenissen moet deze melding zich abonneren?", "what-events": "Op welke gebeurtenissen moet deze melding zich abonneren?",
"user-events": "Gebeurtenissen van gebruiker", "user-events": "Gebeurtenissen van gebruiker",

View file

@ -69,6 +69,7 @@
"new-notification": "Nytt varsel", "new-notification": "Nytt varsel",
"event-notifiers": "Hendelsesvarsler", "event-notifiers": "Hendelsesvarsler",
"apprise-url-skipped-if-blank": "Apprise-URL (hoppes over hvis tom)", "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", "enable-notifier": "Aktiver varslingsagenten",
"what-events": "Hvilke hendelser skal denne varslingsagenten abonnere på?", "what-events": "Hvilke hendelser skal denne varslingsagenten abonnere på?",
"user-events": "Brukerhendelser", "user-events": "Brukerhendelser",

View file

@ -69,6 +69,7 @@
"new-notification": "Nowe powiadomienie", "new-notification": "Nowe powiadomienie",
"event-notifiers": "Powiadomienia o zdarzeniach", "event-notifiers": "Powiadomienia o zdarzeniach",
"apprise-url-skipped-if-blank": "URL Apprise (pominięty, jeśli puste)", "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", "enable-notifier": "Włącz Powiadomienie",
"what-events": "Jakie zdarzenia powinien subskrybować ten powiadamiający?", "what-events": "Jakie zdarzenia powinien subskrybować ten powiadamiający?",
"user-events": "Zdarzenia użytkownika", "user-events": "Zdarzenia użytkownika",
@ -80,7 +81,7 @@
"category-events": "Wydarzenia kategorii", "category-events": "Wydarzenia kategorii",
"when-a-new-user-joins-your-group": "Kiedy nowy użytkownik dołączy do Twojej grupy", "when-a-new-user-joins-your-group": "Kiedy nowy użytkownik dołączy do Twojej grupy",
"recipe-events": "Zdarzenia Przepisów", "recipe-events": "Zdarzenia Przepisów",
"label-events": "Label Events" "label-events": "Etykieta wydarzeń"
}, },
"general": { "general": {
"add": "Dodaj", "add": "Dodaj",
@ -674,8 +675,8 @@
"upload-another-image": "Prześlij kolejny obraz", "upload-another-image": "Prześlij kolejny obraz",
"upload-images": "Prześlij obraz", "upload-images": "Prześlij obraz",
"upload-more-images": "Prześlij więcej obrazów", "upload-more-images": "Prześlij więcej obrazów",
"set-as-cover-image": "Set as recipe cover image", "set-as-cover-image": "Ustaw jako okładkę przepisu",
"cover-image": "Cover image" "cover-image": "Okładka"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Wyszukiwarka przepisów", "recipe-finder": "Wyszukiwarka przepisów",
@ -1168,7 +1169,7 @@
"group-details": "Szczegóły grupy", "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!", "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": "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" "account-details": "Szczegóły konta"
}, },
"validation": { "validation": {

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Notificação", "new-notification": "Nova Notificação",
"event-notifiers": "Notificações de Eventos", "event-notifiers": "Notificações de Eventos",
"apprise-url-skipped-if-blank": "URL Apprise (ignorado se estiver em branco)", "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", "enable-notifier": "Habilitar Notificador",
"what-events": "A quais eventos este notificador deve subscrever?", "what-events": "A quais eventos este notificador deve subscrever?",
"user-events": "Eventos do usuário", "user-events": "Eventos do usuário",

View file

@ -69,6 +69,7 @@
"new-notification": "Nova Notificação", "new-notification": "Nova Notificação",
"event-notifiers": "Notificadores de eventos", "event-notifiers": "Notificadores de eventos",
"apprise-url-skipped-if-blank": "URL da Apprise (ignorado se vazio)", "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", "enable-notifier": "Ativar Notificador",
"what-events": "Que eventos este notificador deve subscrever?", "what-events": "Que eventos este notificador deve subscrever?",
"user-events": "Eventos do utilizador", "user-events": "Eventos do utilizador",

View file

@ -69,6 +69,7 @@
"new-notification": "Notificare nouă", "new-notification": "Notificare nouă",
"event-notifiers": "Notificatori de evenimente", "event-notifiers": "Notificatori de evenimente",
"apprise-url-skipped-if-blank": "URL Apprise (ignorat daca e gol)", "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", "enable-notifier": "Activare notificator",
"what-events": "La ce evenimente ar trebui să se înscrie acest notificator?", "what-events": "La ce evenimente ar trebui să se înscrie acest notificator?",
"user-events": "Evenimente Utilizator", "user-events": "Evenimente Utilizator",

View file

@ -69,6 +69,7 @@
"new-notification": "Новое уведомление", "new-notification": "Новое уведомление",
"event-notifiers": "Уведомления о событии", "event-notifiers": "Уведомления о событии",
"apprise-url-skipped-if-blank": "URL-адрес (пропущен, если пусто)", "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": "Включить уведомления", "enable-notifier": "Включить уведомления",
"what-events": "На какие события следует настроить уведомления?", "what-events": "На какие события следует настроить уведомления?",
"user-events": "События пользователя", "user-events": "События пользователя",

View file

@ -69,6 +69,7 @@
"new-notification": "Nové upozornenie", "new-notification": "Nové upozornenie",
"event-notifiers": "Upozornenia udalostí", "event-notifiers": "Upozornenia udalostí",
"apprise-url-skipped-if-blank": "Informačná URL (preskočená, ak je prázdna)", "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", "enable-notifier": "Zapnúť notifikátor",
"what-events": "Pre ktoré udalosti si želáte zapnúť notifikátor?", "what-events": "Pre ktoré udalosti si želáte zapnúť notifikátor?",
"user-events": "Udalosti používateľa", "user-events": "Udalosti používateľa",

View file

@ -69,6 +69,7 @@
"new-notification": "Novo obvestilo", "new-notification": "Novo obvestilo",
"event-notifiers": "Obvestila o dogodkih", "event-notifiers": "Obvestila o dogodkih",
"apprise-url-skipped-if-blank": "Apprise URL (preskočeno, če je prazno)", "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", "enable-notifier": "Vključi obvestila",
"what-events": "Katere dogodke naj spremlja obveščevalni sistem?", "what-events": "Katere dogodke naj spremlja obveščevalni sistem?",
"user-events": "Dogodki uporabnika", "user-events": "Dogodki uporabnika",

View file

@ -69,6 +69,7 @@
"new-notification": "Ново обавештење", "new-notification": "Ново обавештење",
"event-notifiers": "Обавештавач о догађају", "event-notifiers": "Обавештавач о догађају",
"apprise-url-skipped-if-blank": "Apprise URL (прескочено ако је празно)", "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": "Омогући обавештење", "enable-notifier": "Омогући обавештење",
"what-events": "На које догађаје би требао да се претплати овај обавештавач?", "what-events": "На које догађаје би требао да се претплати овај обавештавач?",
"user-events": "Догађаји корисника", "user-events": "Догађаји корисника",

View file

@ -69,6 +69,7 @@
"new-notification": "Ny avisering", "new-notification": "Ny avisering",
"event-notifiers": "Händelseavisering", "event-notifiers": "Händelseavisering",
"apprise-url-skipped-if-blank": "Apprise-URL (hoppa över om tom)", "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", "enable-notifier": "Aktivera avisering",
"what-events": "Vilka händelser ska denna avisering prenumerera på?", "what-events": "Vilka händelser ska denna avisering prenumerera på?",
"user-events": "Användarhändelser", "user-events": "Användarhändelser",

View file

@ -69,6 +69,7 @@
"new-notification": "Yeni bildirim", "new-notification": "Yeni bildirim",
"event-notifiers": "Etkinlik Bildirimleri", "event-notifiers": "Etkinlik Bildirimleri",
"apprise-url-skipped-if-blank": "Apprise URL'si (boşsa geçilir)", "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", "enable-notifier": "Bildiriciyi Etkinleştir",
"what-events": "Bu bildirimci hangi olaylara abone olmalıdır?", "what-events": "Bu bildirimci hangi olaylara abone olmalıdır?",
"user-events": "Kullanıcı Etkinlikleri", "user-events": "Kullanıcı Etkinlikleri",

View file

@ -69,6 +69,7 @@
"new-notification": "Нове сповіщення", "new-notification": "Нове сповіщення",
"event-notifiers": "Сповіщувачі", "event-notifiers": "Сповіщувачі",
"apprise-url-skipped-if-blank": "Apprise URL (пропущено якщо порожній)", "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": "Увімкнути сповіщувач", "enable-notifier": "Увімкнути сповіщувач",
"what-events": "На які події цей сповіщувач має бути підписаний?", "what-events": "На які події цей сповіщувач має бути підписаний?",
"user-events": "Події користувача", "user-events": "Події користувача",

View file

@ -69,6 +69,7 @@
"new-notification": "Thông báo mới", "new-notification": "Thông báo mới",
"event-notifiers": "Event Notifiers", "event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)", "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", "enable-notifier": "Enable Notifier",
"what-events": "What events should this notifier subscribe to?", "what-events": "What events should this notifier subscribe to?",
"user-events": "User Events", "user-events": "User Events",

View file

@ -69,6 +69,7 @@
"new-notification": "新通知", "new-notification": "新通知",
"event-notifiers": "事件通知器", "event-notifiers": "事件通知器",
"apprise-url-skipped-if-blank": "Apprise URL (如果为空则跳过)", "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": "打开消息通知", "enable-notifier": "打开消息通知",
"what-events": "该通知器需要订阅哪些事件?", "what-events": "该通知器需要订阅哪些事件?",
"user-events": "用户事件", "user-events": "用户事件",

View file

@ -69,6 +69,7 @@
"new-notification": "新通知", "new-notification": "新通知",
"event-notifiers": "事件通知", "event-notifiers": "事件通知",
"apprise-url-skipped-if-blank": "Apprise 網址(空白則略過)", "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": "啟用通知功能", "enable-notifier": "啟用通知功能",
"what-events": "要訂閱哪些事件通知?", "what-events": "要訂閱哪些事件通知?",
"user-events": "用戶相關事件", "user-events": "用戶相關事件",

View file

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

View file

@ -1,4 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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; attributeName?: string | null;
relationalOperator?: RelationalKeyword | RelationalOperator | null; relationalOperator?: RelationalKeyword | RelationalOperator | null;
value?: string | string[] | 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; [k: string]: unknown;
} }
export interface SaveCookBook { export interface SaveCookBook {

View file

@ -1,4 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 */ /* tslint:disable */
/* eslint-disable */
/** /**
/* This file was automatically generated from pydantic models by running pydantic2ts. /* 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 /* 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 { 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"; const prefix = "/api";
@ -8,7 +8,7 @@ const routes = {
cookbooksId: (id: number) => `${prefix}/households/cookbooks/${id}`, 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; baseRoute: string = routes.cookbooks;
itemRoute = routes.cookbooksId; itemRoute = routes.cookbooksId;

View file

@ -81,4 +81,4 @@ declare module "vue" {
} }
} }
export { }; export {};

View file

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

View file

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

View file

@ -19,6 +19,8 @@ from mealie.routes._base.routers import UserAPIRouter
from mealie.schema.user import PrivateUser from mealie.schema.user import PrivateUser
from mealie.schema.user.auth import CredentialsRequestForm from mealie.schema.user.auth import CredentialsRequestForm
from .auth_cache import AuthCache
public_router = APIRouter(tags=["Users: Authentication"]) public_router = APIRouter(tags=["Users: Authentication"])
user_router = UserAPIRouter(tags=["Users: Authentication"]) user_router = UserAPIRouter(tags=["Users: Authentication"])
logger = root_logger.get_logger("auth") logger = root_logger.get_logger("auth")
@ -27,7 +29,7 @@ remember_me_duration = timedelta(days=14)
settings = get_app_settings() settings = get_app_settings()
if settings.OIDC_READY: if settings.OIDC_READY:
oauth = OAuth() oauth = OAuth(cache=AuthCache())
scope = None scope = None
if settings.OIDC_SCOPES_OVERRIDE: if settings.OIDC_SCOPES_OVERRIDE:
scope = 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 import controller
from mealie.routes._base.base_controllers import BasePublicHouseholdExploreController 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.make_dependable import make_dependable
from mealie.schema.response.pagination import PaginationBase, PaginationQuery 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()) response.set_pagination_guides(self.get_explore_url_path(router.url_path_for("get_all")), q.model_dump())
return response return response
@router.get("/{item_id}", response_model=RecipeCookBook) @router.get("/{item_id}", response_model=ReadCookBook)
def get_one(self, item_id: UUID4 | str) -> RecipeCookBook: def get_one(self, item_id: UUID4 | str) -> ReadCookBook:
NOT_FOUND_EXCEPTION = HTTPException(404, "cookbook not found") NOT_FOUND_EXCEPTION = HTTPException(404, "cookbook not found")
if isinstance(item_id, UUID): if isinstance(item_id, UUID):
match_attr = "id" match_attr = "id"
@ -58,13 +58,4 @@ class PublicCookbooksController(BasePublicHouseholdExploreController):
if not household or household.preferences.private_household: if not household or household.preferences.private_household:
raise NOT_FOUND_EXCEPTION raise NOT_FOUND_EXCEPTION
cross_household_recipes = self.cross_household_repos.recipes return cookbook
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)

View file

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

View file

@ -18,10 +18,28 @@ from .restore import (
from .settings import CustomPageBase, CustomPageOut from .settings import CustomPageBase, CustomPageOut
__all__ = [ __all__ = [
"MaintenanceLogs",
"MaintenanceStorageDetails",
"MaintenanceSummary",
"ChowdownURL", "ChowdownURL",
"MigrationFile", "MigrationFile",
"MigrationImport", "MigrationImport",
"Migrations", "Migrations",
"CustomPageBase",
"CustomPageOut",
"CommentImport",
"CustomPageImport",
"GroupImport",
"ImportBase",
"NotificationImport",
"RecipeImport",
"SettingsImport",
"UserImport",
"AllBackups",
"BackupFile",
"BackupOptions",
"CreateBackup",
"ImportJob",
"AdminAboutInfo", "AdminAboutInfo",
"AppInfo", "AppInfo",
"AppStartupInfo", "AppStartupInfo",
@ -31,23 +49,5 @@ __all__ = [
"EmailReady", "EmailReady",
"EmailSuccess", "EmailSuccess",
"EmailTest", "EmailTest",
"CustomPageBase",
"CustomPageOut",
"AllBackups",
"BackupFile",
"BackupOptions",
"CreateBackup",
"ImportJob",
"MaintenanceLogs",
"MaintenanceStorageDetails",
"MaintenanceSummary",
"DebugResponse", "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 # 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__ = [ __all__ = [
"CookBookPagination", "CookBookPagination",
"CreateCookBook", "CreateCookBook",
"ReadCookBook", "ReadCookBook",
"RecipeCookBook",
"SaveCookBook", "SaveCookBook",
"UpdateCookBook", "UpdateCookBook",
] ]

View file

@ -7,7 +7,6 @@ from slugify import slugify
from mealie.core.root_logger import get_logger from mealie.core.root_logger import get_logger
from mealie.db.models.recipe import RecipeModel from mealie.db.models.recipe import RecipeModel
from mealie.schema._mealie import MealieModel from mealie.schema._mealie import MealieModel
from mealie.schema.recipe.recipe import RecipeSummary
from mealie.schema.response.pagination import PaginationBase from mealie.schema.response.pagination import PaginationBase
from mealie.schema.response.query_filter import QueryFilterBuilder, QueryFilterJSON from mealie.schema.response.query_filter import QueryFilterBuilder, QueryFilterJSON
@ -84,10 +83,3 @@ class ReadCookBook(UpdateCookBook):
class CookBookPagination(PaginationBase): class CookBookPagination(PaginationBase):
items: list[ReadCookBook] 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 from .group_statistics import GroupStorage
__all__ = [ __all__ = [
"GroupAdminUpdate",
"GroupStorage",
"GroupDataExport", "GroupDataExport",
"SeederConfig",
"CreateGroupPreferences", "CreateGroupPreferences",
"ReadGroupPreferences", "ReadGroupPreferences",
"UpdateGroupPreferences", "UpdateGroupPreferences",
"GroupStorage",
"DataMigrationCreate", "DataMigrationCreate",
"SupportedMigrations", "SupportedMigrations",
"SeederConfig",
"GroupAdminUpdate",
] ]

View file

@ -70,6 +70,49 @@ from .invite_token import CreateInviteToken, EmailInitationResponse, EmailInvita
from .webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination, WebhookType from .webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination, WebhookType
__all__ = [ __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", "ShoppingListAddRecipeParams",
"ShoppingListAddRecipeParamsBulk", "ShoppingListAddRecipeParamsBulk",
"ShoppingListCreate", "ShoppingListCreate",
@ -93,48 +136,5 @@ __all__ = [
"ShoppingListSave", "ShoppingListSave",
"ShoppingListSummary", "ShoppingListSummary",
"ShoppingListUpdate", "ShoppingListUpdate",
"GroupEventNotifierCreate",
"GroupEventNotifierOptions",
"GroupEventNotifierOptionsOut",
"GroupEventNotifierOptionsSave",
"GroupEventNotifierOut",
"GroupEventNotifierPrivate",
"GroupEventNotifierSave",
"GroupEventNotifierUpdate",
"GroupEventPagination",
"CreateGroupRecipeAction",
"GroupRecipeActionOut",
"GroupRecipeActionPagination",
"GroupRecipeActionPayload",
"GroupRecipeActionType",
"SaveGroupRecipeAction",
"CreateHouseholdPreferences",
"ReadHouseholdPreferences",
"SaveHouseholdPreferences",
"UpdateHouseholdPreferences",
"SetPermissions", "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 from .shopping_list import ListItem, ShoppingListIn, ShoppingListOut
__all__ = [ __all__ = [
"ListItem",
"ShoppingListIn",
"ShoppingListOut",
"CreatePlanEntry", "CreatePlanEntry",
"CreateRandomEntry", "CreateRandomEntry",
"PlanEntryPagination", "PlanEntryPagination",
@ -19,9 +22,6 @@ __all__ = [
"ReadPlanEntry", "ReadPlanEntry",
"SavePlanEntry", "SavePlanEntry",
"UpdatePlanEntry", "UpdatePlanEntry",
"ListItem",
"ShoppingListIn",
"ShoppingListOut",
"PlanRulesCreate", "PlanRulesCreate",
"PlanRulesDay", "PlanRulesDay",
"PlanRulesOut", "PlanRulesOut",

View file

@ -89,6 +89,35 @@ from .recipe_tool import RecipeToolCreate, RecipeToolOut, RecipeToolResponse, Re
from .request_helpers import RecipeDuplicate, RecipeSlug, RecipeZipTokenResponse, SlugResponse, UpdateImageResponse from .request_helpers import RecipeDuplicate, RecipeSlug, RecipeZipTokenResponse, SlugResponse, UpdateImageResponse
__all__ = [ __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", "CreateIngredientFood",
"CreateIngredientFoodAlias", "CreateIngredientFoodAlias",
"CreateIngredientUnit", "CreateIngredientUnit",
@ -111,27 +140,13 @@ __all__ = [
"SaveIngredientFood", "SaveIngredientFood",
"SaveIngredientUnit", "SaveIngredientUnit",
"UnitFoodBase", "UnitFoodBase",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"Nutrition",
"AssignCategories",
"AssignSettings",
"AssignTags",
"DeleteRecipes",
"ExportBase",
"ExportRecipes",
"ExportTypes",
"RecipeCommentCreate", "RecipeCommentCreate",
"RecipeCommentOut", "RecipeCommentOut",
"RecipeCommentPagination", "RecipeCommentPagination",
"RecipeCommentSave", "RecipeCommentSave",
"RecipeCommentUpdate", "RecipeCommentUpdate",
"UserBase", "UserBase",
"RecipeSettings",
"CreateRecipe", "CreateRecipe",
"CreateRecipeBulk", "CreateRecipeBulk",
"CreateRecipeByUrlBulk", "CreateRecipeByUrlBulk",
@ -145,40 +160,25 @@ __all__ = [
"RecipeTagPagination", "RecipeTagPagination",
"RecipeTool", "RecipeTool",
"RecipeToolPagination", "RecipeToolPagination",
"IngredientReferences", "ScrapeRecipe",
"RecipeStep", "ScrapeRecipeBase",
"RecipeNote", "ScrapeRecipeData",
"RecipeSuggestionQuery", "ScrapeRecipeTest",
"RecipeSuggestionResponse", "AssignCategories",
"RecipeSuggestionResponseItem", "AssignSettings",
"RecipeSettings", "AssignTags",
"RecipeShareToken", "DeleteRecipes",
"RecipeShareTokenCreate", "ExportBase",
"RecipeShareTokenSave", "ExportRecipes",
"RecipeShareTokenSummary", "ExportTypes",
"RecipeAsset", "RecipeToolCreate",
"RecipeToolOut",
"RecipeToolResponse",
"RecipeToolSave",
"RecipeImageTypes",
"RecipeDuplicate", "RecipeDuplicate",
"RecipeSlug", "RecipeSlug",
"RecipeZipTokenResponse", "RecipeZipTokenResponse",
"SlugResponse", "SlugResponse",
"UpdateImageResponse", "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", "QueryFilterJSONPart",
"RelationalKeyword", "RelationalKeyword",
"RelationalOperator", "RelationalOperator",
"SearchFilter", "ValidationResponse",
"OrderByNullPosition", "OrderByNullPosition",
"OrderDirection", "OrderDirection",
"PaginationBase", "PaginationBase",
"PaginationQuery", "PaginationQuery",
"RecipeSearchQuery", "RecipeSearchQuery",
"RequestQuery", "RequestQuery",
"ValidationResponse", "SearchFilter",
"ErrorResponse", "ErrorResponse",
"FileTokenResponse", "FileTokenResponse",
"SuccessResponse", "SuccessResponse",

View file

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

222
poetry.lock generated
View file

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