1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-02 20:15:24 +02:00

feat: Add Households to Mealie (#3970)

This commit is contained in:
Michael Genson 2024-08-22 10:14:32 -05:00 committed by GitHub
parent 0c29cef17d
commit eb170cc7e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
315 changed files with 6975 additions and 3577 deletions

View file

@ -2,30 +2,11 @@
<div v-if="preferences">
<BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle>
<v-checkbox v-model="preferences.privateGroup" class="mt-n4" :label="$t('group.private-group')"></v-checkbox>
<v-select
v-model="preferences.firstDayOfWeek"
:prepend-icon="$globals.icons.calendarWeekBegin"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
/>
<BaseCardSectionTitle class="mt-5" :title="$tc('group.group-recipe-preferences')"></BaseCardSectionTitle>
<template v-for="(_, key) in preferences">
<v-checkbox
v-if="labels[key]"
:key="key"
v-model="preferences[key]"
class="mt-n4"
:label="labels[key]"
></v-checkbox>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
import { defineComponent, computed } from "@nuxtjs/composition-api";
export default defineComponent({
props: {
@ -35,48 +16,6 @@ export default defineComponent({
},
},
setup(props, context) {
const { i18n } = useContext();
const labels = {
recipePublic: i18n.tc("group.allow-users-outside-of-your-group-to-see-your-recipes"),
recipeShowNutrition: i18n.tc("group.show-nutrition-information"),
recipeShowAssets: i18n.tc("group.show-recipe-assets"),
recipeLandscapeView: i18n.tc("group.default-to-landscape-view"),
recipeDisableComments: i18n.tc("group.disable-users-from-commenting-on-recipes"),
recipeDisableAmount: i18n.tc("group.disable-organizing-recipe-ingredients-by-units-and-food"),
};
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
const preferences = computed({
get() {
return props.value;
@ -87,8 +26,6 @@ export default defineComponent({
});
return {
allDays,
labels,
preferences,
};
},

View file

@ -39,7 +39,7 @@
import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
import { Recipe } from "~/lib/api/types/recipe";
import RecipeDialogAddToShoppingList from "~/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue";
import { ShoppingListSummary } from "~/lib/api/types/group";
import { ShoppingListSummary } from "~/lib/api/types/household";
import { useUserApi } from "~/composables/api";
export interface ContextMenuItem {

View file

@ -35,7 +35,7 @@
<script lang="ts">
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
import { ReadWebhook } from "~/lib/api/types/group";
import { ReadWebhook } from "~/lib/api/types/household";
import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
export default defineComponent({

View file

@ -0,0 +1,99 @@
<template>
<div v-if="preferences">
<BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle>
<v-checkbox v-model="preferences.privateHousehold" class="mt-n4" :label="$t('household.private-household')"></v-checkbox>
<v-select
v-model="preferences.firstDayOfWeek"
:prepend-icon="$globals.icons.calendarWeekBegin"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
/>
<BaseCardSectionTitle class="mt-5" :title="$tc('household.household-recipe-preferences')"></BaseCardSectionTitle>
<template v-for="(_, key) in preferences">
<v-checkbox
v-if="labels[key]"
:key="key"
v-model="preferences[key]"
class="mt-n4"
:label="labels[key]"
></v-checkbox>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
export default defineComponent({
props: {
value: {
type: Object,
required: true,
},
},
setup(props, context) {
const { i18n } = useContext();
const labels = {
recipePublic: i18n.tc("household.allow-users-outside-of-your-household-to-see-your-recipes"),
recipeShowNutrition: i18n.tc("group.show-nutrition-information"),
recipeShowAssets: i18n.tc("group.show-recipe-assets"),
recipeLandscapeView: i18n.tc("group.default-to-landscape-view"),
recipeDisableComments: i18n.tc("group.disable-users-from-commenting-on-recipes"),
recipeDisableAmount: i18n.tc("group.disable-organizing-recipe-ingredients-by-units-and-food"),
};
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
const preferences = computed({
get() {
return props.value;
},
set(val) {
context.emit("input", val);
},
});
return {
allDays,
labels,
preferences,
};
},
});
</script>
<style lang="scss" scoped>
</style>

View file

@ -337,7 +337,7 @@ export default defineComponent({
);
break;
case EVENTS.updated:
setter("update_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending, "desc", false);
setter("updated_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending, "desc", false);
break;
case EVENTS.lastMade:
setter(

View file

@ -138,11 +138,11 @@ import RecipeDialogShare from "./RecipeDialogShare.vue";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useUserApi } from "~/composables/api";
import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions";
import { useGroupSelf } from "~/composables/use-groups";
import { useHouseholdSelf } from "~/composables/use-households";
import { alert } from "~/composables/use-toast";
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
import { Recipe } from "~/lib/api/types/recipe";
import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/group";
import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/household";
import { PlanEntryType } from "~/lib/api/types/meal-plan";
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
@ -254,14 +254,14 @@ export default defineComponent({
});
const { i18n, $auth, $globals } = useContext();
const { group } = useGroupSelf();
const { household } = useHouseholdSelf();
const { isOwnGroup } = useLoggedInState();
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const firstDayOfWeek = computed(() => {
return group.value?.preferences?.firstDayOfWeek || 0;
return household.value?.preferences?.firstDayOfWeek || 0;
});
// ===========================================================================

View file

@ -18,7 +18,7 @@
</tr>
</template>
<template #item.name="{ item }">
<a :href="`/r/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a>
<a :href="`/g/${groupSlug}/r/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a>
</template>
<template #item.tags="{ item }">
<RecipeChip small :items="item.tags" :is-category="false" url-prefix="tags" />
@ -48,7 +48,7 @@ import UserAvatar from "../User/UserAvatar.vue";
import RecipeChip from "./RecipeChips.vue";
import { Recipe } from "~/lib/api/types/recipe";
import { useUserApi } from "~/composables/api";
import { UserOut } from "~/lib/api/types/user";
import { UserSummary } from "~/lib/api/types/user";
const INPUT_EVENT = "input";
@ -95,7 +95,8 @@ export default defineComponent({
},
},
setup(props, context) {
const { i18n } = useContext();
const { $auth, i18n } = useContext();
const groupSlug = $auth.user?.groupSlug;
function setValue(value: Recipe[]) {
context.emit(INPUT_EVENT, value);
@ -134,7 +135,7 @@ export default defineComponent({
// ============
// Group Members
const api = useUserApi();
const members = ref<UserOut[]>([]);
const members = ref<UserSummary[]>([]);
async function refreshMembers() {
const { data } = await api.groups.fetchMembers();
@ -149,13 +150,19 @@ export default defineComponent({
function getMember(id: string) {
if (members.value[0]) {
return members.value.find((m) => m.id === id)?.username;
return members.value.find((m) => m.id === id)?.fullName;
}
return i18n.t("general.none");
}
return { setValue, headers, members, getMember };
return {
groupSlug,
setValue,
headers,
members,
getMember,
};
},
data() {

View file

@ -127,14 +127,14 @@
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, useContext, watchEffect } from "@nuxtjs/composition-api"
import { toRefs } from "@vueuse/core"
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue"
import { useUserApi } from "~/composables/api"
import { alert } from "~/composables/use-toast"
import { useShoppingListPreferences } from "~/composables/use-users/preferences"
import { ShoppingListSummary } from "~/lib/api/types/group"
import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe"
import { computed, defineComponent, reactive, ref, useContext, watchEffect } from "@nuxtjs/composition-api";
import { toRefs } from "@vueuse/core";
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { useShoppingListPreferences } from "~/composables/use-users/preferences";
import { ShoppingListSummary } from "~/lib/api/types/household";
import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe";
export interface RecipeWithScale extends Recipe {
scale: number;
@ -209,7 +209,8 @@ export default defineComponent({
watchEffect(
() => {
if (shoppingListChoices.value.length === 1 && !state.shoppingListShowAllToggled) {
openShoppingListIngredientDialog(shoppingListChoices.value[0]);
selectedShoppingList.value = shoppingListChoices.value[0];
openShoppingListIngredientDialog(selectedShoppingList.value);
} else {
ready.value = true;
}
@ -365,12 +366,8 @@ export default defineComponent({
}
})
const successMessage = promises.length === 1
? i18n.t("recipe.successfully-added-to-list") as string
: i18n.t("recipe.failed-to-add-to-list") as string;
success ? alert.success(successMessage)
: alert.error(i18n.t("failed-to-add-recipes-to-list") as string)
success ? alert.success(i18n.tc("recipe.successfully-added-to-list"))
: alert.error(i18n.tc("failed-to-add-recipes-to-list"))
state.shoppingListDialog = false;
state.shoppingListIngredientDialog = false;

View file

@ -66,7 +66,7 @@ import { defineComponent, computed, toRefs, reactive, useContext, useRoute } fro
import { useClipboard, useShare, whenever } from "@vueuse/core";
import { RecipeShareToken } from "~/lib/api/types/recipe";
import { useUserApi } from "~/composables/api";
import { useGroupSelf } from "~/composables/use-groups";
import { useHouseholdSelf } from "~/composables/use-households";
import { alert } from "~/composables/use-toast";
export default defineComponent({
@ -113,12 +113,12 @@ export default defineComponent({
);
const { $auth, i18n } = useContext();
const { group } = useGroupSelf();
const { household } = useHouseholdSelf();
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const firstDayOfWeek = computed(() => {
return group.value?.preferences?.firstDayOfWeek || 0;
return household.value?.preferences?.firstDayOfWeek || 0;
});
// ============================================================

View file

@ -349,7 +349,7 @@ export default defineComponent({
{
icon: $globals.icons.update,
name: i18n.tc("general.updated"),
value: "update_at",
value: "updated_at",
},
{
icon: $globals.icons.diceMultiple,

View file

@ -11,7 +11,7 @@
</template>
<script lang="ts">
import { computed, defineComponent } from "@nuxtjs/composition-api";
import { RecipeIngredient } from "~/lib/api/types/group";
import { RecipeIngredient } from "~/lib/api/types/household";
import { useParsedIngredientText } from "~/composables/recipes";
export default defineComponent({

View file

@ -114,7 +114,7 @@ import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@n
import { whenever } from "@vueuse/core";
import { VForm } from "~/types/vuetify";
import { useUserApi } from "~/composables/api";
import { useGroupSelf } from "~/composables/use-groups";
import { useHouseholdSelf } from "~/composables/use-households";
import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe";
export default defineComponent({
@ -131,7 +131,7 @@ export default defineComponent({
setup(props, context) {
const madeThisDialog = ref(false);
const userApi = useUserApi();
const { group } = useGroupSelf();
const { household } = useHouseholdSelf();
const { $auth, i18n } = useContext();
const domMadeThisForm = ref<VForm>();
const newTimelineEvent = ref<RecipeTimelineEventIn>({
@ -157,7 +157,7 @@ export default defineComponent({
);
const firstDayOfWeek = computed(() => {
return group.value?.preferences?.firstDayOfWeek || 0;
return household.value?.preferences?.firstDayOfWeek || 0;
});
function clearImage() {

View file

@ -31,7 +31,7 @@
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
import DOMPurify from "dompurify";
import { useFraction } from "~/composables/recipes/use-fraction";
import { ShoppingListItemOut } from "~/lib/api/types/group";
import { ShoppingListItemOut } from "~/lib/api/types/household";
import { RecipeSummary } from "~/lib/api/types/recipe";
export default defineComponent({

View file

@ -29,7 +29,7 @@
<script lang="ts">
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
import { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/group";
import { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/household";
interface actions {
text: string;

View file

@ -75,7 +75,7 @@
<v-row v-if="listItem.checked" no-gutters class="mb-2">
<v-col cols="auto">
<div class="text-caption font-weight-light font-italic">
{{ $t("shopping-list.completed-on", {date: new Date(listItem.updateAt || "").toLocaleDateString($i18n.locale)}) }}
{{ $t("shopping-list.completed-on", {date: new Date(listItem.updatedAt || "").toLocaleDateString($i18n.locale)}) }}
</div>
</v-col>
</v-row>
@ -99,7 +99,7 @@ import { defineComponent, computed, ref, useContext } from "@nuxtjs/composition-
import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue";
import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";
import MultiPurposeLabel from "./MultiPurposeLabel.vue";
import { ShoppingListItemOut } from "~/lib/api/types/group";
import { ShoppingListItemOut } from "~/lib/api/types/household";
import { MultiPurposeLabelOut, MultiPurposeLabelSummary } from "~/lib/api/types/labels";
import { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe";
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";

View file

@ -111,7 +111,7 @@
<script lang="ts">
import { defineComponent, computed, watch } from "@nuxtjs/composition-api";
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/household";
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";

View file

@ -183,7 +183,7 @@ export default defineComponent({
{
icon: $globals.icons.calendarMultiselect,
title: i18n.tc("meal-plan.meal-planner"),
to: "/group/mealplan/planner/view",
to: "/household/mealplan/planner/view",
restricted: true,
},
{

View file

@ -1,5 +1,5 @@
<template>
<v-toolbar flat>
<v-toolbar color="transparent" flat>
<BaseButton color="null" rounded secondary @click="$router.go(-1)">
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
{{ $t('general.back') }}

View file

@ -85,6 +85,8 @@
:label="inputField.label"
:name="inputField.varName"
:items="inputField.options"
:item-text="inputField.itemText"
:item-value="inputField.itemValue"
:return-object="false"
:hint="inputField.hint"
persistent-hint

View file

@ -4,7 +4,7 @@ import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
import { QueryValue } from "~/lib/api/base/route";
type BoundT = {
id?: string | number;
id?: string | number | null;
};
interface PublicStoreActions<T extends BoundT> {

View file

@ -160,6 +160,9 @@ export function usePageUser(): { user: UserOut } {
group: "",
groupId: "",
groupSlug: "",
household: "",
householdId: "",
householdSlug: "",
cacheKey: "",
email: "",
},

View file

@ -46,7 +46,7 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
}
const { quantity, food, unit, note } = ingredient;
const usePluralUnit = quantity !== undefined && (quantity * scale > 1 || quantity * scale === 0);
const usePluralUnit = quantity !== undefined && ((quantity || 0) * scale > 1 || (quantity || 0) * scale === 0);
const usePluralFood = (!quantity) || quantity * scale > 1
let returnQty = "";
@ -69,8 +69,8 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
}
}
const unitName = useUnitName(unit, usePluralUnit);
const foodName = useFoodName(food, usePluralFood);
const unitName = useUnitName(unit || undefined, usePluralUnit);
const foodName = useFoodName(food || undefined, usePluralFood);
return {
quantity: returnQty ? sanitizeIngredientHTML(returnQty) : undefined,

View file

@ -2,7 +2,7 @@ import { reactive, ref, Ref } from "@nuxtjs/composition-api";
import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory";
import { usePublicExploreApi } from "../api/api-client";
import { useUserApi } from "~/composables/api";
import { RecipeTag } from "~/lib/api/types/admin";
import { RecipeTag } from "~/lib/api/types/recipe";
const items: Ref<RecipeTag[]> = ref([]);
const publicStoreLoading = ref(false);

View file

@ -1,6 +1,7 @@
import { useAsync, ref, Ref, useContext } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { usePublicExploreApi } from "./api/api-client";
import { useHouseholdSelf } from "./use-households";
import { useUserApi } from "~/composables/api";
import { ReadCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
@ -67,6 +68,7 @@ export const usePublicCookbooks = function (groupSlug: string) {
export const useCookbooks = function () {
const api = useUserApi();
const { household } = useHouseholdSelf();
const loading = ref(false);
const { i18n } = useContext();
@ -100,7 +102,7 @@ export const useCookbooks = function () {
async createOne() {
loading.value = true;
const { data } = await api.cookbooks.createOne({
name: i18n.t("cookbook.cookbook-with-name", [String((cookbookStore?.value?.length ?? 0) + 1)]) as string,
name: i18n.t("cookbook.household-cookbook-name", [household.value?.name || "", String((cookbookStore?.value?.length ?? 0) + 1)]) as string,
});
if (data && cookbookStore?.value) {
cookbookStore.value.push(data);

View file

@ -1,7 +1,7 @@
import { computed, reactive, ref } from "@nuxtjs/composition-api";
import { useStoreActions } from "./partials/use-actions-factory";
import { useUserApi } from "~/composables/api";
import { GroupRecipeActionOut, RecipeActionType } from "~/lib/api/types/group";
import { GroupRecipeActionOut, GroupRecipeActionType } from "~/lib/api/types/household";
import { Recipe } from "~/lib/api/types/recipe";
const groupRecipeActions = ref<GroupRecipeActionOut[] | null>(null);
@ -10,7 +10,7 @@ const loading = ref(false);
export function useGroupRecipeActionData() {
const data = reactive({
id: "",
actionType: "link" as RecipeActionType,
actionType: "link" as GroupRecipeActionType,
title: "",
url: "",
});

View file

@ -1,7 +1,7 @@
import { useAsync, ref } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { useUserApi } from "~/composables/api";
import { ReadWebhook } from "~/lib/api/types/group";
import { ReadWebhook } from "~/lib/api/types/household";
export const useGroupWebhooks = function () {
const api = useUserApi();

View file

@ -51,7 +51,7 @@ export const useGroups = function () {
loading.value = true;
const asyncKey = String(Date.now());
const groups = useAsync(async () => {
const { data } = await api.groups.getAll();
const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});;
if (data) {
return data.items;
@ -66,7 +66,7 @@ export const useGroups = function () {
async function refreshAllGroups() {
loading.value = true;
const { data } = await api.groups.getAll();
const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});;
if (data) {
groups.value = data.items;

View file

@ -0,0 +1,117 @@
import { computed, ref, Ref, useAsync } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { HouseholdCreate, HouseholdInDB } from "~/lib/api/types/household";
const householdSelfRef = ref<HouseholdInDB | null>(null);
const loading = ref(false);
export const useHouseholdSelf = function () {
const api = useUserApi();
async function refreshHouseholdSelf() {
loading.value = true;
const { data } = await api.households.getCurrentUserHousehold();
householdSelfRef.value = data;
loading.value = false;
}
const actions = {
get() {
if (!(householdSelfRef.value || loading.value)) {
refreshHouseholdSelf();
}
return householdSelfRef;
},
async updatePreferences() {
if (!householdSelfRef.value) {
await refreshHouseholdSelf();
}
if (!householdSelfRef.value?.preferences) {
return;
}
const { data } = await api.households.setPreferences(householdSelfRef.value.preferences);
if (data) {
householdSelfRef.value.preferences = data;
}
},
};
const household = actions.get();
return { actions, household };
};
export const useHouseholds = function () {
const api = useUserApi();
const loading = ref(false);
function getAllHouseholds() {
loading.value = true;
const asyncKey = String(Date.now());
const households = useAsync(async () => {
const { data } = await api.households.getAll(1, -1, {orderBy: "name, group.name", orderDirection: "asc"});
if (data) {
return data.items;
} else {
return null;
}
}, asyncKey);
loading.value = false;
return households;
}
async function refreshAllHouseholds() {
loading.value = true;
const { data } = await api.households.getAll(1, -1, {orderBy: "name, group.name", orderDirection: "asc"});;
if (data) {
households.value = data.items;
} else {
households.value = null;
}
loading.value = false;
}
async function deleteHousehold(id: string | number) {
loading.value = true;
const { data } = await api.households.deleteOne(id);
loading.value = false;
refreshAllHouseholds();
return data;
}
async function createHousehold(payload: HouseholdCreate) {
loading.value = true;
const { data } = await api.households.createOne(payload);
if (data && households.value) {
households.value.push(data);
}
}
const households = getAllHouseholds();
function useHouseholdsInGroup(groupIdRef: Ref<string>) {
return computed(
() => {
return (households.value && groupIdRef.value)
? households.value.filter((h) => h.groupId === groupIdRef.value)
: [];
},
);
}
return {
households,
useHouseholdsInGroup,
getAllHouseholds,
refreshAllHouseholds,
deleteHousehold,
createHousehold,
};
};

View file

@ -1,7 +1,7 @@
import { computed, reactive, watch } from "@nuxtjs/composition-api";
import { useLocalStorage } from "@vueuse/core";
import { useUserApi } from "~/composables/api";
import { ShoppingListItemOut, ShoppingListOut } from "~/lib/api/types/group";
import { ShoppingListItemOut, ShoppingListOut } from "~/lib/api/types/household";
import { RequestResponse } from "~/lib/api/types/non-generated";
const localStorageKey = "shopping-list-queue";
@ -144,7 +144,7 @@ export function useShoppingListItemActions(shoppingListId: string) {
function checkUpdateState(list: ShoppingListOut) {
const cutoffDate = new Date(queue.lastUpdate + queueTimeout).toISOString();
if (list.updateAt && list.updateAt > cutoffDate) {
if (list.updatedAt && list.updatedAt > cutoffDate) {
// If the queue is too far behind the shopping list to reliably do updates, we clear the queue
console.log("Out of sync with server; clearing queue");
clearQueueItems("all");

View file

@ -8,6 +8,7 @@
"database-type": "Database Type",
"database-url": "Database URL",
"default-group": "Default Group",
"default-household": "Default Household",
"demo": "Demo",
"demo-status": "Demo Status",
"development": "Development",
@ -238,7 +239,7 @@
"keep-my-recipes-private-description": "Sets your group and all recipes defaults to private. You can always change this later."
},
"manage-members": "Manage Members",
"manage-members-description": "Manage the permissions of the members in your groups. {manage} allows the user to access the data-management page {invite} allows the user to generate invitation links for other users. Group owners cannot change their own permissions.",
"manage-members-description": "Manage the permissions of the members in your household. {manage} allows the user to access the data-management page, and {invite} allows the user to generate invitation links for other users. Group owners cannot change their own permissions.",
"manage": "Manage",
"invite": "Invite",
"looking-to-update-your-profile": "Looking to Update Your Profile?",
@ -246,7 +247,7 @@
"default-recipe-preferences": "Default Recipe Preferences",
"group-preferences": "Group Preferences",
"private-group": "Private Group",
"private-group-description": "Setting your group to private will default all public view options to default. This overrides an individual recipes public view settings.",
"private-group-description": "Setting your group to private will default all public view options to default. This overrides any individual households or recipes public view settings.",
"enable-public-access": "Enable Public Access",
"enable-public-access-description": "Make group recipes public by default, and allow visitors to view recipes without logging-in",
"allow-users-outside-of-your-group-to-see-your-recipes": "Allow users outside of your group to see your recipes",
@ -260,7 +261,7 @@
"disable-users-from-commenting-on-recipes": "Disable users from commenting on recipes",
"disable-users-from-commenting-on-recipes-description": "Hides the comment section on the recipe page and disables commenting",
"disable-organizing-recipe-ingredients-by-units-and-food": "Disable organizing recipe ingredients by units and food",
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields.",
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields",
"general-preferences": "General Preferences",
"group-recipe-preferences": "Group Recipe Preferences",
"report": "Report",
@ -268,7 +269,28 @@
"group-management": "Group Management",
"admin-group-management": "Admin Group Management",
"admin-group-management-text": "Changes to this group will be reflected immediately.",
"group-id-value": "Group Id: {0}"
"group-id-value": "Group Id: {0}",
"total-households": "Total Households"
},
"household": {
"household": "Household",
"households": "Households",
"user-household": "User Household",
"create-household": "Create Household",
"household-name": "Household Name",
"household-group": "Household Group",
"household-management": "Household Management",
"manage-households": "Manage Households",
"admin-household-management": "Admin Household Management",
"admin-household-management-text": "Changes to this household will be reflected immediately.",
"household-id-value": "Household Id: {0}",
"private-household": "Private Household",
"private-household-description": "Setting your household to private will default all public view options to default. This overrides any individual recipes public view settings.",
"household-recipe-preferences": "Household Recipe Preferences",
"default-recipe-preferences-description": "These are the default settings when a new recipe is created in your household. These can be changed for individual recipes in the recipe settings menu.",
"allow-users-outside-of-your-household-to-see-your-recipes": "Allow users outside of your household to see your recipes",
"allow-users-outside-of-your-household-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your household or with a pre-generated private link",
"household-preferences": "Household Preferences"
},
"meal-plan": {
"create-a-new-meal-plan": "Create a New Meal Plan",
@ -1230,6 +1252,8 @@
"account-summary-description": "Here's a summary of your group's information.",
"group-statistics": "Group Statistics",
"group-statistics-description": "Your Group Statistics provide some insight how you're using Mealie.",
"household-statistics": "Household Statistics",
"household-statistics-description": "Your Household Statistics provide some insight how you're using Mealie.",
"storage-capacity": "Storage Capacity",
"storage-capacity-description": "Your storage capacity is a calculation of the images and assets you have uploaded.",
"personal": "Personal",
@ -1239,10 +1263,13 @@
"api-tokens-description": "Manage your API Tokens for access from external applications.",
"group-description": "These items are shared within your group. Editing one of them will change it for the whole group!",
"group-settings": "Group Settings",
"group-settings-description": "Manage your common group settings like mealplan and privacy settings.",
"group-settings-description": "Manage your common group settings, like privacy settings.",
"household-description": "These items are shared within your household. Editing one of them will change it for the whole household!",
"household-settings": "Household Settings",
"household-settings-description": "Manage your household settings, like mealplan and privacy settings.",
"cookbooks-description": "Manage a collection of recipe categories and generate pages for them.",
"members": "Members",
"members-description": "See who's in your group and manage their permissions.",
"members-description": "See who's in your household and manage their permissions.",
"webhooks-description": "Set up webhooks that trigger on days that you have mealplans scheduled.",
"notifiers": "Notifiers",
"notifiers-description": "Set up email and push notifications that trigger on specific events.",
@ -1277,6 +1304,7 @@
"require-all-tools": "Require All Tools",
"cookbook-name": "Cookbook Name",
"cookbook-with-name": "Cookbook {0}",
"household-cookbook-name": "{0} Cookbook {1}",
"create-a-cookbook": "Create a Cookbook",
"cookbook": "Cookbook"
}

View file

@ -64,6 +64,12 @@ export default defineComponent({
title: i18n.tc("user.users"),
restricted: true,
},
{
icon: $globals.icons.household,
to: "/admin/manage/households",
title: i18n.tc("household.households"),
restricted: true,
},
{
icon: $globals.icons.group,
to: "/admin/manage/groups",

View file

@ -1,5 +1,5 @@
<template>
<v-app dark>
<v-app v-if="ready" dark>
<v-card-title>
<slot>
<h1 class="mx-auto">{{ $t("page.404-page-not-found") }}</h1>
@ -75,9 +75,21 @@ export default defineComponent({
}
}
async function handle404() {
const normalizedRoute = route.value.fullPath.replace(/\/$/, "");
const newRoute = normalizedRoute.replace(/^\/group\/(mealplan|members|notifiers|webhooks)(\/.*)?$/, "/household/$1$2");
if (newRoute !== normalizedRoute) {
await router.replace(newRoute);
} else {
await insertGroupSlugIntoRoute();
}
ready.value = true;
}
if (props.error.statusCode === 404) {
// see if adding the groupSlug fixes the error
insertGroupSlugIntoRoute().then(() => { ready.value = true });
handle404();
} else {
ready.value = true;
}

View file

@ -1,5 +1,6 @@
import { RecipeAPI } from "./user/recipes";
import { UserApi } from "./user/users";
import { HouseholdAPI } from "./user/households";
import { GroupAPI } from "./user/groups";
import { BackupAPI } from "./user/backups";
import { UploadFile } from "./user/upload";
@ -28,6 +29,7 @@ import { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class UserApiClient {
public recipes: RecipeAPI;
public users: UserApi;
public households: HouseholdAPI;
public groups: GroupAPI;
public backups: BackupAPI;
public categories: CategoriesAPI;
@ -63,6 +65,7 @@ export class UserApiClient {
// Users
this.users = new UserApi(requests);
this.households = new HouseholdAPI(requests);
this.groups = new GroupAPI(requests);
this.cookbooks = new CookbookAPI(requests);
this.groupRecipeActions = new GroupRecipeActionsAPI(requests);

View file

@ -3,10 +3,11 @@ import { RecipeCookBook } from "~/lib/api/types/cookbook";
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
const prefix = "/api";
const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}`
const routes = {
cookbooksGroupSlug: (groupSlug: string | number) => `${prefix}/explore/cookbooks/${groupSlug}`,
cookbooksGroupSlugCookbookId: (groupSlug: string | number, cookbookId: string | number) => `${prefix}/explore/cookbooks/${groupSlug}/${cookbookId}`,
cookbooksGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/cookbooks`,
cookbooksGroupSlugCookbookId: (groupSlug: string | number, cookbookId: string | number) => `${exploreGroupSlug(groupSlug)}/cookbooks/${cookbookId}`,
};
export class PublicCookbooksApi extends BaseCRUDAPIReadOnly<RecipeCookBook> {

View file

@ -3,10 +3,11 @@ import { IngredientFood } from "~/lib/api/types/recipe";
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
const prefix = "/api";
const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}`
const routes = {
foodsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/foods/${groupSlug}`,
foodsGroupSlugFoodId: (groupSlug: string | number, foodId: string | number) => `${prefix}/explore/foods/${groupSlug}/${foodId}`,
foodsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/foods`,
foodsGroupSlugFoodId: (groupSlug: string | number, foodId: string | number) => `${exploreGroupSlug(groupSlug)}/foods/${foodId}`,
};
export class PublicFoodsApi extends BaseCRUDAPIReadOnly<IngredientFood> {

View file

@ -3,14 +3,15 @@ import { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
const prefix = "/api";
const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}`
const routes = {
categoriesGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/categories`,
categoriesGroupSlugCategoryId: (groupSlug: string | number, categoryId: string | number) => `${prefix}/explore/organizers/${groupSlug}/categories/${categoryId}`,
tagsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/tags`,
tagsGroupSlugTagId: (groupSlug: string | number, tagId: string | number) => `${prefix}/explore/organizers/${groupSlug}/tags/${tagId}`,
toolsGroupSlug: (groupSlug: string | number) => `${prefix}/explore/organizers/${groupSlug}/tools`,
toolsGroupSlugToolId: (groupSlug: string | number, toolId: string | number) => `${prefix}/explore/organizers/${groupSlug}/tools/${toolId}`,
categoriesGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/categories`,
categoriesGroupSlugCategoryId: (groupSlug: string | number, categoryId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/categories/${categoryId}`,
tagsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tags`,
tagsGroupSlugTagId: (groupSlug: string | number, tagId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tags/${tagId}`,
toolsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tools`,
toolsGroupSlugToolId: (groupSlug: string | number, toolId: string | number) => `${exploreGroupSlug(groupSlug)}/organizers/tools`,
};
export class PublicCategoriesApi extends BaseCRUDAPIReadOnly<RecipeCategory> {

View file

@ -5,10 +5,11 @@ import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generate
import { RecipeSearchQuery } from "../../user/recipes/recipe";
const prefix = "/api";
const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}`
const routes = {
recipesGroupSlug: (groupSlug: string | number) => `${prefix}/explore/recipes/${groupSlug}`,
recipesGroupSlugRecipeSlug: (groupSlug: string | number, recipeSlug: string | number) => `${prefix}/explore/recipes/${groupSlug}/${recipeSlug}`,
recipesGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/recipes`,
recipesGroupSlugRecipeSlug: (groupSlug: string | number, recipeSlug: string | number) => `${exploreGroupSlug(groupSlug)}/recipes/${recipeSlug}`,
};
export class PublicRecipeApi extends BaseCRUDAPIReadOnly<Recipe> {

View file

@ -10,6 +10,8 @@ export interface AdminAboutInfo {
version: string;
demoStatus: boolean;
allowSignup: boolean;
defaultGroupSlug?: string | null;
defaultHouseholdSlug?: string | null;
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
@ -18,8 +20,9 @@ export interface AdminAboutInfo {
apiPort: number;
apiDocs: boolean;
dbType: string;
dbUrl?: string;
dbUrl?: string | null;
defaultGroup: string;
defaultHousehold: string;
buildId: string;
recipeScraperVersion: string;
}
@ -37,7 +40,8 @@ export interface AppInfo {
version: string;
demoStatus: boolean;
allowSignup: boolean;
defaultGroupSlug?: string;
defaultGroupSlug?: string | null;
defaultHouseholdSlug?: string | null;
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
@ -51,6 +55,7 @@ export interface AppStartupInfo {
export interface AppStatistics {
totalRecipes: number;
totalUsers: number;
totalHouseholds: number;
totalGroups: number;
uncategorizedRecipes: number;
untaggedRecipes: number;
@ -93,16 +98,16 @@ export interface ChowdownURL {
export interface CommentImport {
name: string;
status: boolean;
exception?: string;
exception?: string | null;
}
export interface CreateBackup {
tag?: string;
tag?: string | null;
options: BackupOptions;
templates?: string[];
templates?: string[] | null;
}
export interface CustomPageBase {
name: string;
slug?: string;
slug: string | null;
position: number;
categories?: RecipeCategoryResponse[];
}
@ -113,38 +118,41 @@ export interface RecipeCategoryResponse {
recipes?: RecipeSummary[];
}
export interface RecipeSummary {
id?: string;
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string;
name?: string | null;
slug?: string;
image?: unknown;
recipeYield?: string;
totalTime?: string;
prepTime?: string;
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
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;
orgURL?: string;
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
updateAt?: string;
lastMade?: string;
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;
id?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string;
id?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTool {
id: string;
@ -155,11 +163,11 @@ export interface RecipeTool {
export interface CustomPageImport {
name: string;
status: boolean;
exception?: string;
exception?: string | null;
}
export interface CustomPageOut {
name: string;
slug?: string;
slug: string | null;
position: number;
categories?: RecipeCategoryResponse[];
id: number;
@ -169,7 +177,7 @@ export interface EmailReady {
}
export interface EmailSuccess {
success: boolean;
error?: string;
error?: string | null;
}
export interface EmailTest {
email: string;
@ -177,12 +185,12 @@ export interface EmailTest {
export interface GroupImport {
name: string;
status: boolean;
exception?: string;
exception?: string | null;
}
export interface ImportBase {
name: string;
status: boolean;
exception?: string;
exception?: string | null;
}
export interface ImportJob {
recipes?: boolean;
@ -217,8 +225,8 @@ export interface MigrationFile {
export interface MigrationImport {
name: string;
status: boolean;
exception?: string;
slug?: string;
exception?: string | null;
slug?: string | null;
}
export interface Migrations {
type: string;
@ -227,25 +235,26 @@ export interface Migrations {
export interface NotificationImport {
name: string;
status: boolean;
exception?: string;
exception?: string | null;
}
export interface OIDCInfo {
configurationUrl?: string;
clientId?: string;
configurationUrl: string | null;
clientId: string | null;
groupsClaim: string | null;
}
export interface RecipeImport {
name: string;
status: boolean;
exception?: string;
slug?: string;
exception?: string | null;
slug?: string | null;
}
export interface SettingsImport {
name: string;
status: boolean;
exception?: string;
exception?: string | null;
}
export interface UserImport {
name: string;
status: boolean;
exception?: string;
exception?: string | null;
}

View file

@ -8,7 +8,7 @@
export interface CreateCookBook {
name: string;
description?: string;
slug?: string;
slug?: string | null;
position?: number;
public?: boolean;
categories?: CategoryBase[];
@ -37,7 +37,7 @@ export interface RecipeTool {
export interface ReadCookBook {
name: string;
description?: string;
slug?: string;
slug?: string | null;
position?: number;
public?: boolean;
categories?: CategoryBase[];
@ -47,12 +47,13 @@ export interface ReadCookBook {
requireAllTags?: boolean;
requireAllTools?: boolean;
groupId: string;
householdId: string;
id: string;
}
export interface RecipeCookBook {
name: string;
description?: string;
slug?: string;
slug?: string | null;
position?: number;
public?: boolean;
categories?: CategoryBase[];
@ -62,47 +63,51 @@ export interface RecipeCookBook {
requireAllTags?: boolean;
requireAllTools?: boolean;
groupId: string;
householdId: string;
id: string;
recipes: RecipeSummary[];
}
export interface RecipeSummary {
id?: string;
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string;
name?: string | null;
slug?: string;
image?: unknown;
recipeYield?: string;
totalTime?: string;
prepTime?: string;
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
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;
orgURL?: string;
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
updateAt?: string;
lastMade?: string;
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;
id?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string;
id?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface SaveCookBook {
name: string;
description?: string;
slug?: string;
slug?: string | null;
position?: number;
public?: boolean;
categories?: CategoryBase[];
@ -112,11 +117,12 @@ export interface SaveCookBook {
requireAllTags?: boolean;
requireAllTools?: boolean;
groupId: string;
householdId: string;
}
export interface UpdateCookBook {
name: string;
description?: string;
slug?: string;
slug?: string | null;
position?: number;
public?: boolean;
categories?: CategoryBase[];
@ -126,5 +132,6 @@ export interface UpdateCookBook {
requireAllTags?: boolean;
requireAllTools?: boolean;
groupId: string;
householdId: string;
id: string;
}

View file

@ -5,10 +5,6 @@
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type RecipeActionType =
| "link"
| "post";
export type WebhookType = "mealplan";
export type SupportedMigrations =
| "nextcloud"
| "chowdown"
@ -17,59 +13,23 @@ export type SupportedMigrations =
| "mealie_alpha"
| "tandoor"
| "plantoeat"
| "myrecipebox"
| "recipekeeper";
export interface CreateGroupPreferences {
privateGroup?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
groupId: string;
}
export interface CreateGroupRecipeAction {
actionType: RecipeActionType;
title: string;
url: string;
}
export interface CreateInviteToken {
uses: number;
}
export interface CreateWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
}
export interface DataMigrationCreate {
sourceType: SupportedMigrations;
}
export interface EmailInitationResponse {
success: boolean;
error?: string;
}
export interface EmailInvitation {
email: string;
token: string;
}
export interface GroupAdminUpdate {
id: string;
name: string;
preferences?: UpdateGroupPreferences;
preferences?: UpdateGroupPreferences | null;
}
export interface UpdateGroupPreferences {
privateGroup?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
}
export interface GroupDataExport {
id: string;
@ -80,140 +40,6 @@ export interface GroupDataExport {
size: string;
expires: string;
}
export interface GroupEventNotifierCreate {
name: string;
appriseUrl: string;
}
/**
* These events are in-sync with the EventTypes found in the EventBusService.
* If you modify this, make sure to update the EventBusService as well.
*/
export interface GroupEventNotifierOptions {
testMessage?: boolean;
webhookTask?: boolean;
recipeCreated?: boolean;
recipeUpdated?: boolean;
recipeDeleted?: boolean;
userSignup?: boolean;
dataMigrations?: boolean;
dataExport?: boolean;
dataImport?: boolean;
mealplanEntryCreated?: boolean;
shoppingListCreated?: boolean;
shoppingListUpdated?: boolean;
shoppingListDeleted?: boolean;
cookbookCreated?: boolean;
cookbookUpdated?: boolean;
cookbookDeleted?: boolean;
tagCreated?: boolean;
tagUpdated?: boolean;
tagDeleted?: boolean;
categoryCreated?: boolean;
categoryUpdated?: boolean;
categoryDeleted?: boolean;
}
/**
* These events are in-sync with the EventTypes found in the EventBusService.
* If you modify this, make sure to update the EventBusService as well.
*/
export interface GroupEventNotifierOptionsOut {
testMessage?: boolean;
webhookTask?: boolean;
recipeCreated?: boolean;
recipeUpdated?: boolean;
recipeDeleted?: boolean;
userSignup?: boolean;
dataMigrations?: boolean;
dataExport?: boolean;
dataImport?: boolean;
mealplanEntryCreated?: boolean;
shoppingListCreated?: boolean;
shoppingListUpdated?: boolean;
shoppingListDeleted?: boolean;
cookbookCreated?: boolean;
cookbookUpdated?: boolean;
cookbookDeleted?: boolean;
tagCreated?: boolean;
tagUpdated?: boolean;
tagDeleted?: boolean;
categoryCreated?: boolean;
categoryUpdated?: boolean;
categoryDeleted?: boolean;
id: string;
}
/**
* These events are in-sync with the EventTypes found in the EventBusService.
* If you modify this, make sure to update the EventBusService as well.
*/
export interface GroupEventNotifierOptionsSave {
testMessage?: boolean;
webhookTask?: boolean;
recipeCreated?: boolean;
recipeUpdated?: boolean;
recipeDeleted?: boolean;
userSignup?: boolean;
dataMigrations?: boolean;
dataExport?: boolean;
dataImport?: boolean;
mealplanEntryCreated?: boolean;
shoppingListCreated?: boolean;
shoppingListUpdated?: boolean;
shoppingListDeleted?: boolean;
cookbookCreated?: boolean;
cookbookUpdated?: boolean;
cookbookDeleted?: boolean;
tagCreated?: boolean;
tagUpdated?: boolean;
tagDeleted?: boolean;
categoryCreated?: boolean;
categoryUpdated?: boolean;
categoryDeleted?: boolean;
notifierId: string;
}
export interface GroupEventNotifierOut {
id: string;
name: string;
enabled: boolean;
groupId: string;
options: GroupEventNotifierOptionsOut;
}
export interface GroupEventNotifierPrivate {
id: string;
name: string;
enabled: boolean;
groupId: string;
options: GroupEventNotifierOptionsOut;
appriseUrl: string;
}
export interface GroupEventNotifierSave {
name: string;
appriseUrl: string;
enabled?: boolean;
groupId: string;
options?: GroupEventNotifierOptions;
}
export interface GroupEventNotifierUpdate {
name: string;
appriseUrl?: string;
enabled?: boolean;
groupId: string;
options?: GroupEventNotifierOptions;
id: string;
}
export interface GroupRecipeActionOut {
actionType: RecipeActionType;
title: string;
url: string;
groupId: string;
id: string;
}
export interface GroupStatistics {
totalRecipes: number;
totalUsers: number;
totalCategories: number;
totalTags: number;
totalTools: number;
}
export interface GroupStorage {
usedStorageBytes: number;
usedStorageStr: string;
@ -222,408 +48,9 @@ export interface GroupStorage {
}
export interface ReadGroupPreferences {
privateGroup?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
groupId: string;
id: string;
}
export interface ReadInviteToken {
token: string;
usesLeft: number;
groupId: string;
}
export interface ReadWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
groupId: string;
id: string;
}
export interface SaveGroupRecipeAction {
actionType: RecipeActionType;
title: string;
url: string;
groupId: string;
}
export interface SaveInviteToken {
usesLeft: number;
groupId: string;
token: string;
}
export interface SaveWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
groupId: string;
}
export interface SeederConfig {
locale: string;
}
export interface SetPermissions {
userId: string;
canManage?: boolean;
canInvite?: boolean;
canOrganize?: boolean;
}
export interface ShoppingListAddRecipeParams {
recipeIncrementQuantity?: number;
recipeIngredients?: RecipeIngredient[];
}
export interface RecipeIngredient {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
display?: string;
title?: string;
originalText?: string;
referenceId?: string;
}
export interface IngredientUnit {
name: string;
pluralName?: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
pluralAbbreviation?: string;
useAbbreviation?: boolean;
aliases?: IngredientUnitAlias[];
id: string;
createdAt?: string;
updateAt?: string;
}
export interface IngredientUnitAlias {
name: string;
}
export interface CreateIngredientUnit {
name: string;
pluralName?: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
pluralAbbreviation?: string;
useAbbreviation?: boolean;
aliases?: CreateIngredientUnitAlias[];
}
export interface CreateIngredientUnitAlias {
name: string;
}
export interface IngredientFood {
name: string;
pluralName?: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
aliases?: IngredientFoodAlias[];
id: string;
label?: MultiPurposeLabelSummary;
createdAt?: string;
updateAt?: string;
}
export interface IngredientFoodAlias {
name: string;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
export interface CreateIngredientFood {
name: string;
pluralName?: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
aliases?: CreateIngredientFoodAlias[];
}
export interface CreateIngredientFoodAlias {
name: string;
}
export interface ShoppingListCreate {
name?: string;
extras?: {
[k: string]: unknown;
};
createdAt?: string;
updateAt?: string;
}
export interface ShoppingListItemBase {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string;
labelId?: string;
unitId?: string;
extras?: {
[k: string]: unknown;
};
}
export interface ShoppingListItemCreate {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string;
labelId?: string;
unitId?: string;
extras?: {
[k: string]: unknown;
};
recipeReferences?: ShoppingListItemRecipeRefCreate[];
}
export interface ShoppingListItemRecipeRefCreate {
recipeId: string;
recipeQuantity?: number;
recipeScale?: number;
recipeNote?: string;
}
export interface ShoppingListItemOut {
quantity?: number;
unit?: IngredientUnit;
food?: IngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string;
labelId?: string;
unitId?: string;
extras?: {
[k: string]: unknown;
};
id: string;
label?: MultiPurposeLabelSummary;
recipeReferences?: ShoppingListItemRecipeRefOut[];
createdAt?: string;
updateAt?: string;
}
export interface ShoppingListItemRecipeRefOut {
recipeId: string;
recipeQuantity?: number;
recipeScale?: number;
recipeNote?: string;
id: string;
shoppingListItemId: string;
}
export interface ShoppingListItemRecipeRefUpdate {
recipeId: string;
recipeQuantity?: number;
recipeScale?: number;
recipeNote?: string;
id: string;
shoppingListItemId: string;
}
export interface ShoppingListItemUpdate {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string;
labelId?: string;
unitId?: string;
extras?: {
[k: string]: unknown;
};
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
}
/**
* Only used for bulk update operations where the shopping list item id isn't already supplied
*/
export interface ShoppingListItemUpdateBulk {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string;
labelId?: string;
unitId?: string;
extras?: {
[k: string]: unknown;
};
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
id: string;
}
/**
* Container for bulk shopping list item changes
*/
export interface ShoppingListItemsCollectionOut {
createdItems?: ShoppingListItemOut[];
updatedItems?: ShoppingListItemOut[];
deletedItems?: ShoppingListItemOut[];
}
export interface ShoppingListMultiPurposeLabelCreate {
shoppingListId: string;
labelId: string;
position?: number;
}
export interface ShoppingListMultiPurposeLabelOut {
shoppingListId: string;
labelId: string;
position?: number;
id: string;
label: MultiPurposeLabelSummary;
}
export interface ShoppingListMultiPurposeLabelUpdate {
shoppingListId: string;
labelId: string;
position?: number;
id: string;
}
export interface ShoppingListOut {
name?: string;
extras?: {
[k: string]: unknown;
};
createdAt?: string;
updateAt?: string;
groupId: string;
userId: string;
id: string;
listItems?: ShoppingListItemOut[];
recipeReferences: ShoppingListRecipeRefOut[];
labelSettings: ShoppingListMultiPurposeLabelOut[];
}
export interface ShoppingListRecipeRefOut {
id: string;
shoppingListId: string;
recipeId: string;
recipeQuantity: number;
recipe: RecipeSummary;
}
export interface RecipeSummary {
id?: string;
userId?: string;
groupId?: string;
name?: string;
slug?: string;
image?: unknown;
recipeYield?: string;
totalTime?: string;
prepTime?: string;
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
updateAt?: string;
lastMade?: string;
}
export interface RecipeCategory {
id?: string;
name: string;
slug: string;
}
export interface RecipeTag {
id?: string;
name: string;
slug: string;
}
export interface RecipeTool {
id: string;
name: string;
slug: string;
onHand?: boolean;
}
export interface ShoppingListRemoveRecipeParams {
recipeDecrementQuantity?: number;
}
export interface ShoppingListSave {
name?: string;
extras?: {
[k: string]: unknown;
};
createdAt?: string;
updateAt?: string;
groupId: string;
userId: string;
}
export interface ShoppingListSummary {
name?: string;
extras?: {
[k: string]: unknown;
};
createdAt?: string;
updateAt?: string;
groupId: string;
userId: string;
id: string;
recipeReferences: ShoppingListRecipeRefOut[];
labelSettings: ShoppingListMultiPurposeLabelOut[];
}
export interface ShoppingListUpdate {
name?: string;
extras?: {
[k: string]: unknown;
};
createdAt?: string;
updateAt?: string;
groupId: string;
userId: string;
id: string;
listItems?: ShoppingListItemOut[];
}
export interface RecipeIngredientBase {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
display?: string;
}

View file

@ -0,0 +1,664 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type GroupRecipeActionType = "link" | "post";
export type WebhookType = "mealplan";
export interface CreateGroupRecipeAction {
actionType: GroupRecipeActionType;
title: string;
url: string;
}
export interface CreateHouseholdPreferences {
privateHousehold?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
}
export interface CreateInviteToken {
uses: number;
}
export interface CreateWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
}
export interface EmailInitationResponse {
success: boolean;
error?: string | null;
}
export interface EmailInvitation {
email: string;
token: string;
}
export interface GroupEventNotifierCreate {
name: string;
appriseUrl?: string | null;
}
/**
* These events are in-sync with the EventTypes found in the EventBusService.
* If you modify this, make sure to update the EventBusService as well.
*/
export interface GroupEventNotifierOptions {
testMessage?: boolean;
webhookTask?: boolean;
recipeCreated?: boolean;
recipeUpdated?: boolean;
recipeDeleted?: boolean;
userSignup?: boolean;
dataMigrations?: boolean;
dataExport?: boolean;
dataImport?: boolean;
mealplanEntryCreated?: boolean;
shoppingListCreated?: boolean;
shoppingListUpdated?: boolean;
shoppingListDeleted?: boolean;
cookbookCreated?: boolean;
cookbookUpdated?: boolean;
cookbookDeleted?: boolean;
tagCreated?: boolean;
tagUpdated?: boolean;
tagDeleted?: boolean;
categoryCreated?: boolean;
categoryUpdated?: boolean;
categoryDeleted?: boolean;
}
export interface GroupEventNotifierOptionsOut {
testMessage?: boolean;
webhookTask?: boolean;
recipeCreated?: boolean;
recipeUpdated?: boolean;
recipeDeleted?: boolean;
userSignup?: boolean;
dataMigrations?: boolean;
dataExport?: boolean;
dataImport?: boolean;
mealplanEntryCreated?: boolean;
shoppingListCreated?: boolean;
shoppingListUpdated?: boolean;
shoppingListDeleted?: boolean;
cookbookCreated?: boolean;
cookbookUpdated?: boolean;
cookbookDeleted?: boolean;
tagCreated?: boolean;
tagUpdated?: boolean;
tagDeleted?: boolean;
categoryCreated?: boolean;
categoryUpdated?: boolean;
categoryDeleted?: boolean;
id: string;
}
export interface GroupEventNotifierOptionsSave {
testMessage?: boolean;
webhookTask?: boolean;
recipeCreated?: boolean;
recipeUpdated?: boolean;
recipeDeleted?: boolean;
userSignup?: boolean;
dataMigrations?: boolean;
dataExport?: boolean;
dataImport?: boolean;
mealplanEntryCreated?: boolean;
shoppingListCreated?: boolean;
shoppingListUpdated?: boolean;
shoppingListDeleted?: boolean;
cookbookCreated?: boolean;
cookbookUpdated?: boolean;
cookbookDeleted?: boolean;
tagCreated?: boolean;
tagUpdated?: boolean;
tagDeleted?: boolean;
categoryCreated?: boolean;
categoryUpdated?: boolean;
categoryDeleted?: boolean;
notifierId: string;
}
export interface GroupEventNotifierOut {
id: string;
name: string;
enabled: boolean;
groupId: string;
householdId: string;
options: GroupEventNotifierOptionsOut;
}
export interface GroupEventNotifierPrivate {
id: string;
name: string;
enabled: boolean;
groupId: string;
householdId: string;
options: GroupEventNotifierOptionsOut;
appriseUrl: string;
}
export interface GroupEventNotifierSave {
name: string;
appriseUrl?: string | null;
enabled?: boolean;
groupId: string;
householdId: string;
options?: GroupEventNotifierOptions;
}
export interface GroupEventNotifierUpdate {
name: string;
appriseUrl?: string | null;
enabled?: boolean;
groupId: string;
householdId: string;
options?: GroupEventNotifierOptions;
id: string;
}
export interface GroupRecipeActionOut {
actionType: GroupRecipeActionType;
title: string;
url: string;
groupId: string;
householdId: string;
id: string;
}
export interface HouseholdCreate {
groupId?: string | null;
name: string;
}
export interface HouseholdInDB {
groupId: string;
name: string;
id: string;
slug: string;
preferences?: ReadHouseholdPreferences | null;
group: string;
users?: HouseholdUserSummary[] | null;
webhooks?: ReadWebhook[];
}
export interface ReadHouseholdPreferences {
privateHousehold?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
id: string;
}
export interface HouseholdUserSummary {
id: string;
fullName: string;
}
export interface ReadWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
groupId: string;
householdId: string;
id: string;
}
export interface HouseholdSave {
groupId: string;
name: string;
}
export interface HouseholdStatistics {
totalRecipes: number;
totalUsers: number;
totalCategories: number;
totalTags: number;
totalTools: number;
}
export interface HouseholdSummary {
groupId: string;
name: string;
id: string;
slug: string;
preferences?: ReadHouseholdPreferences | null;
}
export interface ReadInviteToken {
token: string;
usesLeft: number;
groupId: string;
householdId: string;
}
export interface SaveGroupRecipeAction {
actionType: GroupRecipeActionType;
title: string;
url: string;
groupId: string;
householdId: string;
}
export interface SaveHouseholdPreferences {
privateHousehold?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
householdId: string;
}
export interface SaveInviteToken {
usesLeft: number;
groupId: string;
householdId: string;
token: string;
}
export interface SaveWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
groupId: string;
householdId: string;
}
export interface SetPermissions {
userId: string;
canManage?: boolean;
canInvite?: boolean;
canOrganize?: boolean;
}
export interface ShoppingListAddRecipeParams {
recipeIncrementQuantity?: number;
recipeIngredients?: RecipeIngredient[] | null;
}
export interface RecipeIngredient {
quantity?: number | null;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean | null;
disableAmount?: boolean;
display?: string;
title?: string | null;
originalText?: string | null;
referenceId?: string;
}
export interface IngredientUnit {
id: string;
name: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
} | null;
onHand?: boolean;
fraction?: boolean;
abbreviation?: string;
pluralAbbreviation?: string | null;
useAbbreviation?: boolean;
aliases?: IngredientUnitAlias[];
createdAt?: string | null;
updatedAt?: string | null;
}
export interface IngredientUnitAlias {
name: string;
[k: string]: unknown;
}
export interface CreateIngredientUnit {
id?: string | null;
name: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
} | null;
onHand?: boolean;
fraction?: boolean;
abbreviation?: string;
pluralAbbreviation?: string | null;
useAbbreviation?: boolean;
aliases?: CreateIngredientUnitAlias[];
[k: string]: unknown;
}
export interface CreateIngredientUnitAlias {
name: string;
[k: string]: unknown;
}
export interface IngredientFood {
id: string;
name: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
} | null;
onHand?: boolean;
labelId?: string | null;
aliases?: IngredientFoodAlias[];
label?: MultiPurposeLabelSummary | null;
createdAt?: string | null;
updatedAt?: string | null;
}
export interface IngredientFoodAlias {
name: string;
[k: string]: unknown;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
export interface CreateIngredientFood {
id?: string | null;
name: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
} | null;
onHand?: boolean;
labelId?: string | null;
aliases?: CreateIngredientFoodAlias[];
[k: string]: unknown;
}
export interface CreateIngredientFoodAlias {
name: string;
[k: string]: unknown;
}
export interface ShoppingListCreate {
name?: string | null;
extras?: {
[k: string]: unknown;
} | null;
createdAt?: string | null;
updatedAt?: string | null;
}
export interface ShoppingListItemBase {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean;
disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string | null;
labelId?: string | null;
unitId?: string | null;
extras?: {
[k: string]: unknown;
} | null;
}
export interface ShoppingListItemCreate {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean;
disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string | null;
labelId?: string | null;
unitId?: string | null;
extras?: {
[k: string]: unknown;
} | null;
id?: string | null;
recipeReferences?: ShoppingListItemRecipeRefCreate[];
}
export interface ShoppingListItemRecipeRefCreate {
recipeId: string;
recipeQuantity?: number;
recipeScale?: number | null;
recipeNote?: string | null;
}
export interface ShoppingListItemOut {
quantity?: number;
unit?: IngredientUnit | null;
food?: IngredientFood | null;
note?: string | null;
isFood?: boolean;
disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string | null;
labelId?: string | null;
unitId?: string | null;
extras?: {
[k: string]: unknown;
} | null;
id: string;
groupId: string;
householdId: string;
label?: MultiPurposeLabelSummary | null;
recipeReferences?: ShoppingListItemRecipeRefOut[];
createdAt?: string | null;
updatedAt?: string | null;
}
export interface ShoppingListItemRecipeRefOut {
recipeId: string;
recipeQuantity?: number;
recipeScale?: number | null;
recipeNote?: string | null;
id: string;
shoppingListItemId: string;
}
export interface ShoppingListItemRecipeRefUpdate {
recipeId: string;
recipeQuantity?: number;
recipeScale?: number | null;
recipeNote?: string | null;
id: string;
shoppingListItemId: string;
}
export interface ShoppingListItemUpdate {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean;
disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string | null;
labelId?: string | null;
unitId?: string | null;
extras?: {
[k: string]: unknown;
} | null;
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
}
/**
* Only used for bulk update operations where the shopping list item id isn't already supplied
*/
export interface ShoppingListItemUpdateBulk {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean;
disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
position?: number;
foodId?: string | null;
labelId?: string | null;
unitId?: string | null;
extras?: {
[k: string]: unknown;
} | null;
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
id: string;
}
/**
* Container for bulk shopping list item changes
*/
export interface ShoppingListItemsCollectionOut {
createdItems?: ShoppingListItemOut[];
updatedItems?: ShoppingListItemOut[];
deletedItems?: ShoppingListItemOut[];
}
export interface ShoppingListMultiPurposeLabelCreate {
shoppingListId: string;
labelId: string;
position?: number;
}
export interface ShoppingListMultiPurposeLabelOut {
shoppingListId: string;
labelId: string;
position?: number;
id: string;
label: MultiPurposeLabelSummary;
}
export interface ShoppingListMultiPurposeLabelUpdate {
shoppingListId: string;
labelId: string;
position?: number;
id: string;
}
export interface ShoppingListOut {
name?: string | null;
extras?: {
[k: string]: unknown;
} | null;
createdAt?: string | null;
updatedAt?: string | null;
groupId: string;
userId: string;
id: string;
listItems?: ShoppingListItemOut[];
householdId: string;
recipeReferences?: ShoppingListRecipeRefOut[];
labelSettings?: ShoppingListMultiPurposeLabelOut[];
}
export interface ShoppingListRecipeRefOut {
id: string;
shoppingListId: string;
recipeId: string;
recipeQuantity: number;
recipe: RecipeSummary;
}
export interface RecipeSummary {
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string | null;
slug?: string;
image?: unknown;
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;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTool {
id: string;
name: string;
slug: string;
onHand?: boolean;
}
export interface ShoppingListRemoveRecipeParams {
recipeDecrementQuantity?: number;
}
export interface ShoppingListSave {
name?: string | null;
extras?: {
[k: string]: unknown;
} | null;
createdAt?: string | null;
updatedAt?: string | null;
groupId: string;
userId: string;
}
export interface ShoppingListSummary {
name?: string | null;
extras?: {
[k: string]: unknown;
} | null;
createdAt?: string | null;
updatedAt?: string | null;
groupId: string;
userId: string;
id: string;
householdId: string;
recipeReferences: ShoppingListRecipeRefOut[];
labelSettings: ShoppingListMultiPurposeLabelOut[];
}
export interface ShoppingListUpdate {
name?: string | null;
extras?: {
[k: string]: unknown;
} | null;
createdAt?: string | null;
updatedAt?: string | null;
groupId: string;
userId: string;
id: string;
listItems?: ShoppingListItemOut[];
}
export interface UpdateHousehold {
groupId: string;
name: string;
id: string;
slug: string;
}
export interface UpdateHouseholdAdmin {
groupId: string;
name: string;
id: string;
preferences?: UpdateHouseholdPreferences | null;
}
export interface UpdateHouseholdPreferences {
privateHousehold?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
}
export interface RecipeIngredientBase {
quantity?: number | null;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean | null;
disableAmount?: boolean | null;
display?: string;
}

View file

@ -19,46 +19,18 @@ export interface CreatePlanEntry {
entryType?: PlanEntryType & string;
title?: string;
text?: string;
recipeId?: string;
recipeId?: string | null;
}
export interface CreateRandomEntry {
date: string;
entryType?: PlanEntryType & string;
}
export interface ListItem {
title?: string;
title?: string | null;
text?: string;
quantity?: number;
checked?: boolean;
}
export interface MealDayIn {
date?: string;
meals: MealIn[];
}
export interface MealIn {
slug?: string;
name?: string;
description?: string;
}
export interface MealDayOut {
date?: string;
meals: MealIn[];
id: number;
}
export interface MealPlanIn {
group: string;
startDate: string;
endDate: string;
planDays: MealDayIn[];
}
export interface MealPlanOut {
group: string;
startDate: string;
endDate: string;
planDays: MealDayIn[];
id: number;
shoppingList?: number;
}
export interface PlanRulesCreate {
day?: PlanRulesDay & string;
entryType?: PlanRulesType & string;
@ -76,6 +48,7 @@ export interface PlanRulesOut {
categories?: Category[];
tags?: Tag[];
groupId: string;
householdId: string;
id: string;
}
export interface PlanRulesSave {
@ -84,51 +57,56 @@ export interface PlanRulesSave {
categories?: Category[];
tags?: Tag[];
groupId: string;
householdId: string;
}
export interface ReadPlanEntry {
date: string;
entryType?: PlanEntryType & string;
title?: string;
text?: string;
recipeId?: string;
recipeId?: string | null;
id: number;
groupId: string;
userId?: string;
recipe?: RecipeSummary;
userId?: string | null;
householdId: string;
recipe?: RecipeSummary | null;
}
export interface RecipeSummary {
id?: string;
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string;
name?: string | null;
slug?: string;
image?: unknown;
recipeYield?: string;
totalTime?: string;
prepTime?: string;
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
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;
orgURL?: string;
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
updateAt?: string;
lastMade?: string;
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;
id?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTag {
id?: string;
id?: string | null;
name: string;
slug: string;
[k: string]: unknown;
}
export interface RecipeTool {
id: string;
@ -141,18 +119,18 @@ export interface SavePlanEntry {
entryType?: PlanEntryType & string;
title?: string;
text?: string;
recipeId?: string;
recipeId?: string | null;
groupId: string;
userId?: string;
userId?: string | null;
}
export interface ShoppingListIn {
name: string;
group?: string;
group?: string | null;
items: ListItem[];
}
export interface ShoppingListOut {
name: string;
group?: string;
group?: string | null;
items: ListItem[];
id: number;
}
@ -161,8 +139,8 @@ export interface UpdatePlanEntry {
entryType?: PlanEntryType & string;
title?: string;
text?: string;
recipeId?: string;
recipeId?: string | null;
id: number;
groupId: string;
userId?: string;
userId?: string | null;
}

View file

@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export interface OpenAIIngredient {
/**
*
* The input is simply the ingredient string you are processing as-is. It is forbidden to
* modify this at all, you must provide the input exactly as you received it.
*
*/
input: string;
/**
*
* This value is a float between 0 - 100, where 100 is full confidence that the result is correct,
* and 0 is no confidence that the result is correct. If you're unable to parse anything,
* and you put the entire string in the notes, you should return 0 confidence. If you can easily
* parse the string into each component, then you should return a confidence of 100. If you have to
* guess which part is the unit and which part is the food, your confidence should be lower, such as 60.
* Even if there is no unit or note, if you're able to determine the food, you may use a higher confidence.
* If the entire ingredient consists of only a food, you can use a confidence of 100.
*
*/
confidence?: number | null;
/**
*
* The numerical representation of how much of this ingredient. For instance, if you receive
* "3 1/2 grams of minced garlic", the quantity is "3 1/2". Quantity may be represented as a whole number
* (integer), a float or decimal, or a fraction. You should output quantity in only whole numbers or
* floats, converting fractions into floats. Floats longer than 10 decimal places should be
* rounded to 10 decimal places.
*
*/
quantity?: number | null;
/**
*
* The unit of measurement for this ingredient. For instance, if you receive
* "2 lbs chicken breast", the unit is "lbs" (short for "pounds").
*
*/
unit?: string | null;
/**
*
* The actual physical ingredient used in the recipe. For instance, if you receive
* "3 cups of onions, chopped", the food is "onions".
*
*/
food?: string | null;
/**
*
* The rest of the text that represents more detail on how to prepare the ingredient.
* Anything that is not one of the above should be the note. For instance, if you receive
* "one can of butter beans, drained" the note would be "drained". If you receive
* "3 cloves of garlic peeled and finely chopped", the note would be "peeled and finely chopped".
*
*/
note?: string | null;
}
export interface OpenAIIngredients {
ingredients?: OpenAIIngredient[];
}
export interface OpenAIBase {}

View file

@ -55,29 +55,32 @@ export interface CategorySave {
groupId: string;
}
export interface CreateIngredientFood {
id?: string | null;
name: string;
pluralName?: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
aliases?: CreateIngredientFoodAlias[];
} | null;
onHand?: boolean;
labelId?: string | null;
aliases?: CreateIngredientFoodAlias[];
}
export interface CreateIngredientFoodAlias {
name: string;
}
export interface CreateIngredientUnit {
id?: string | null;
name: string;
pluralName?: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
};
} | null;
onHand?: boolean;
fraction?: boolean;
abbreviation?: string;
pluralAbbreviation?: string;
pluralAbbreviation?: string | null;
useAbbreviation?: boolean;
aliases?: CreateIngredientUnitAlias[];
}
@ -89,16 +92,16 @@ export interface CreateRecipe {
}
export interface CreateRecipeBulk {
url: string;
categories?: RecipeCategory[];
tags?: RecipeTag[];
categories?: RecipeCategory[] | null;
tags?: RecipeTag[] | null;
}
export interface RecipeCategory {
id?: string;
id?: string | null;
name: string;
slug: string;
}
export interface RecipeTag {
id?: string;
id?: string | null;
name: string;
slug: string;
}
@ -116,27 +119,27 @@ export interface ExportRecipes {
exportType?: ExportTypes & string;
}
export interface IngredientConfidence {
average?: number;
comment?: number;
name?: number;
unit?: number;
quantity?: number;
food?: number;
average?: number | null;
comment?: number | null;
name?: number | null;
unit?: number | null;
quantity?: number | null;
food?: number | null;
}
export interface IngredientFood {
id: string;
name: string;
pluralName?: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
aliases?: IngredientFoodAlias[];
id: string;
label?: MultiPurposeLabelSummary;
createdAt?: string;
updateAt?: string;
} | null;
onHand?: boolean;
labelId?: string | null;
aliases?: IngredientFoodAlias[];
label?: MultiPurposeLabelSummary | null;
createdAt?: string | null;
updatedAt?: string | null;
}
export interface IngredientFoodAlias {
name: string;
@ -151,27 +154,28 @@ export interface MultiPurposeLabelSummary {
* A list of ingredient references.
*/
export interface IngredientReferences {
referenceId?: string;
referenceId?: string | null;
}
export interface IngredientRequest {
parser?: RegisteredParser & string;
ingredient: string;
}
export interface IngredientUnit {
id: string;
name: string;
pluralName?: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
};
} | null;
onHand?: boolean;
fraction?: boolean;
abbreviation?: string;
pluralAbbreviation?: string;
pluralAbbreviation?: string | null;
useAbbreviation?: boolean;
aliases?: IngredientUnitAlias[];
id: string;
createdAt?: string;
updateAt?: string;
createdAt?: string | null;
updatedAt?: string | null;
}
export interface IngredientUnitAlias {
name: string;
@ -189,64 +193,65 @@ export interface MergeUnit {
toUnit: string;
}
export interface Nutrition {
calories?: string;
fatContent?: string;
proteinContent?: string;
carbohydrateContent?: string;
fiberContent?: string;
sodiumContent?: string;
sugarContent?: string;
calories?: string | null;
fatContent?: string | null;
proteinContent?: string | null;
carbohydrateContent?: string | null;
fiberContent?: string | null;
sodiumContent?: string | null;
sugarContent?: string | null;
}
export interface ParsedIngredient {
input?: string;
input?: string | null;
confidence?: IngredientConfidence;
ingredient: RecipeIngredient;
}
export interface RecipeIngredient {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
quantity?: number | null;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean | null;
disableAmount?: boolean;
display?: string;
title?: string;
originalText?: string;
title?: string | null;
originalText?: string | null;
referenceId?: string;
}
export interface Recipe {
id?: string;
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string;
name?: string | null;
slug?: string;
image?: unknown;
recipeYield?: string;
totalTime?: string;
prepTime?: string;
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
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;
orgURL?: string;
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
updateAt?: string;
lastMade?: string;
rating?: number | null;
orgURL?: string | null;
dateAdded?: string | null;
dateUpdated?: string | null;
createdAt?: string | null;
updatedAt?: string | null;
lastMade?: string | null;
recipeIngredient?: RecipeIngredient[];
recipeInstructions?: RecipeStep[];
nutrition?: Nutrition;
settings?: RecipeSettings;
assets?: RecipeAsset[];
notes?: RecipeNote[];
recipeInstructions?: RecipeStep[] | null;
nutrition?: Nutrition | null;
settings?: RecipeSettings | null;
assets?: RecipeAsset[] | null;
notes?: RecipeNote[] | null;
extras?: {
[k: string]: unknown;
};
comments?: RecipeCommentOut[];
} | null;
comments?: RecipeCommentOut[] | null;
}
export interface RecipeTool {
id: string;
@ -255,15 +260,15 @@ export interface RecipeTool {
onHand?: boolean;
}
export interface RecipeStep {
id?: string;
title?: string;
id?: string | null;
title?: string | null;
text: string;
ingredientReferences?: IngredientReferences[];
}
export interface RecipeAsset {
name: string;
icon: string;
fileName?: string;
fileName?: string | null;
}
export interface RecipeNote {
title: string;
@ -274,13 +279,13 @@ export interface RecipeCommentOut {
text: string;
id: string;
createdAt: string;
updateAt: string;
updatedAt: string;
userId: string;
user: UserBase;
}
export interface UserBase {
id: string;
username?: string;
username?: string | null;
admin: boolean;
}
export interface RecipeCategoryResponse {
@ -290,28 +295,29 @@ export interface RecipeCategoryResponse {
recipes?: RecipeSummary[];
}
export interface RecipeSummary {
id?: string;
id?: string | null;
userId?: string;
householdId?: string;
groupId?: string;
name?: string;
name?: string | null;
slug?: string;
image?: unknown;
recipeYield?: string;
totalTime?: string;
prepTime?: string;
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
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;
orgURL?: string;
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
updateAt?: string;
lastMade?: string;
rating?: number | null;
orgURL?: string | null;
dateAdded?: string | null;
dateUpdated?: string | null;
createdAt?: string | null;
updatedAt?: string | null;
lastMade?: string | null;
}
export interface RecipeCommentCreate {
recipeId: string;
@ -327,15 +333,15 @@ export interface RecipeCommentUpdate {
text: string;
}
export interface RecipeDuplicate {
name?: string;
name?: string | null;
}
export interface RecipeIngredientBase {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
note?: string;
isFood?: boolean;
disableAmount?: boolean;
quantity?: number | null;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
isFood?: boolean | null;
disableAmount?: boolean | null;
display?: string;
}
export interface RecipeLastMade {
@ -379,17 +385,17 @@ export interface RecipeTimelineEventCreate {
userId: string;
subject: string;
eventType: TimelineEventType;
eventMessage?: string;
image?: TimelineEventImage & string;
eventMessage?: string | null;
image?: TimelineEventImage | null;
timestamp?: string;
}
export interface RecipeTimelineEventIn {
recipeId: string;
userId?: string;
userId?: string | null;
subject: string;
eventType: TimelineEventType;
eventMessage?: string;
image?: TimelineEventImage & string;
eventMessage?: string | null;
image?: TimelineEventImage | null;
timestamp?: string;
}
export interface RecipeTimelineEventOut {
@ -397,17 +403,19 @@ export interface RecipeTimelineEventOut {
userId: string;
subject: string;
eventType: TimelineEventType;
eventMessage?: string;
image?: TimelineEventImage & string;
eventMessage?: string | null;
image?: TimelineEventImage | null;
timestamp?: string;
id: string;
groupId: string;
householdId: string;
createdAt: string;
updateAt: string;
updatedAt: string;
}
export interface RecipeTimelineEventUpdate {
subject: string;
eventMessage?: string;
image?: TimelineEventImage;
eventMessage?: string | null;
image?: TimelineEventImage | null;
}
export interface RecipeToolCreate {
name: string;
@ -435,26 +443,30 @@ export interface RecipeZipTokenResponse {
token: string;
}
export interface SaveIngredientFood {
id?: string | null;
name: string;
pluralName?: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
} | null;
onHand?: boolean;
labelId?: string | null;
aliases?: CreateIngredientFoodAlias[];
groupId: string;
}
export interface SaveIngredientUnit {
id?: string | null;
name: string;
pluralName?: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
};
} | null;
onHand?: boolean;
fraction?: boolean;
abbreviation?: string;
pluralAbbreviation?: string;
pluralAbbreviation?: string | null;
useAbbreviation?: boolean;
aliases?: CreateIngredientUnitAlias[];
groupId: string;
@ -465,8 +477,9 @@ export interface ScrapeRecipe {
}
export interface ScrapeRecipeTest {
url: string;
useOpenAI?: boolean;
}
export interface SlugResponse { }
export interface SlugResponse {}
export interface TagIn {
name: string;
}
@ -481,12 +494,14 @@ export interface TagSave {
groupId: string;
}
export interface UnitFoodBase {
id?: string | null;
name: string;
pluralName?: string;
pluralName?: string | null;
description?: string;
extras?: {
[k: string]: unknown;
};
} | null;
onHand?: boolean;
}
export interface UpdateImageResponse {
image: string;

View file

@ -11,7 +11,7 @@ export type OrderDirection = "asc" | "desc";
export interface ErrorResponse {
message: string;
error?: boolean;
exception?: string;
exception?: string | null;
}
export interface FileTokenResponse {
fileToken: string;
@ -19,19 +19,19 @@ export interface FileTokenResponse {
export interface PaginationQuery {
page?: number;
perPage?: number;
orderBy?: string;
orderByNullPosition?: OrderByNullPosition;
orderBy?: string | null;
orderByNullPosition?: OrderByNullPosition | null;
orderDirection?: OrderDirection & string;
queryFilter?: string;
paginationSeed?: string;
queryFilter?: string | null;
paginationSeed?: string | null;
}
export interface RecipeSearchQuery {
cookbook?: string;
cookbook?: string | null;
requireAllCategories?: boolean;
requireAllTags?: boolean;
requireAllTools?: boolean;
requireAllFoods?: boolean;
search?: string;
search?: string | null;
}
export interface SuccessResponse {
message: string;

View file

@ -19,8 +19,9 @@ export interface CreateToken {
token: string;
}
export interface CreateUserRegistration {
group?: string;
groupToken?: string;
group?: string | null;
household?: string | null;
groupToken?: string | null;
email: string;
username: string;
fullName: string;
@ -45,21 +46,19 @@ export interface ForgotPassword {
export interface GroupBase {
name: string;
}
export interface GroupHouseholdSummary {
id: string;
name: string;
}
export interface GroupInDB {
name: string;
id: string;
slug: string;
categories?: CategoryBase[];
categories?: CategoryBase[] | null;
webhooks?: ReadWebhook[];
users?: UserOut[];
preferences?: ReadGroupPreferences;
}
export interface GroupSummary {
name: string;
id: string;
slug: string;
preferences?: ReadGroupPreferences;
households?: GroupHouseholdSummary[] | null;
users?: UserSummary[] | null;
preferences?: ReadGroupPreferences | null;
}
export interface CategoryBase {
name: string;
@ -73,43 +72,24 @@ export interface ReadWebhook {
webhookType?: WebhookType & string;
scheduledTime: string;
groupId: string;
householdId: string;
id: string;
}
export interface UserOut {
export interface UserSummary {
id: string;
username?: string;
fullName?: string;
email: string;
authMethod?: AuthMethod & string;
admin?: boolean;
group: string;
advanced?: boolean;
canInvite?: boolean;
canManage?: boolean;
canOrganize?: boolean;
groupId: string;
groupSlug: string;
tokens?: LongLiveTokenOut[];
cacheKey: string;
}
export interface LongLiveTokenOut {
token: string;
name: string;
id: number;
createdAt?: string;
fullName: string;
}
export interface ReadGroupPreferences {
privateGroup?: boolean;
firstDayOfWeek?: number;
recipePublic?: boolean;
recipeShowNutrition?: boolean;
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
recipeDisableAmount?: boolean;
groupId: string;
id: string;
}
export interface GroupSummary {
name: string;
id: string;
slug: string;
preferences?: ReadGroupPreferences | null;
}
export interface LongLiveTokenIn {
name: string;
integrationId?: string;
@ -124,23 +104,32 @@ export interface LongLiveTokenInDB {
}
export interface PrivateUser {
id: string;
username?: string;
fullName?: string;
username?: string | null;
fullName?: string | null;
email: string;
authMethod?: AuthMethod & string;
admin?: boolean;
group: string;
household: string;
advanced?: boolean;
canInvite?: boolean;
canManage?: boolean;
canOrganize?: boolean;
groupId: string;
groupSlug: string;
tokens?: LongLiveTokenOut[];
householdId: string;
householdSlug: string;
tokens?: LongLiveTokenOut[] | null;
cacheKey: string;
password: string;
loginAttemps?: number;
lockedAt?: string;
lockedAt?: string | null;
}
export interface LongLiveTokenOut {
token: string;
name: string;
id: number;
createdAt?: string | null;
}
export interface OIDCRequest {
id_token: string;
@ -168,8 +157,8 @@ export interface Token {
token_type: string;
}
export interface TokenData {
user_id?: string;
username?: string;
user_id?: string | null;
username?: string | null;
}
export interface UnlockResults {
unlocked?: number;
@ -178,7 +167,7 @@ export interface UpdateGroup {
name: string;
id: string;
slug: string;
categories?: CategoryBase[];
categories?: CategoryBase[] | null;
webhooks?: CreateWebhook[];
}
export interface CreateWebhook {
@ -189,53 +178,75 @@ export interface CreateWebhook {
scheduledTime: string;
}
export interface UserBase {
id?: string;
username?: string;
fullName?: string;
id?: string | null;
username?: string | null;
fullName?: string | null;
email: string;
authMethod?: AuthMethod & string;
admin?: boolean;
group?: string;
group?: string | null;
household?: string | null;
advanced?: boolean;
canInvite?: boolean;
canManage?: boolean;
canOrganize?: boolean;
}
export interface UserIn {
id?: string;
username?: string;
fullName?: string;
id?: string | null;
username?: string | null;
fullName?: string | null;
email: string;
authMethod?: AuthMethod & string;
admin?: boolean;
group?: string;
group?: string | null;
household?: string | null;
advanced?: boolean;
canInvite?: boolean;
canManage?: boolean;
canOrganize?: boolean;
password: string;
}
export interface UserOut {
id: string;
username?: string | null;
fullName?: string | null;
email: string;
authMethod?: AuthMethod & string;
admin?: boolean;
group: string;
household: string;
advanced?: boolean;
canInvite?: boolean;
canManage?: boolean;
canOrganize?: boolean;
groupId: string;
groupSlug: string;
householdId: string;
householdSlug: string;
tokens?: LongLiveTokenOut[] | null;
cacheKey: string;
}
export interface UserRatingCreate {
recipeId: string;
rating?: number;
rating?: number | null;
isFavorite?: boolean;
userId: string;
}
export interface UserRatingOut {
recipeId: string;
rating?: number;
rating?: number | null;
isFavorite?: boolean;
userId: string;
id: string;
}
export interface UserRatingSummary {
recipeId: string;
rating?: number;
rating?: number | null;
isFavorite?: boolean;
}
export interface UserSummary {
id: string;
fullName: string;
export interface UserRatingUpdate {
rating?: number | null;
isFavorite?: boolean | null;
}
export interface ValidateResetToken {
token: string;

View file

@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
import { EmailInitationResponse, EmailInvitation } from "~/lib/api/types/group";
import { EmailInitationResponse, EmailInvitation } from "~/lib/api/types/household";
import { ForgotPassword } from "~/lib/api/types/user";
import { EmailTest } from "~/lib/api/types/admin";
@ -7,7 +7,7 @@ const routes = {
base: "/api/admin/email",
forgotPassword: "/api/users/forgot-password",
invitation: "/api/groups/invitations/email",
invitation: "/api/households/invitations/email",
};
export class EmailAPI extends BaseAPI {

View file

@ -4,8 +4,8 @@ import { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/lib/api/types/
const prefix = "/api";
const routes = {
cookbooks: `${prefix}/groups/cookbooks`,
cookbooksId: (id: number) => `${prefix}/groups/cookbooks/${id}`,
cookbooks: `${prefix}/households/cookbooks`,
cookbooksId: (id: number) => `${prefix}/households/cookbooks/${id}`,
};
export class CookbookAPI extends BaseCRUDAPI<CreateCookBook, RecipeCookBook, UpdateCookBook> {

View file

@ -1,11 +1,11 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { GroupEventNotifierCreate, GroupEventNotifierOut, GroupEventNotifierUpdate } from "~/lib/api/types/group";
import { GroupEventNotifierCreate, GroupEventNotifierOut, GroupEventNotifierUpdate } from "~/lib/api/types/household";
const prefix = "/api";
const routes = {
eventNotifier: `${prefix}/groups/events/notifications`,
eventNotifierId: (id: string | number) => `${prefix}/groups/events/notifications/${id}`,
eventNotifier: `${prefix}/households/events/notifications`,
eventNotifierId: (id: string | number) => `${prefix}/households/events/notifications/${id}`,
};
export class GroupEventNotifierApi extends BaseCRUDAPI<

View file

@ -4,8 +4,8 @@ import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
const prefix = "/api";
const routes = {
rule: `${prefix}/groups/mealplans/rules`,
ruleId: (id: string | number) => `${prefix}/groups/mealplans/rules/${id}`,
rule: `${prefix}/households/mealplans/rules`,
ruleId: (id: string | number) => `${prefix}/households/mealplans/rules/${id}`,
};
export class MealPlanRulesApi extends BaseCRUDAPI<PlanRulesCreate, PlanRulesOut> {

View file

@ -4,9 +4,9 @@ import { CreatePlanEntry, CreateRandomEntry, ReadPlanEntry, UpdatePlanEntry } fr
const prefix = "/api";
const routes = {
mealplan: `${prefix}/groups/mealplans`,
random: `${prefix}/groups/mealplans/random`,
mealplanId: (id: string | number) => `${prefix}/groups/mealplans/${id}`,
mealplan: `${prefix}/households/mealplans`,
random: `${prefix}/households/mealplans/random`,
mealplanId: (id: string | number) => `${prefix}/households/mealplans/${id}`,
};
export class MealPlanAPI extends BaseCRUDAPI<CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry> {

View file

@ -1,11 +1,11 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/group";
import { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/household";
const prefix = "/api";
const routes = {
groupRecipeActions: `${prefix}/groups/recipe-actions`,
groupRecipeActionsId: (id: string | number) => `${prefix}/groups/recipe-actions/${id}`,
groupRecipeActions: `${prefix}/households/recipe-actions`,
groupRecipeActionsId: (id: string | number) => `${prefix}/households/recipe-actions/${id}`,
};
export class GroupRecipeActionsAPI extends BaseCRUDAPI<CreateGroupRecipeAction, GroupRecipeActionOut> {

View file

@ -9,20 +9,20 @@ import {
ShoppingListMultiPurposeLabelUpdate,
ShoppingListOut,
ShoppingListUpdate,
} from "~/lib/api/types/group";
} from "~/lib/api/types/household";
const prefix = "/api";
const routes = {
shoppingLists: `${prefix}/groups/shopping/lists`,
shoppingListsId: (id: string) => `${prefix}/groups/shopping/lists/${id}`,
shoppingListIdAddRecipe: (id: string, recipeId: string) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}`,
shoppingListIdRemoveRecipe: (id: string, recipeId: string) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}/delete`,
shoppingListIdUpdateLabelSettings: (id: string) => `${prefix}/groups/shopping/lists/${id}/label-settings`,
shoppingLists: `${prefix}/households/shopping/lists`,
shoppingListsId: (id: string) => `${prefix}/households/shopping/lists/${id}`,
shoppingListIdAddRecipe: (id: string, recipeId: string) => `${prefix}/households/shopping/lists/${id}/recipe/${recipeId}`,
shoppingListIdRemoveRecipe: (id: string, recipeId: string) => `${prefix}/households/shopping/lists/${id}/recipe/${recipeId}/delete`,
shoppingListIdUpdateLabelSettings: (id: string) => `${prefix}/households/shopping/lists/${id}/label-settings`,
shoppingListItems: `${prefix}/groups/shopping/items`,
shoppingListItemsCreateBulk: `${prefix}/groups/shopping/items/create-bulk`,
shoppingListItemsId: (id: string) => `${prefix}/groups/shopping/items/${id}`,
shoppingListItems: `${prefix}/households/shopping/items`,
shoppingListItemsCreateBulk: `${prefix}/households/shopping/items/create-bulk`,
shoppingListItemsId: (id: string) => `${prefix}/households/shopping/items/${id}`,
};
export class ShoppingListsApi extends BaseCRUDAPI<ShoppingListCreate, ShoppingListOut, ShoppingListUpdate> {

View file

@ -1,12 +1,12 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreateWebhook, ReadWebhook } from "~/lib/api/types/group";
import { CreateWebhook, ReadWebhook } from "~/lib/api/types/household";
const prefix = "/api";
const routes = {
webhooks: `${prefix}/groups/webhooks`,
webhooksId: (id: string | number) => `${prefix}/groups/webhooks/${id}`,
webhooksIdTest: (id: string | number) => `${prefix}/groups/webhooks/${id}/test`,
webhooks: `${prefix}/households/webhooks`,
webhooksId: (id: string | number) => `${prefix}/households/webhooks/${id}`,
webhooksIdTest: (id: string | number) => `${prefix}/households/webhooks/${id}/test`,
};
export class WebhooksAPI extends BaseCRUDAPI<CreateWebhook, ReadWebhook> {

View file

@ -1,13 +1,10 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CategoryBase, GroupBase, GroupInDB, GroupSummary, UserOut } from "~/lib/api/types/user";
import { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user";
import { HouseholdSummary } from "~/lib/api/types/household";
import {
CreateInviteToken,
GroupAdminUpdate,
GroupStatistics,
GroupStorage,
ReadGroupPreferences,
ReadInviteToken,
SetPermissions,
UpdateGroupPreferences,
} from "~/lib/api/types/group";
@ -16,16 +13,14 @@ const prefix = "/api";
const routes = {
groups: `${prefix}/admin/groups`,
groupsSelf: `${prefix}/groups/self`,
categories: `${prefix}/groups/categories`,
members: `${prefix}/groups/members`,
permissions: `${prefix}/groups/permissions`,
preferences: `${prefix}/groups/preferences`,
statistics: `${prefix}/groups/statistics`,
storage: `${prefix}/groups/storage`,
invitation: `${prefix}/groups/invitations`,
households: `${prefix}/households`,
membersHouseholdId: (householdId: string | number | null) => {
return householdId ?
`${prefix}/households/members?householdId=${householdId}` :
`${prefix}/groups/members`;
},
groupsId: (id: string | number) => `${prefix}/admin/groups/${id}`,
};
@ -38,14 +33,6 @@ export class GroupAPI extends BaseCRUDAPI<GroupBase, GroupInDB, GroupAdminUpdate
return await this.requests.get<GroupSummary>(routes.groupsSelf);
}
async getCategories() {
return await this.requests.get<CategoryBase[]>(routes.categories);
}
async setCategories(payload: CategoryBase[]) {
return await this.requests.put<CategoryBase[]>(routes.categories, payload);
}
async getPreferences() {
return await this.requests.get<ReadGroupPreferences>(routes.preferences);
}
@ -55,21 +42,12 @@ export class GroupAPI extends BaseCRUDAPI<GroupBase, GroupInDB, GroupAdminUpdate
return await this.requests.put<ReadGroupPreferences, UpdateGroupPreferences>(routes.preferences, payload);
}
async createInvitation(payload: CreateInviteToken) {
return await this.requests.post<ReadInviteToken>(routes.invitation, payload);
async fetchMembers(householdId: string | number | null = null) {
return await this.requests.get<UserSummary[]>(routes.membersHouseholdId(householdId));
}
async fetchMembers() {
return await this.requests.get<UserOut[]>(routes.members);
}
async setMemberPermissions(payload: SetPermissions) {
// TODO: This should probably be a patch request, which isn't offered by the API currently
return await this.requests.put<UserOut, SetPermissions>(routes.permissions, payload);
}
async statistics() {
return await this.requests.get<GroupStatistics>(routes.statistics);
async fetchHouseholds() {
return await this.requests.get<HouseholdSummary[]>(routes.households);
}
async storage() {

View file

@ -0,0 +1,64 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { UserOut } from "~/lib/api/types/user";
import {
HouseholdCreate,
HouseholdInDB,
UpdateHouseholdAdmin,
HouseholdStatistics,
ReadHouseholdPreferences,
SetPermissions,
UpdateHouseholdPreferences,
CreateInviteToken,
ReadInviteToken,
} from "~/lib/api/types/household";
const prefix = "/api";
const routes = {
households: `${prefix}/admin/households`,
householdsSelf: `${prefix}/households/self`,
members: `${prefix}/households/members`,
permissions: `${prefix}/households/permissions`,
preferences: `${prefix}/households/preferences`,
statistics: `${prefix}/households/statistics`,
invitation: `${prefix}/households/invitations`,
householdsId: (id: string | number) => `${prefix}/admin/households/${id}`,
};
export class HouseholdAPI extends BaseCRUDAPI<HouseholdCreate, HouseholdInDB, UpdateHouseholdAdmin> {
baseRoute = routes.households;
itemRoute = routes.householdsId;
/** Returns the Group Data for the Current User
*/
async getCurrentUserHousehold() {
return await this.requests.get<HouseholdInDB>(routes.householdsSelf);
}
async getPreferences() {
return await this.requests.get<ReadHouseholdPreferences>(routes.preferences);
}
async setPreferences(payload: UpdateHouseholdPreferences) {
// TODO: This should probably be a patch request, which isn't offered by the API currently
return await this.requests.put<ReadHouseholdPreferences, UpdateHouseholdPreferences>(routes.preferences, payload);
}
async createInvitation(payload: CreateInviteToken) {
return await this.requests.post<ReadInviteToken>(routes.invitation, payload);
}
async fetchMembers() {
return await this.requests.get<UserOut[]>(routes.members);
}
async setMemberPermissions(payload: SetPermissions) {
// TODO: This should probably be a patch request, which isn't offered by the API currently
return await this.requests.put<UserOut, SetPermissions>(routes.permissions, payload);
}
async statistics() {
return await this.requests.get<HouseholdStatistics>(routes.statistics);
}
}

View file

@ -1,6 +1,4 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { QueryValue, route } from "~/lib/api/base/route";
import { PaginationData } from "~/lib/api/types/non-generated";
import {
ChangePassword,
DeleteTokenResponse,
@ -12,7 +10,6 @@ import {
UserOut,
UserRatingOut,
UserRatingSummary,
UserSummary,
} from "~/lib/api/types/user";
export interface UserRatingsSummaries {
@ -26,7 +23,6 @@ export interface UserRatingsOut {
const prefix = "/api";
const routes = {
groupUsers: `${prefix}/users/group-users`,
usersSelf: `${prefix}/users/self`,
ratingsSelf: `${prefix}/users/self/ratings`,
passwordReset: `${prefix}/users/reset-password`,
@ -51,10 +47,6 @@ export class UserApi extends BaseCRUDAPI<UserIn, UserOut, UserBase> {
baseRoute: string = routes.users;
itemRoute = (itemid: string) => routes.usersId(itemid);
async getGroupUsers(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
return await this.requests.get<PaginationData<UserSummary>>(route(routes.groupUsers, { page, perPage, ...params }));
}
async addFavorite(id: string, slug: string) {
return await this.requests.post(routes.usersIdFavoritesSlug(id, slug), {});
}

View file

@ -17,6 +17,7 @@ import {
mdiAccountGroup,
mdiSlotMachine,
mdiHome,
mdiHomeAccount,
mdiMagnify,
mdiPotSteamOutline,
mdiTranslate,
@ -226,6 +227,7 @@ export const icons = {
heart: mdiHeart,
heartOutline: mdiHeartOutline,
home: mdiHome,
household: mdiHomeAccount,
import: mdiImport,
information: mdiInformation,
informationVariant: mdiInformationVariant,

View file

@ -477,7 +477,7 @@ export default {
"name": "Meal Planner",
"short_name": "Meal Planner",
"description": "Open the meal planner",
"url": "/group/mealplan/planner/view",
"url": "/household/mealplan/planner/view",
"icons": [
{
"src": "/icons/mdiCalendarMultiselect-192x192.png",

View file

@ -60,7 +60,7 @@
<i18n path="settings.backup.experimental-description" />
</v-card-text>
</BaseCardSectionTitle>
<v-toolbar color="background" flat class="justify-between">
<v-toolbar color="transparent" flat class="justify-between">
<BaseButton class="mr-2" @click="createBackup"> {{ $t("settings.backup.create-heading") }} </BaseButton>
<AppButtonUpload
:text-btn="false"

View file

@ -1,15 +1,14 @@
// TODO: Edit Group
<template>
<v-container fluid>
<BaseDialog
v-model="createDialog"
:title="$t('group.create-group')"
:icon="$globals.icons.group"
@submit="createGroup(createUserForm.data)"
@submit="createGroup(createGroupForm.data)"
>
<template #activator> </template>
<v-card-text>
<AutoForm v-model="createUserForm.data" :update-mode="updateMode" :items="createUserForm.items" />
<AutoForm v-model="createGroupForm.data" :update-mode="updateMode" :items="createGroupForm.items" />
</v-card-text>
</BaseDialog>
@ -27,7 +26,7 @@
<BaseCardSectionTitle :title="$tc('group.group-management')"> </BaseCardSectionTitle>
<section>
<v-toolbar flat color="background" class="justify-between">
<v-toolbar flat color="transparent" class="justify-between">
<BaseButton @click="openDialog"> {{ $t("general.create") }} </BaseButton>
</v-toolbar>
@ -41,15 +40,15 @@
:search="search"
@click:row="handleRowClick"
>
<template #item.households="{ item }">
{{ item.households.length }}
</template>
<template #item.users="{ item }">
{{ item.users.length }}
</template>
<template #item.webhookEnable="{ item }">
{{ item.webhooks.length > 0 ? $t("general.yes") : $t("general.no") }}
</template>
<template #item.actions="{ item }">
<v-btn
:disabled="item && item.users.length > 0"
:disabled="item && (item.households.length > 0 || item.users.length > 0)"
class="mr-1"
icon
color="error"
@ -94,12 +93,12 @@ export default defineComponent({
value: "id",
},
{ text: i18n.t("general.name"), value: "name" },
{ text: i18n.t("group.total-households"), value: "households" },
{ text: i18n.t("user.total-users"), value: "users" },
{ text: i18n.t("user.webhooks-enabled"), value: "webhookEnable" },
{ text: i18n.t("general.delete"), value: "actions" },
],
updateMode: false,
createUserForm: {
createGroupForm: {
items: [
{
label: i18n.t("group.group-name"),
@ -116,7 +115,7 @@ export default defineComponent({
function openDialog() {
state.createDialog = true;
state.createUserForm.data.name = "";
state.createGroupForm.data.name = "";
}
const router = useRouter();

View file

@ -0,0 +1,117 @@
<template>
<v-container v-if="household" class="narrow-container">
<BasePageTitle>
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-group-settings.svg')"></v-img>
</template>
<template #title> {{ $t('household.admin-household-management') }} </template>
{{ $t('household.admin-household-management-text') }}
</BasePageTitle>
<AppToolbar back> </AppToolbar>
<v-card-text> {{ $t('household.household-id-value', [household.id]) }} </v-card-text>
<v-form v-if="!userError" ref="refHouseholdEditForm" @submit.prevent="handleSubmit">
<v-card outlined>
<v-card-text>
<v-select
v-if="groups"
v-model="household.groupId"
disabled
:items="groups"
rounded
class="rounded-lg"
item-text="name"
item-value="id"
:return-object="false"
filled
:label="$tc('group.user-group')"
:rules="[validators.required]"
/>
<v-text-field
v-model="household.name"
:label="$t('household.household-name')"
:rules="[validators.required]"
/>
<HouseholdPreferencesEditor v-if="household.preferences" v-model="household.preferences" />
</v-card-text>
</v-card>
<div class="d-flex pa-2">
<BaseButton type="submit" edit class="ml-auto"> {{ $t("general.update") }}</BaseButton>
</div>
</v-form>
</v-container>
</template>
<script lang="ts">
import { defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api";
import HouseholdPreferencesEditor from "~/components/Domain/Household/HouseholdPreferencesEditor.vue";
import { useGroups } from "~/composables/use-groups";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { validators } from "~/composables/use-validators";
import { HouseholdInDB } from "~/lib/api/types/household";
import { VForm } from "~/types/vuetify";
export default defineComponent({
components: {
HouseholdPreferencesEditor,
},
layout: "admin",
setup() {
const route = useRoute();
const { i18n } = useContext();
const { groups } = useGroups();
const householdId = route.value.params.id;
// ==============================================
// New User Form
const refHouseholdEditForm = ref<VForm | null>(null);
const userApi = useUserApi();
const household = ref<HouseholdInDB | null>(null);
const userError = ref(false);
onMounted(async () => {
const { data, error } = await userApi.households.getOne(householdId);
if (error?.response?.status === 404) {
alert.error(i18n.tc("user.user-not-found"));
userError.value = true;
}
if (data) {
household.value = data;
}
});
async function handleSubmit() {
if (!refHouseholdEditForm.value?.validate() || household.value === null) {
return;
}
const { response, data } = await userApi.households.updateOne(household.value.id, household.value);
if (response?.status === 200 && data) {
if (household.value.slug !== data.slug) {
// the slug updated, which invalidates the nav URLs
window.location.reload();
}
household.value = data;
} else {
alert.error(i18n.tc("settings.settings-update-failed"));
}
}
return {
groups,
household,
validators,
userError,
refHouseholdEditForm,
handleSubmit,
};
},
});
</script>

View file

@ -0,0 +1,167 @@
<template>
<v-container fluid>
<BaseDialog
v-model="createDialog"
:title="$t('household.create-household')"
:icon="$globals.icons.household"
@submit="createHousehold(createHouseholdForm.data)"
>
<template #activator> </template>
<v-card-text>
<v-select
v-if="groups"
v-model="createHouseholdForm.data.groupId"
:items="groups"
rounded
class="rounded-lg"
item-text="name"
item-value="id"
:return-object="false"
filled
:label="$tc('household.household-group')"
:rules="[validators.required]"
/>
<AutoForm v-model="createHouseholdForm.data" :update-mode="updateMode" :items="createHouseholdForm.items" />
</v-card-text>
</BaseDialog>
<BaseDialog
v-model="confirmDialog"
:title="$t('general.confirm')"
color="error"
@confirm="deleteHousehold(deleteTarget)"
>
<template #activator> </template>
<v-card-text>
{{ $t("general.confirm-delete-generic") }}
</v-card-text>
</BaseDialog>
<BaseCardSectionTitle :title="$tc('household.household-management')"> </BaseCardSectionTitle>
<section>
<v-toolbar flat color="transparent" class="justify-between">
<BaseButton @click="openDialog"> {{ $t("general.create") }} </BaseButton>
</v-toolbar>
<v-data-table
:headers="headers"
:items="households || []"
item-key="id"
class="elevation-0"
hide-default-footer
disable-pagination
:search="search"
@click:row="handleRowClick"
>
<template #item.users="{ item }">
{{ item.users.length }}
</template>
<template #item.group="{ item }">
{{ item.group }}
</template>
<template #item.webhookEnable="{ item }">
{{ item.webhooks.length > 0 ? $t("general.yes") : $t("general.no") }}
</template>
<template #item.actions="{ item }">
<v-btn
:disabled="item && item.users.length > 0"
class="mr-1"
icon
color="error"
@click.stop="
confirmDialog = true;
deleteTarget = item.id;
"
>
<v-icon>
{{ $globals.icons.delete }}
</v-icon>
</v-btn>
</template>
</v-data-table>
<v-divider></v-divider>
</section>
</v-container>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api";
import { fieldTypes } from "~/composables/forms";
import { useGroups } from "~/composables/use-groups";
import { useHouseholds } from "~/composables/use-households";
import { validators } from "~/composables/use-validators";
import { HouseholdInDB } from "~/lib/api/types/household";
export default defineComponent({
layout: "admin",
setup() {
const { i18n } = useContext();
const { groups } = useGroups();
const { households, refreshAllHouseholds, deleteHousehold, createHousehold } = useHouseholds();
const state = reactive({
createDialog: false,
confirmDialog: false,
deleteTarget: 0,
search: "",
headers: [
{
text: i18n.t("household.household"),
align: "start",
sortable: false,
value: "id",
},
{ text: i18n.t("general.name"), value: "name" },
{ text: i18n.t("group.group"), value: "group" },
{ text: i18n.t("user.total-users"), value: "users" },
{ text: i18n.t("user.webhooks-enabled"), value: "webhookEnable" },
{ text: i18n.t("general.delete"), value: "actions" },
],
updateMode: false,
createHouseholdForm: {
items: [
{
label: i18n.t("household.household-name"),
varName: "name",
type: fieldTypes.TEXT,
rules: ["required"],
},
],
data: {
groupId: "",
name: "",
},
},
});
function openDialog() {
state.createDialog = true;
state.createHouseholdForm.data.name = "";
state.createHouseholdForm.data.groupId = "";
}
const router = useRouter();
function handleRowClick(item: HouseholdInDB) {
router.push(`/admin/manage/households/${item.id}`);
}
return {
...toRefs(state),
groups,
households,
validators,
refreshAllHouseholds,
deleteHousehold,
createHousehold,
openDialog,
handleRowClick,
};
},
head() {
return {
title: this.$t("household.manage-households") as string,
};
},
});
</script>

View file

@ -14,9 +14,11 @@
<div class="d-flex">
<p> {{ $t("user.user-id-with-value", {id: user.id} ) }}</p>
</div>
<!-- This is disabled since we can't properly handle changing the user's group in most scenarios -->
<v-select
v-if="groups"
v-model="user.group"
disabled
:items="groups"
rounded
class="rounded-lg"
@ -26,7 +28,20 @@
filled
:label="$tc('group.user-group')"
:rules="[validators.required]"
></v-select>
/>
<v-select
v-if="households"
v-model="user.household"
:items="households"
rounded
class="rounded-lg"
item-text="name"
item-value="name"
:return-object="false"
filled
:label="$tc('household.user-household')"
:rules="[validators.required]"
/>
<div class="d-flex py-2 pr-2">
<BaseButton type="button" :loading="generatingToken" create @click.prevent="handlePasswordReset">
{{ $t("user.generate-password-reset-link") }}
@ -65,6 +80,7 @@
import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api";
import { useAdminApi, useUserApi } from "~/composables/api";
import { useGroups } from "~/composables/use-groups";
import { useHouseholds } from "~/composables/use-households";
import { alert } from "~/composables/use-toast";
import { useUserForm } from "~/composables/use-users";
import { validators } from "~/composables/use-validators";
@ -76,6 +92,7 @@ export default defineComponent({
setup() {
const { userForm } = useUserForm();
const { groups } = useGroups();
const { useHouseholdsInGroup } = useHouseholds();
const { i18n } = useContext();
const route = useRoute();
@ -89,6 +106,8 @@ export default defineComponent({
const adminApi = useAdminApi();
const user = ref<UserOut | null>(null);
const households = useHouseholdsInGroup(computed(() => user.value?.groupId || ""));
const disabledFields = computed(() => {
return user.value?.authMethod !== "Mealie" ? ["admin"] : [];
})
@ -154,6 +173,7 @@ export default defineComponent({
refNewUserForm,
handleSubmit,
groups,
households,
validators,
handlePasswordReset,
resetUrl,

View file

@ -12,17 +12,30 @@
<v-card-text>
<v-select
v-if="groups"
v-model="newUserData.group"
v-model="selectedGroupId"
:items="groups"
rounded
class="rounded-lg"
item-text="name"
item-value="id"
:return-object="false"
filled
:label="$t('group.user-group')"
:rules="[validators.required]"
/>
<v-select
v-if="households"
v-model="newUserData.household"
:items="households"
rounded
class="rounded-lg"
item-text="name"
item-value="name"
:return-object="false"
filled
:label="$t('group.user-group')"
:label="$t('household.user-household')"
:rules="[validators.required]"
></v-select>
/>
<AutoForm v-model="newUserData" :items="userForm" />
</v-card-text>
</v-card>
@ -34,9 +47,10 @@
</template>
<script lang="ts">
import { defineComponent, useRouter, reactive, ref, toRefs } from "@nuxtjs/composition-api";
import { computed, defineComponent, useRouter, reactive, ref, toRefs, watch } from "@nuxtjs/composition-api";
import { useAdminApi } from "~/composables/api";
import { useGroups } from "~/composables/use-groups";
import { useHouseholds } from "~/composables/use-households";
import { useUserForm } from "~/composables/use-users";
import { validators } from "~/composables/use-validators";
import { VForm } from "~/types/vuetify";
@ -46,6 +60,7 @@ export default defineComponent({
setup() {
const { userForm } = useUserForm();
const { groups } = useGroups();
const { useHouseholdsInGroup } = useHouseholds();
const router = useRouter();
// ==============================================
@ -55,13 +70,20 @@ export default defineComponent({
const adminApi = useAdminApi();
const selectedGroupId = ref<string>("");
const households = useHouseholdsInGroup(selectedGroupId);
const selectedGroup = computed(() => {
return groups.value?.find((group) => group.id === selectedGroupId.value);
});
const state = reactive({
newUserData: {
username: "",
fullName: "",
email: "",
admin: false,
group: "",
group: selectedGroup.value?.name || "",
household: "",
advanced: false,
canInvite: false,
canManage: false,
@ -70,6 +92,10 @@ export default defineComponent({
authMethod: "Mealie",
},
});
watch(selectedGroup, (newGroup) => {
state.newUserData.group = newGroup?.name || "";
state.newUserData.household = "";
});
async function handleSubmit() {
if (!refNewUserForm.value?.validate()) return;
@ -87,6 +113,8 @@ export default defineComponent({
refNewUserForm,
handleSubmit,
groups,
selectedGroupId,
households,
validators,
};
},

View file

@ -18,7 +18,7 @@
<BaseCardSectionTitle :title="$tc('user.user-management')"> </BaseCardSectionTitle>
<section>
<v-toolbar color="background" flat class="justify-between">
<v-toolbar color="transparent" flat class="justify-between">
<BaseButton to="/admin/manage/users/create" class="mr-2">
{{ $t("general.create") }}
</BaseButton>
@ -129,6 +129,7 @@ export default defineComponent({
{ text: i18n.t("user.full-name"), value: "fullName" },
{ text: i18n.t("user.email"), value: "email" },
{ text: i18n.t("group.group"), value: "group" },
{ text: i18n.t("household.household"), value: "household" },
{ text: i18n.t("user.auth-method"), value: "authMethod" },
{ text: i18n.t("user.admin"), value: "admin" },
{ text: i18n.t("general.delete"), value: "actions", sortable: false, align: "center" },

View file

@ -312,7 +312,6 @@ export default defineComponent({
const preferences = {
...data.preferences,
privateGroup: !commonSettings.value.makeGroupRecipesPublic,
recipePublic: commonSettings.value.makeGroupRecipesPublic,
}
const payload = {
@ -327,6 +326,32 @@ export default defineComponent({
}
}
async function updateHousehold() {
// @ts-ignore-next-line user will never be null here
const { data } = await api.households.getOne($auth.user?.householdId);
if (!data || !data.preferences) {
alert.error(i18n.tc("events.something-went-wrong"));
return;
}
const preferences = {
...data.preferences,
privateHousehold: !commonSettings.value.makeGroupRecipesPublic,
recipePublic: commonSettings.value.makeGroupRecipesPublic,
}
const payload = {
...data,
preferences,
}
// @ts-ignore-next-line user will never be null here
const { response } = await api.households.updateOne($auth.user?.householdId, payload);
if (!response || response.status !== 200) {
alert.error(i18n.tc("events.something-went-wrong"));
}
}
async function seedFoods() {
const { response } = await api.seeders.foods({ locale: locale.value })
if (!response || response.status !== 200) {
@ -365,6 +390,7 @@ export default defineComponent({
async function submitCommonSettings() {
const tasks = [
updateGroup(),
updateHousehold(),
seedData(),
]

View file

@ -367,6 +367,11 @@ export default defineComponent({
icon: $globals.icons.group,
value: data.defaultGroup,
},
{
name: i18n.t("about.default-household"),
icon: $globals.icons.household,
value: data.defaultHousehold,
},
{
slot: "recipe-scraper",
name: i18n.t("settings.recipe-scraper-version"),

View file

@ -24,17 +24,18 @@ export default defineComponent({
const groupName = ref<string>("");
const queryFilter = ref<string>("");
async function fetchGroup() {
const { data } = await api.groups.getCurrentUserGroup();
async function fetchHousehold() {
const { data } = await api.households.getCurrentUserHousehold();
if (data) {
queryFilter.value = `recipe.group_id="${data.id}"`;
groupName.value = data.name;
// TODO: once users are able to fetch other households' recipes, remove the household filter
queryFilter.value = `recipe.group_id="${data.groupId}" AND recipe.household_id="${data.id}"`;
groupName.value = data.group;
}
ready.value = true;
}
fetchGroup();
fetchHousehold();
return {
groupName,
queryFilter,

View file

@ -121,7 +121,7 @@
import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api";
import { validators } from "~/composables/use-validators";
import { useGroupRecipeActions, useGroupRecipeActionData } from "~/composables/use-group-recipe-actions";
import { GroupRecipeActionOut } from "~/lib/api/types/group";
import { GroupRecipeActionOut } from "~/lib/api/types/household";
export default defineComponent({
setup() {

View file

@ -25,139 +25,22 @@
<DocLink class="mt-2" link="/documentation/getting-started/faq/#how-do-private-groups-and-recipes-work" />
</div>
</div>
<v-select
v-model="group.preferences.firstDayOfWeek"
:prepend-icon="$globals.icons.calendarWeekBegin"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
@change="groupActions.updatePreferences()"
/>
</section>
<section v-if="group">
<BaseCardSectionTitle class="mt-10" :title="$tc('group.default-recipe-preferences')">
{{ $t("group.default-recipe-preferences-description") }}
</BaseCardSectionTitle>
<div class="preference-container">
<div v-for="p in preferencesEditor" :key="p.key">
<v-checkbox
v-model="group.preferences[p.key]"
hide-details
dense
:label="p.label"
@change="groupActions.updatePreferences()"
/>
<p class="ml-8 text-subtitle-2 my-0 py-0">
{{ p.description }}
</p>
</div>
</div>
</section>
</v-container>
</template>
<script lang="ts">
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
import { defineComponent } from "@nuxtjs/composition-api";
import { useGroupSelf } from "~/composables/use-groups";
import { ReadGroupPreferences } from "~/lib/api/types/group";
export default defineComponent({
middleware: ["auth", "can-manage-only"],
setup() {
const { group, actions: groupActions } = useGroupSelf();
const { i18n } = useContext();
type Preference = {
key: keyof ReadGroupPreferences;
value: boolean;
label: string;
description: string;
};
const preferencesEditor = computed<Preference[]>(() => {
if (!group.value || !group.value.preferences) {
return [];
}
return [
{
key: "recipePublic",
value: group.value.preferences.recipePublic || false,
label: i18n.t("group.allow-users-outside-of-your-group-to-see-your-recipes"),
description: i18n.t("group.allow-users-outside-of-your-group-to-see-your-recipes-description"),
} as Preference,
{
key: "recipeShowNutrition",
value: group.value.preferences.recipeShowNutrition || false,
label: i18n.t("group.show-nutrition-information"),
description: i18n.t("group.show-nutrition-information-description"),
} as Preference,
{
key: "recipeShowAssets",
value: group.value.preferences.recipeShowAssets || false,
label: i18n.t("group.show-recipe-assets"),
description: i18n.t("group.show-recipe-assets-description"),
} as Preference,
{
key: "recipeLandscapeView",
value: group.value.preferences.recipeLandscapeView || false,
label: i18n.t("group.default-to-landscape-view"),
description: i18n.t("group.default-to-landscape-view-description"),
} as Preference,
{
key: "recipeDisableComments",
value: group.value.preferences.recipeDisableComments || false,
label: i18n.t("group.disable-users-from-commenting-on-recipes"),
description: i18n.t("group.disable-users-from-commenting-on-recipes-description"),
} as Preference,
{
key: "recipeDisableAmount",
value: group.value.preferences.recipeDisableAmount || false,
label: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food"),
description: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food-description"),
} as Preference,
];
});
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
return {
group,
groupActions,
allDays,
preferencesEditor,
};
},
head() {

View file

@ -0,0 +1,178 @@
<template>
<v-container class="narrow-container">
<BasePageTitle class="mb-5">
<template #header>
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-group-settings.svg')"></v-img>
</template>
<template #title> {{ $t("profile.household-settings") }} </template>
{{ $t("profile.household-description") }}
</BasePageTitle>
<section v-if="household">
<BaseCardSectionTitle class="mt-10" :title="$tc('household.household-preferences')"></BaseCardSectionTitle>
<div class="mb-6">
<v-checkbox
v-model="household.preferences.privateHousehold"
hide-details
dense
:label="$t('household.private-household')"
@change="householdActions.updatePreferences()"
/>
<div class="ml-8">
<p class="text-subtitle-2 my-0 py-0">
{{ $t("household.private-household-description") }}
</p>
<DocLink class="mt-2" link="/documentation/getting-started/faq/#how-do-private-groups-and-recipes-work" />
</div>
</div>
<v-select
v-model="household.preferences.firstDayOfWeek"
:prepend-icon="$globals.icons.calendarWeekBegin"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
@change="householdActions.updatePreferences()"
/>
</section>
<section v-if="household">
<BaseCardSectionTitle class="mt-10" :title="$tc('group.default-recipe-preferences')">
{{ $t("household.default-recipe-preferences-description") }}
</BaseCardSectionTitle>
<div class="preference-container">
<div v-for="p in preferencesEditor" :key="p.key">
<v-checkbox
v-model="household.preferences[p.key]"
hide-details
dense
:label="p.label"
@change="householdActions.updatePreferences()"
/>
<p class="ml-8 text-subtitle-2 my-0 py-0">
{{ p.description }}
</p>
</div>
</div>
</section>
</v-container>
</template>
<script lang="ts">
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
import { useHouseholdSelf } from "~/composables/use-households";
import { ReadHouseholdPreferences } from "~/lib/api/types/household";
export default defineComponent({
middleware: ["auth", "can-manage-only"],
setup() {
const { household, actions: householdActions } = useHouseholdSelf();
const { i18n } = useContext();
type Preference = {
key: keyof ReadHouseholdPreferences;
value: boolean;
label: string;
description: string;
};
const preferencesEditor = computed<Preference[]>(() => {
if (!household.value || !household.value.preferences) {
return [];
}
return [
{
key: "recipePublic",
value: household.value.preferences.recipePublic || false,
label: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes"),
description: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes-description"),
} as Preference,
{
key: "recipeShowNutrition",
value: household.value.preferences.recipeShowNutrition || false,
label: i18n.t("group.show-nutrition-information"),
description: i18n.t("group.show-nutrition-information-description"),
} as Preference,
{
key: "recipeShowAssets",
value: household.value.preferences.recipeShowAssets || false,
label: i18n.t("group.show-recipe-assets"),
description: i18n.t("group.show-recipe-assets-description"),
} as Preference,
{
key: "recipeLandscapeView",
value: household.value.preferences.recipeLandscapeView || false,
label: i18n.t("group.default-to-landscape-view"),
description: i18n.t("group.default-to-landscape-view-description"),
} as Preference,
{
key: "recipeDisableComments",
value: household.value.preferences.recipeDisableComments || false,
label: i18n.t("group.disable-users-from-commenting-on-recipes"),
description: i18n.t("group.disable-users-from-commenting-on-recipes-description"),
} as Preference,
{
key: "recipeDisableAmount",
value: household.value.preferences.recipeDisableAmount || false,
label: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food"),
description: i18n.t("group.disable-organizing-recipe-ingredients-by-units-and-food-description"),
} as Preference,
];
});
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
return {
household,
householdActions,
allDays,
preferencesEditor,
};
},
head() {
return {
title: this.$t("household.household") as string,
};
},
});
</script>
<style lang="css">
.preference-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 600px;
}
</style>

View file

@ -39,10 +39,10 @@
<div class="d-flex flex-wrap align-center justify-space-between mb-2">
<v-tabs style="width: fit-content;">
<v-tab :to="`/group/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab>
<v-tab :to="`/group/mealplan/planner/edit`">{{ $t('general.edit') }}</v-tab>
<v-tab :to="`/household/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab>
<v-tab :to="`/household/mealplan/planner/edit`">{{ $t('general.edit') }}</v-tab>
</v-tabs>
<ButtonLink :icon="$globals.icons.calendar" :to="`/group/mealplan/settings`" :text="$tc('general.settings')" />
<ButtonLink :icon="$globals.icons.calendar" :to="`/household/mealplan/settings`" :text="$tc('general.settings')" />
</div>
<div>
@ -56,7 +56,7 @@
<script lang="ts">
import { computed, defineComponent, ref, useRoute, useRouter, watch } from "@nuxtjs/composition-api";
import { isSameDay, addDays, parseISO } from "date-fns";
import { useGroupSelf } from "~/composables/use-groups";
import { useHouseholdSelf } from "~/composables/use-households";
import { useMealplans } from "~/composables/use-group-mealplan";
import { useUserMealPlanPreferences } from "~/composables/use-users/preferences";
@ -65,7 +65,7 @@ export default defineComponent({
setup() {
const route = useRoute();
const router = useRouter();
const { group } = useGroupSelf();
const { household } = useHouseholdSelf();
const mealPlanPreferences = useUserMealPlanPreferences();
const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7);
@ -74,8 +74,8 @@ export default defineComponent({
});
// Force to /view if current route is /planner
if (route.value.path === "/group/mealplan/planner") {
router.push("/group/mealplan/planner/view");
if (route.value.path === "/household/mealplan/planner") {
router.push("/household/mealplan/planner/view");
}
function fmtYYYYMMDD(date: Date) {
@ -95,7 +95,7 @@ export default defineComponent({
});
const firstDayOfWeek = computed(() => {
return group.value?.preferences?.firstDayOfWeek || 0;
return household.value?.preferences?.firstDayOfWeek || 0;
});
const weekRange = computed(() => {

View file

@ -229,7 +229,7 @@ import { useMealplans, usePlanTypeOptions, getEntryTypeText } from "~/composable
import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue";
import { PlanEntryType, UpdatePlanEntry } from "~/lib/api/types/meal-plan";
import { useUserApi } from "~/composables/api";
import { useGroupSelf } from "~/composables/use-groups";
import { useHouseholdSelf } from "~/composables/use-households";
import { useRecipeSearch } from "~/composables/recipes/use-recipe-search";
export default defineComponent({
@ -249,7 +249,7 @@ export default defineComponent({
},
setup(props) {
const api = useUserApi();
const { group } = useGroupSelf();
const { household } = useHouseholdSelf();
const state = ref({
dialog: false,
@ -257,7 +257,7 @@ export default defineComponent({
});
const firstDayOfWeek = computed(() => {
return group.value?.preferences?.firstDayOfWeek || 0;
return household.value?.preferences?.firstDayOfWeek || 0;
});
function onMoveCallback(evt: SortableEvent) {
@ -308,7 +308,7 @@ export default defineComponent({
entryType: "dinner" as PlanEntryType,
existing: false,
id: 0,
groupId: ""
groupId: "",
});
function openDialog(date: Date) {

View file

@ -54,7 +54,7 @@
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
import { MealsByDate } from "./types";
import { ReadPlanEntry } from "~/lib/api/types/meal-plan";
import GroupMealPlanDayContextMenu from "~/components/Domain/Group/GroupMealPlanDayContextMenu.vue";
import GroupMealPlanDayContextMenu from "~/components/Domain/Household/GroupMealPlanDayContextMenu.vue";
import RecipeCardMobile from "~/components/Domain/Recipe/RecipeCardMobile.vue";
import { RecipeSummary } from "~/lib/api/types/recipe";

View file

@ -89,7 +89,7 @@
import { defineComponent, ref, useAsync } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
import GroupMealPlanRuleForm from "~/components/Domain/Group/GroupMealPlanRuleForm.vue";
import GroupMealPlanRuleForm from "~/components/Domain/Household/GroupMealPlanRuleForm.vue";
import { useAsyncKey } from "~/composables/use-utils";
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";

View file

@ -97,7 +97,7 @@ export default defineComponent({
];
async function refreshMembers() {
const { data } = await api.groups.fetchMembers();
const { data } = await api.households.fetchMembers();
if (data) {
members.value = data;
}
@ -111,7 +111,7 @@ export default defineComponent({
canOrganize: user.canOrganize,
};
await api.groups.setMemberPermissions(payload);
await api.households.setMemberPermissions(payload);
}
onMounted(async () => {

View file

@ -109,7 +109,7 @@
import { defineComponent, useAsync, reactive, useContext, toRefs } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
import { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/group";
import { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/household";
interface OptionKey {
text: string;

View file

@ -45,7 +45,7 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { useGroupWebhooks, timeUTC } from "~/composables/use-group-webhooks";
import GroupWebhookEditor from "~/components/Domain/Group/GroupWebhookEditor.vue";
import GroupWebhookEditor from "~/components/Domain/Household/GroupWebhookEditor.vue";
import { alert } from "~/composables/use-toast";
export default defineComponent({

View file

@ -294,8 +294,8 @@ import { useCopyList } from "~/composables/use-copy";
import { useUserApi } from "~/composables/api";
import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue"
import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue";
import { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/group";
import { UserSummary } from "~/lib/api/types/user";
import { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/household";
import { UserOut } from "~/lib/api/types/user";
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue";
import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store";
@ -444,7 +444,7 @@ export default defineComponent({
unchecked: shoppingList.value?.listItems?.filter((item) => !item.checked) ?? [],
checked: shoppingList.value?.listItems
?.filter((item) => item.checked)
.sort((a, b) => (a.updateAt < b.updateAt ? 1 : -1))
.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1))
?? [],
};
});
@ -863,7 +863,7 @@ export default defineComponent({
item.position = shoppingList.value.listItems.length;
// set a temporary updatedAt timestamp prior to refresh so it appears at the top of the checked items
item.updateAt = new Date().toISOString();
item.updatedAt = new Date().toISOString();
}
// make updates reflect immediately
@ -934,7 +934,7 @@ export default defineComponent({
: 0;
createListItemData.value.createdAt = new Date().toISOString();
createListItemData.value.updateAt = createListItemData.value.createdAt;
createListItemData.value.updatedAt = createListItemData.value.createdAt;
updateListItemOrder();
@ -1020,16 +1020,16 @@ export default defineComponent({
// ===============================================================
// Shopping List Settings
const allUsers = ref<UserSummary[]>([]);
const allUsers = ref<UserOut[]>([]);
const currentUserId = ref<string | undefined>();
async function fetchAllUsers() {
const { data } = await userApi.users.getGroupUsers(1, -1, { orderBy: "full_name", orderDirection: "asc" });
const { data } = await userApi.households.fetchMembers();
if (!data) {
return;
}
// update current user
allUsers.value = data.items;
allUsers.value = data.sort((a, b) => ((a.fullName || "") < (b.fullName || "") ? -1 : 1));
currentUserId.value = shoppingList.value?.userId;
}

View file

@ -7,7 +7,7 @@
<p class="subtitle-1 mb-0 text-center">
{{ $t('profile.description') }}
</p>
<v-card flat color="background" width="100%" max-width="600px">
<v-card flat color="transparent" width="100%" max-width="600px">
<v-card-actions class="d-flex justify-center my-4">
<v-btn v-if="$auth.user.canInvite" outlined rounded @click="getSignupLink()">
<v-icon left>
@ -57,9 +57,9 @@
<v-row tag="section">
<v-col cols="12" sm="12" md="12">
<v-card outlined>
<v-card-title class="headline pb-0"> {{ $t('profile.group-statistics') }} </v-card-title>
<v-card-title class="headline pb-0"> {{ $t('profile.household-statistics') }} </v-card-title>
<v-card-text class="py-0">
{{ $t('profile.group-statistics-description') }}
{{ $t('profile.household-statistics-description') }}
</v-card-text>
<v-card-text class="d-flex flex-wrap justify-center align-center" style="gap: 0.8rem">
<StatsCards
@ -106,8 +106,66 @@
</AdvancedOnly>
</v-row>
</section>
<v-divider class="my-7"></v-divider>
<v-divider class="my-7" />
<section>
<div>
<h3 class="headline">{{ $t('household.household') }}</h3>
<p>{{ $t('profile.household-description') }}</p>
</div>
<v-row tag="section">
<v-col v-if="$auth.user.canManage" cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.household-settings'), to: `/household` }"
:image="require('~/static/svgs/manage-group-settings.svg')"
>
<template #title> {{ $t('profile.household-settings') }} </template>
{{ $t('profile.household-settings-description') }}
</UserProfileLinkCard>
</v-col>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-cookbooks'), to: `/g/${groupSlug}/cookbooks` }"
:image="require('~/static/svgs/manage-cookbooks.svg')"
>
<template #title> {{ $t('sidebar.cookbooks') }} </template>
{{ $t('profile.cookbooks-description') }}
</UserProfileLinkCard>
</v-col>
<v-col v-if="user.canManage" cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-members'), to: `/household/members` }"
:image="require('~/static/svgs/manage-members.svg')"
>
<template #title> {{ $t('profile.members') }} </template>
{{ $t('profile.members-description') }}
</UserProfileLinkCard>
</v-col>
<AdvancedOnly>
<v-col v-if="user.advanced" cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-webhooks'), to: `/household/webhooks` }"
:image="require('~/static/svgs/manage-webhooks.svg')"
>
<template #title> {{ $t('settings.webhooks.webhooks') }} </template>
{{ $t('profile.webhooks-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
<AdvancedOnly>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-notifiers'), to: `/household/notifiers` }"
:image="require('~/static/svgs/manage-notifiers.svg')"
>
<template #title> {{ $t('profile.notifiers') }} </template>
{{ $t('profile.notifiers-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
</v-row>
</section>
<v-divider class="my-7" />
<section v-if="$auth.user.canManage || $auth.user.canOrganize || $auth.user.advanced">
<div>
<h3 class="headline">{{ $t('group.group') }}</h3>
<p>{{ $t('profile.group-description') }}</p>
@ -122,46 +180,6 @@
{{ $t('profile.group-settings-description') }}
</UserProfileLinkCard>
</v-col>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-cookbooks'), to: `/g/${groupSlug}/cookbooks` }"
:image="require('~/static/svgs/manage-cookbooks.svg')"
>
<template #title> {{ $t('sidebar.cookbooks') }} </template>
{{ $t('profile.cookbooks-description') }}
</UserProfileLinkCard>
</v-col>
<v-col v-if="user.canManage" cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-members'), to: `/group/members` }"
:image="require('~/static/svgs/manage-members.svg')"
>
<template #title> {{ $t('profile.members') }} </template>
{{ $t('profile.members-description') }}
</UserProfileLinkCard>
</v-col>
<AdvancedOnly>
<v-col v-if="user.advanced" cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-webhooks'), to: `/group/webhooks` }"
:image="require('~/static/svgs/manage-webhooks.svg')"
>
<template #title> {{ $t('settings.webhooks.webhooks') }} </template>
{{ $t('profile.webhooks-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
<AdvancedOnly>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: $tc('profile.manage-notifiers'), to: `/group/notifiers` }"
:image="require('~/static/svgs/manage-notifiers.svg')"
>
<template #title> {{ $t('profile.notifiers') }} </template>
{{ $t('profile.notifiers-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
<!-- $auth.user.canOrganize should not be null because of the auth middleware -->
<v-col v-if="$auth.user.canOrganize" cols="12" sm="12" md="6">
<UserProfileLinkCard
@ -224,7 +242,7 @@ export default defineComponent({
const api = useUserApi();
async function getSignupLink() {
const { data } = await api.groups.createInvitation({ uses: 1 });
const { data } = await api.households.createInvitation({ uses: 1 });
if (data) {
token.value = data.token;
generatedSignupLink.value = constructLink(data.token);
@ -272,7 +290,7 @@ export default defineComponent({
});
const stats = useAsync(async () => {
const { data } = await api.groups.statistics();
const { data } = await api.households.statistics();
if (data) {
return data;
@ -306,7 +324,7 @@ export default defineComponent({
const statsTo = computed<{ [key: string]: string }>(() => { return {
totalRecipes: `/g/${groupSlug.value}/`,
totalUsers: "/group/members",
totalUsers: "/household/members",
totalCategories: `/g/${groupSlug.value}/recipes/categories`,
totalTags: `/g/${groupSlug.value}/recipes/tags`,
totalTools: `/g/${groupSlug.value}/recipes/tools`,